🗄️ B3 · Repository Layer: in-memory Map
B3 · Repository Layer: in-memory Map: The repository layer is the data's **archive clerk**: it does raw storage jobs — "store this bug", "fetch number 42", "delete" — but knows N
The repository layer is the data's **archive clerk**: it does raw storage jobs — "store this bug", "fetch number 42", "delete" — but knows NO business rules ("can this user delete?" is not its question). On this page, instead of a real database we use an in-memory `Map `; the goal is not to learn storage tech but to see the **separation of responsibilities across layers**. But why put storage in a separate layer — couldn't the controller write straight to the Map? Because if storage moves from a Map to a real database one day, only this layer should change while the business logic and controller above stay the same — the single-responsibility principle. In Java the equivalent is an `interface BugRepository` and its implementation; the caller knows only the `save`/`findById` signatures, not whether a Map or JPA sits behind it. In QA the repository is the key to **test isolation**: an in-memory repository can be reset before each test, so tests do not pollute each other's data — the "test fails because of a record left by the previous test" nightmare is solved here.
In-Memory Repository
**🐞 Defect Birth — if `findById` returns `null` instead of an empty `Optional`** **Code:** `public Bug findById(Long id) { return store.get(id); }` — returns `null` directly when the record is absent (no Optional). **What happens:** when the service calls `repository.findById(999).getSeverity()`, the missing record returns `null` and `.getSeverity()` throws a **NullPointerException**. Result: `GET /api/v1/bugs/999` returns **500 Internal Server Error** instead of 404. **Why sneaky:** "record not found" is actually a normal, expected case (404) — but returning `null` turns it into a server crash (500). A tester looking at the symptom says "server bug", while the root cause is a missing null-safety. **Where the tester catches it:** sending `GET /api/v1/bugs/999` with a nonexistent id and getting 500 while expecting 404. Returning `Optional` forces the upper layer to say "return 404 if absent" and prevents the crash.
🎬 404 or 500? Empty Optional vs null
A request wants a nonexistent record: GET /api/v1/bugs/999. The outcome depends on what the repository returns.
Path A — the repository returns null. When the upper layer calls `.getSeverity()`, a NullPointerException blows up → 500 Internal Server Error.
Path B — the repository returns an empty Optional. "No record" is no longer a crash but a handleable state.
An empty Optional forces the upper layer to say "return 404 if absent". The client gets the correct, expected response: 404 Not Found.
The lesson — "Not found" is a normal state (404), not a crash (500). Optional bakes this difference into the structure. The tester verifies by expecting 404 for a nonexistent id.
Why Layer Separation Brings Test Isolation
Repository only stores…
The in-memory Map has no business rules; it is just save/find/delete. This simplicity makes it ideal for tests.
Reset before each test…
Clearing the Map (store.clear()) wipes all data; each test starts from a fresh state.