Core Framework

Services and Context

Initialize shared dependencies once and expose them through typed context helpers.

By Guillermo Alvarez

The composition root

app/init/context.go initializes shared dependencies once:

func Context(ctx context.Context) context.Context {
    posts, err := postservice.New()
    if err != nil {
        panic(err)
    }

    ctx = timeservice.WithContext(ctx, timeservice.New())
    ctx = postservice.WithContext(ctx, posts)
    return ctx
}

Views and public files are passed to lazyapp.New from app/init/app.go. Application context should focus on application services.

Typed context helpers

Each service owns an unexported key type and exported helpers:

type contextKey struct{}

func WithContext(
    ctx context.Context,
    service *Service,
) context.Context {
    return context.WithValue(ctx, contextKey{}, service)
}

func FromContext(ctx context.Context) (*Service, bool) {
    service, ok := ctx.Value(contextKey{}).(*Service)
    return service, ok
}

An unexported key type prevents collisions with other packages.

Resolve dependencies in constructors

Controller constructors validate their requirements:

posts, ok := postservice.FromContext(ctx)
if !ok {
    return nil, fmt.Errorf(
        "posts service is missing from application context",
    )
}

This keeps concrete controller constructors uniform while still making missing dependencies fail explicitly.

Shared and request-local state

Application services may be shared when they are safe for concurrent use. Controller instances must not be shared.

The framework derives a request-specific context containing the http.ResponseWriter before constructing a controller. Render state stays on that new controller instance.

Run race tests whenever shared service behavior changes:

go test -race ./...

What belongs in context

In GoLazy, application context is dependency wiring. Store initialized services and framework infrastructure there.

Do not use it as a general-purpose parameter bag for:

  • Optional action arguments.
  • View data.
  • Values that can be passed directly within a service.
  • Mutable request state owned by a controller.

Keep helper APIs typed and package-owned so dependency access remains searchable and testable.

Service design

Services should expose application operations rather than HTTP concepts. A posts service can provide List and Get; the controller decides how those results become status codes and HTML.

This separation allows focused unit tests without constructing requests or templates.