threedotslab/templates/command.md

2.6 KiB

Command Handler Scaffold Template

Generate a single command handler file following the 4-component pattern.

Placeholders

  • {{Name}} — PascalCase command name (e.g., ScheduleTraining)
  • {{name}} — camelCase (e.g., scheduleTraining)
  • {{module}} — Go module path from go.mod
  • {{entity}} — Domain entity name, lowercase (e.g., hour)
  • {{Entity}} — Domain entity name, PascalCase (e.g., Hour)

File: app/command/{{name_snake}}.go

package command

import (
	"context"

	"github.com/sirupsen/logrus"

	"{{module}}/domain/{{entity}}"
	"{{module_common}}/decorator"
)

// 1. Command struct — imperative verb + noun, plain data
type {{Name}} struct {
	// TODO: Add command fields
	// Example:
	// UUID string
	// Hour time.Time
}

// 2. Exported handler type alias
type {{Name}}Handler decorator.CommandHandler[{{Name}}]

// 3. Unexported concrete handler struct
type {{name}}Handler struct {
	{{entity}}Repo {{entity}}.Repository
}

// 4. Constructor with nil-checks + decorator wrapping
func New{{Name}}Handler(
	{{entity}}Repo {{entity}}.Repository,
	logger *logrus.Entry,
	metricsClient decorator.MetricsClient,
) {{Name}}Handler {
	if {{entity}}Repo == nil {
		panic("nil {{entity}}Repo")
	}
	if logger == nil {
		panic("nil logger")
	}
	if metricsClient == nil {
		panic("nil metricsClient")
	}

	return decorator.ApplyCommandDecorators[{{Name}}](
		{{name}}Handler{{"{"}}{{entity}}Repo: {{entity}}Repo},
		logger,
		metricsClient,
	)
}

// Handle — orchestrates domain logic, does NOT contain business rules
func (h {{name}}Handler) Handle(ctx context.Context, cmd {{Name}}) error {
	// TODO: Implement command handling
	//
	// Typical patterns:
	//
	// Pattern A — Update via callback:
	//   return h.{{entity}}Repo.Update{{Entity}}(ctx, cmd.UUID, func(e *{{entity}}.{{Entity}}) (*{{entity}}.{{Entity}}, error) {
	//       if err := e.SomeDomainAction(); err != nil {
	//           return nil, err
	//       }
	//       return e, nil
	//   })
	//
	// Pattern B — Create new entity:
	//   entity, err := {{entity}}.New{{Entity}}(cmd.UUID, ...)
	//   if err != nil {
	//       return err
	//   }
	//   return h.{{entity}}Repo.Save(ctx, entity)

	return nil
}

Update app/app.go

After creating the handler, add it to the Commands struct:

type Commands struct {
	// ... existing handlers ...
	{{Name}} command.{{Name}}Handler
}

Update service/application.go

Wire the handler in the composition root:

Commands: app.Commands{
	// ... existing handlers ...
	{{Name}}: command.New{{Name}}Handler(
		{{entity}}Repository,
		logger,
		metricsClient,
	),
},