liveBook · Manning

Repository pattern

As explained in Learn Go with Pocket-Sized Projects

The repository pattern is an abstraction that mediates between an application’s domain model and persistent data storage.

A repository gives application logic a place to store and retrieve domain objects without putting storage code in an API handler. For a small game service, an in-memory repository is quick to build, but games disappear when the instance stops. With multiple instances, a request may reach one that does not hold the game. section 8.3 section 8.7

Keep storage in an internal/repository package and use domain types as the shared language between storage and handlers. An in-memory implementation can store those types directly. If a database needs storage-specific fields, use a separate storage structure and adapt it to the domain so the schema can evolve independently. section 8.3.1 section 8.4

A compact Go implementation starts with:

  • A repository value containing a map keyed by game ID, plus New to initialize the map. section 8.4.2
  • Separate Add, Find, and Update operations. Add rejects an existing ID; Find reports a missing ID; Update must not silently insert one. section 8.4.2
  • One repository initialized at service startup and passed to the router. Give each handler only the methods it needs through a small interface, such as Add for game creation. section 8.4.3
Listing 8.30 newgame/handler.go: Minimal interface
type gameAdder interface { #1
    Add(game session.Game) error
}

// Handler returns the handler for the game creation endpoint.
func Handler(db gameAdder) http.HandlerFunc { #2
    return func(w http.ResponseWriter, _ *http.Request) {
        // ...
    }
}

The game-creation handler accepts a one-method interface rather than requiring the concrete repository; a test stub can implement that same interface. section 8.4.3

Test repository operations and handler behavior separately. Because HTTP requests run concurrently, protect a shared map from concurrent access with a mutex. For list operations, sort the results before returning them: map-backed storage alone does not give clients or tests a dependable order. section 8.4.2 section 8.4.3 section 10.5.1

A second use: saved habits

A habit-tracking service can start with another internal, in-memory repository. Its domain Create function accepts a small storage interface and wraps a save error with context. Repository methods take context.Context in anticipation of database calls that may need to stop when a client interrupts them. This is a practical first version, not a scalable persistence solution. section 10.3.3

How Learn Go with Pocket-Sized Projects covers it

How deep it goes
Works through an in-memory map, constructor, operations, injection, tests, and concurrency; defers database implementation.
Examples
Uses stored games, a stubbed game-creation handler, and a sorted list of saved habits.
What it assumes
Assumes familiarity with Go handlers and basic tests while walking through repository construction and interface-based stubbing.
What you can do after
Build it from scratch as an injectable in-memory repository, then test its operations and protect shared state.
How it connects
Connects repositories to domain models, dependency injection, small Go interfaces, mutexes, and deterministic API results.

Also explained in

Compare how 9 books explain repository pattern →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Go with Pocket-Sized Projects. The section links point to the parts of the book it draws on.