Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aggregate Command Handling with Dependencies #159

Open
bounoable opened this issue Sep 27, 2023 · 0 comments
Open

Aggregate Command Handling with Dependencies #159

bounoable opened this issue Sep 27, 2023 · 0 comments
Labels
draft enhancement New feature or request

Comments

@bounoable
Copy link
Contributor

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
}
@bounoable bounoable added enhancement New feature or request draft labels Sep 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
draft enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant