Separating Data Store from the Domain

3 months ago - Direct link

Hey Gophers,

I'm working on a personal project and am just working on figuring out how I'd like to structure my code. I'm running into a bit of a snag and wanted to see how you all might handle this.

I would like to use the "repository" pattern you see presented in Domain Driven Design (and used in plenty of other places). The idea is simple, to have a struct that acts as a repository and handles all interactions with whatever kind of data store I like. This way I avoid cluttering the domain with mapping tables to structs and executing queries.

Simple enough, you might end up with an interface that looks like this:

package inventory

import "context"

type Repository interface {
GetProduct(ctx context.Context, sku string) (Product, error)
AddProduct(ctx context.Context, product Product, quantity int) (error)
AddTransaction(ctx context.Context, product Product, quantity int) (error)

Pretty nice. Maybe the factory function that creates this repository takes in your database connection and the resulting struct uses that DB connection to implement the "GetProduct". This is an especially nice pattern because it makes mocking the "Repository" interface dead simple.

The rub I'm having here is this. Imagine that we wanted to have an atomic transaction that updated two tables. For instance you need to call the AddProduct function, and then the AddTransaction function above. We would want to begin a transaction, execute our updates, and commit if everything looks good. That transaction would need to be in the "model" layer, allowing the model to decide whether it makes sense to commit or not.

Your model layer function might look something like this:

package inventory

type Inventory struct {
repo *Repository

func (i *Inventory) AddInventory(ctx context.Context, product Product, quantity int) error {
err := repo.AddPr...

Go to article →