Core Framework
Services and Context
Initialize shared dependencies once and expose them through typed context helpers.
The composition root
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 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.
When lazyapp.App.ListenAndServe starts the server, it installs the
application context as the server base context. Inside the app, request
processing uses r.Context() so services and framework dependencies come from
one context path.
Routes construct controller prototypes at startup. For each request, GoLazy
copies the prototype into a pooled instance, binds the current request and
response writer, then resets request state before returning the instance to the
pool. Do not store request-specific data in constructor fields. Put request
setup in actions or BeforeAction.
If you build your own http.Server, set BaseContext to return the
application context:
server := &http.Server{
Addr: ":3000",
Handler: app,
BaseContext: func(_ net.Listener) context.Context {
return app.Context
},
}
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.