ThreeDotsLabs / wild-workouts-go-ddd-example

Go DDD example application. Complete project to show how to apply DDD, Clean Architecture, and CQRS by practical refactoring.
https://threedots.tech
MIT License
5.14k stars 472 forks source link

Couple of ideas #16

Open blaggacao opened 3 years ago

blaggacao commented 3 years ago

While continuing to work on ddd-gen (example) I had a couple of ideas / made a couple of choices worth sharing here:



blaggacao commented 3 years ago

I'm playing with the idea of first (above the line) vs second (blow the line)

// Repository knows how to persist the service's aggregate
type Repository interface {
    // Add knows how to create an initialized instance of an aggregate
    // it expects an initialized instance be return from an add funcion or nil to bail out
    Add(ctx context.Context,f func() (*account.Account)) (uuid.UUID, error)
    // Add knows how to remove an identifiable instance of an aggregate
    // it also returns a copy to a remove funtion in order to bail out
    Rem(ctx context.Context, i Identifiable, f func(a account.Account) bool) error
    // Update knows how to update an identifiable instance of an aggregate
    Update(ctx context.Context, i Identifiable, f func(a *account.Account) bool) error
}

// Identifiable can be identified by the Repository
type Identifiable interface {
    // Identifer knows how to identify an object
    Identifier() uuid.UUID
}

// Store knows how to read and write an entity
type Store interface {
    // Load knows how to load an entity
    Load(ctx context.Context, uuid uuid.UUID) (a *account.Account, err error)
    // Save knows how to save an entity
    Save(ctx context.Context, a *account.Account) error
}
// Identifiable can be identified so that the store can load it
type Identifiable interface {
    // Identifer knows how to identify an object
    Identifier() uuid.UUID
}

The latter approach has several benefits in my eyes:

// cave: pseudo-code ...
func(h *DoSomethingHandler) Handle(ctx context.Context, ds DoSomething) error {
    old, err := Load(ctx, ds.Identifier())
    new := old.Copy()
    ...
    ok := h.pol.Can(ctx, ds, "DoSomething", new) // check policy
    if err = h.handle(ctx, new); err != nil { // handle domain logic
        return errwrap.Wrap(ErrInDomain, err)
    }
    if err = Save(ctx, new); err != nil { // nil to delete
        return errwrap.Wrap(ErrDuringSave, err)
    }
    ...
    err = Publish(ctx, &SomethingDone{...})
    if err != nil {
        Save(ctx, old)
    }
}