Practical DDD(Domain Driven Design) & CQRS implementation on order bounded context
go 1.17
- go to directory /cmd/http/
- go run main.go
locate => http://localhost:8080/swagger/index.html
- docker build -t go-ddd -f docker/Dockerfile .
- docker run -it --rm -p 8080:8080 go-ddd
- locate
http://localhost:8080/swagger/index.html
- kubectl apply -f ./deploy/k8s/deployment.yaml
- kubectl apply -f ./deploy/k8s/service.yaml
- 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
- Health checks
- Graceful shutdown on interrupt signals
- Global http error handling with Problem Details rfc7807 (https://datatracker.ietf.org/doc/html/rfc7807)
- Prometheus metrics for echo
- Swagger docs (/swagger/index.html)
- Graceful config management by viper
- Mediator usage for command dispatching
- DDD structure
- Optimistic concurrency.
- Docker, K8s
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