/go-ddd

Practical implementation of Domain Driven Design in go

Primary LanguageGoMIT LicenseMIT

go-ddd

Practical DDD(Domain Driven Design) & CQRS implementation on order bounded context

Prerequisites

go 1.22.1

Warming - Up

Helm

  • make helm-charts

Futures

  • 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

Libraries

Note:

Check vertical-slice branch for Vertical-Slice (feature driven) packaging style structure.

Command dispatcher

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)
    }

Pipeline Behaviours

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

    }

IAC

TBD