Skip to main content

Build a service

1. Define ports and use case

type WidgetRepository interface {
Get(context.Context, string) (Widget, error)
Create(context.Context, Widget) (Widget, error)
}

type Service struct {
widgets WidgetRepository
}

func New(widgets WidgetRepository) *Service {
return &Service{widgets: widgets}
}

Keep this package free of transport and driver imports.

2. Implement typed handlers

type Handler struct {
service *service.Service
}

func (h *Handler) Get(
ctx context.Context,
req GetWidgetRequest,
) (WidgetResponse, error) {
widget, err := h.service.Get(ctx, req.Actor.Subject, req.ID)
if err != nil {
return WidgetResponse{}, fmt.Errorf("get widget: %w", err)
}
return toResponse(widget), nil
}

GetWidgetRequest embeds httpx.Actor and binds its ID from the path. The handler returns values and errors; platform/http renders them.

3. Declare routes

func Routes(h *Handler) httpx.RouteSet {
return httpx.RouteSet{
Prefix: "/widgets",
Routes: []httpx.Route{
httpx.GET(
"/{id}",
httpx.Handle(h.Get),
httpx.OpID("getWidget"),
),
},
}
}

4. Wire the adapter graph

func wire(
_ context.Context,
app *bootstrap.App[config.Config],
) (http.Handler, error) {
repo := postgres.NewWidgetRepository(app.DB)
handler := api.New(service.New(repo))

return httpx.NewRouter(httpx.RouterSpec{
Base: "/v1",
URNs: api.URNs,
Health: app.Health,
Metrics: metrics.Handler(),
Logger: app.Log,
Auth: httpx.AuthSpec{
Authenticate: middleware.Resolve(
middleware.AuthConfig{
HMACSecret: app.Cfg.InternalAuth.SharedSecret,
JWT: app.Cfg.JWT,
},
),
},
}, api.Routes(handler)), nil
}

5. Declare process dependencies

Use bootstrap.Spec as shown in Bootstrap and configuration. Register event consumers with Background, mandatory workflow workers with Go, vendor closers with Closer, and non-SQL dependency checks with Probe.

6. Verify

  • unit-test Service with a fake repository;
  • call Handler.Get directly with a verified Subject;
  • test repository and migrations against disposable PostgreSQL;
  • assert route/OpenAPI drift and auth flags;
  • run go test -race ./...;
  • exercise the route through the local gateway.