Aggregate Command Handling with Dependencies
Opened this issue · 0 comments
bounoable commented
Context
The *aggregate.Base
type embeds a command.Handler
to allow registration of commands in the aggregate constructor, like this:
package example
type Movie struct {
*aggregate.Base
}
func NewMovie(id uuid.UUID) *Movie {
m := &Movie{Base: aggregate.New(...))
command.ApplyWith(m, m.Create, "movie.create")
return m
}
type CreateParams struct {
Name string
Rating int8
}
func (m *Movie) Create(params CreateParams) error {
// aggregate.Next(...)
return nil
}
This works well as long as the Create
method does not depend on any external dependencies, like it does in the following example (context.Context
and RatingService
):
package example
type Movie struct {
*aggregate.Base
}
func NewMovie(id uuid.UUID) *Movie {
m := &Movie{Base: aggregate.New(...))
command.ApplyWith(m, func(name string) error {
// Here, we would need access to a context.Context and RatingService
return m.Create(???, name, ???)
}, "movie.create")
return m
}
type CreateParams struct {
Name string
Rating int8
}
type RatingService interface {
Rating(ctx context.Context, name string) (int8, error)
}
func (m *Movie) Create(ctx context.Context, name string, svc RatingService) error {
rating, err := svc.Rating(ctx, name)
if err != nil {
return err
}
aggregate.Next(m, "movie.created", CreateParams{
Name: name,
Rating: rating,
})
return nil
}
Ideas
a.command.ApplyContextWith
helper
Idea: Add a new command.ApplyContextWith
helper function to register command handlers that accept a generic command.Context
type.
package example
type Movie struct { ... }
func NewMovie() *Movie {
m := &Movie{...}
command.ApplyContextWith(m, func(ctx command.Ctx[string]) error {
name := ctx.Payload()
return m.Create(ctx, name, ???) // still no RatingService available
}, "movie.create")
return m
}
b. Dependency Injection Container
Idea: Provide a DI Container to command.Context
to provide dependencies to aggregates when setting up commands.
// commands.go
package example
import "github.com/modernice/goes/command/handler"
func HandleCommands(ctx context.Context, svc RatingService, bus command.Bus, repo aggregate.Repository) <-chan error {
return handler.New(NewMovie, repo, bus).MustHandle(ctx, command.Provide(svc))
}
// movie.go
package example
func NewMovie() *Movie {
m := &Movie{...}
command.ApplyContextWith(m, func(ctx command.Ctx[string]) error {
name := ctx.Payload()
ratingSvc := di.Inject[RatingService](ctx)
return m.Create(ctx, name, ratingSvc)
}, "movie.create")
return m
}