CodeRigor

LESSON

Put mutable state behind a repository boundary

Use a synchronized bounded in-memory store with monotonic IDs, stable ordering and snapshots that cannot mutate stored notes.

Handlers should translate HTTP into application operations rather than own a global map. A small Repository interface makes that boundary visible: Create, List, Get and Delete.

The supplied MemoryStore is a complete bounded local implementation. It keeps at most 100 active notes, assigns monotonic IDs and synchronizes access. Those choices are sufficient for this learning API, but they do not provide durable storage, multi-process consistency or authorization.

A handler depends on a contract
start / 3
HTTP translation calls a repository interface. The memory implementation owns its map, identity counter and mutex; tests can inject another implementation.
A handler depends on a contract
HTTP translation calls a repository interface. The memory implementation owns its map, identity counter and mutex; tests can inject another implementation.

In Create, the check and mutation must be one critical section. If two goroutines check capacity before either inserts, separate locks can permit an extra record. Likewise, reading and incrementing the ID counter separately can reuse an identity.

Duplicate titles are allowed. A title is editable data, not the record key. IDs stay monotonic after deletion so a later note does not silently reuse an earlier path.

Go
package main
import ("fmt")
func main(){
notes:=map[int]string{1:"same",2:"same"};fmt.Println(len(notes));delete(notes,1);fmt.Println(notes[2])
}

List returns a new non-nil slice sorted by ID. A non-nil empty slice encodes as [] rather than null, which keeps the API’s empty-collection response stable. Sorting avoids exposing Go map iteration order as an accidental public contract.

A returned container must not alias mutable internal storage. Here Note contains an int and immutable string value, so copying the Note is enough. If a future record contains slices, maps or pointers, decide whether deeper copying or another ownership strategy is needed.

The interface also supports failure testing. A fake repository can return an internal error so handler tests verify a stable 500 response without leaking private backend details. List’s interface deliberately has no error return in this tiny lab; a real storage adapter would need to revisit that limitation.

Return snapshots, not writable internals
start / 3
The repository copies values into stable order. A caller can edit its returned slice without altering stored notes.
Return snapshots, not writable internals
The repository copies values into stable order. A caller can edit its returned slice without altering stored notes.
Go
package main
import ("fmt";"encoding/json")
func main(){
empty:=make([]int,0);var missing []int;a,_:=json.Marshal(empty);b,_:=json.Marshal(missing);fmt.Println(string(a),string(b))
}

Complete Create, then test full capacity, deletion followed by creation, repeated titles and concurrent calls. Inspect the supplied snapshot/get/delete contracts as part of the boundary.

Next we write a small read-only contract probe and show why status-only tests miss broken behavior.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Optional design discussion of handlers, dependencies and tests. This 2019 talk predates current Go routing changes; use the current net/http documentation and this course’s explicit route contract for the lab.