Practical DDD(Domain Driven Design) & CQRS implementation on order bounded context
go 1.22.1
- run in terminal:
make run
- Locate to http://localhost:8080/swagger/index.html
- make helm-charts
- Health checks
- Graceful shutdown on interrupt signals
- Global http error handling with Problem Details rfc7807
- Prometheus metrics for echo
- Swagger docs (/swagger/index.html)
- Graceful config management by viper
- Mediator usage for command dispatching & dynamic behaviours
- DDD structure
- Optimistic concurrency control.
- Docker, K8s, Helm.
- Distroless docker image usage
- OTEL(open telemetry) & grafana/otel-lgtm integration
- Structured Logging with slog
- (TODO): Toxiproxy for resilency testing
- (TODO): Tilt & kind setup
- mediator https://github.com/eyazici90/go-mediator
- echo https://github.com/labstack/echo
- viper https://github.com/spf13/viper
- validator https://github.com/go-playground/validator
- swaggo https://github.com/swaggo/echo-swagger
- retry-go https://github.com/avast/retry-go
- testify https://github.com/stretchr/testify
- golangci https://github.com/golangci/golangci-lint
Check vertical-slice branch for Vertical-Slice (feature driven) packaging style structure.
Mediator with pipeline behaviours (order matters for pipeline behaviours)
sender, err := mediator.New(
// Behaviors
mediator.WithBehaviourFunc(behavior.Measure),
mediator.WithBehaviourFunc(behavior.Validate),
mediator.WithBehaviour(behavior.NewCancellator(timeout)),
// Handlers
mediator.WithHandler(command.CreateOrder{}, command.NewCreateOrderHandler(store)),
mediator.WithHandler(command.PayOrder{}, command.NewPayOrderHandler(store, store)),
mediator.WithHandler(command.ShipOrder{}, command.NewShipOrderHandler(store, store, ep)),
)
err = sender.Send(ctx, cmd)
Command & Command handler
type CreateOrderCommand struct {
Id string `validate:"required,min=10"`
}
type CreateOrderCommandHandler struct {
orderCreator OrderCreator
}
func (CreateOrderCommand) Key() int { return createCommandKey }
func NewCreateOrderCommandHandler(orderCreator OrderCreator) CreateOrderCommandHandler {
return CreateOrderHandler{orderCreator: orderCreator}
}
func (h CreateOrderCommandHandler) Handle(ctx context.Context, msg mediator.Message) error {
cmd, ok := msg.(CreateOrder)
if err := checkType(ok); err != nil {
return err
}
ordr, err := order.NewOrder(order.ID(cmd.ID), order.NewCustomerID(), order.NewProductID(), time.Now,
order.Submitted, aggregate.NewVersion())
if err != nil {
return errors.Wrap(err, "create order handle failed")
}
return h.orderCreator.Create(ctx, ordr)
}
Auto command validation
var validate *validator.Validate = validator.New()
type Validator struct{}
func NewValidator() *Validator { return &Validator{} }
func (v *Validator) Process(ctx context.Context, msg mediator.Message, next mediator.Next) error {
if err := validate.Struct(msg); err != nil {
return err
}
return next(ctx)
}
Context timeout
type Cancellator struct {
timeout int
}
func NewCancellator(timeout int) *Cancellator { return &Cancellator{timeout} }
func (c *Cancellator) Process(ctx context.Context, msg mediator.Message, next mediator.Next) error {
timeoutContext, cancel := context.WithTimeout(ctx, time.Duration(time.Duration(c.timeout)*time.Second))
defer cancel()
result := next(timeoutContext)
return result
}
TBD