Application Services
Application Services own business behavior.
Controllers, commands, jobs, events, and schedules should call services rather than each reimplementing workflows at their own runtime boundary.
Service Shape
package reports
type Service struct {
repo *Repository
queue Queue
cache Cache
events Events
}
func NewService(repo *Repository, queue Queue, cache Cache, events Events) *Service {
return &Service{
repo: repo,
queue: queue,
cache: cache,
events: events,
}
}Required dependencies stay required. Optional dependencies should be modeled explicitly.
One Service, Multiple Callers
The JSON API Route is the smallest complete example: users.Service.Find is constructed by Wire, called by a controller, unit-tested without HTTP, and observed through the generated route. A command, job handler, subscriber, or schedule can call the same typed service method without importing web or copying the workflow.
Verify a service-first change before running the HTTP runtime:
forj build
go test ./...Expected result: generated wiring succeeds and service tests pass without starting a listener, worker, or scheduler. Use the scenario's forj api and curl check only when confirming the HTTP boundary.
Inputs and Outputs
Use typed inputs for service operations. This illustrative excerpt omits the workflow inside Create:
type CreateReportInput struct {
Name string
OwnerID string
Immediate bool
}
func (s *Service) Create(ctx context.Context, input CreateReportInput) (Report, error) {
// ...
}This keeps service APIs independent from HTTP request structs, CLI flag structs, and queue payload structs.
Runtime Boundaries
Multiple entry points may call the same service:
- HTTP controller
- CLI command
- queue job handler
- event subscriber
- scheduler entry
The service should not need to know whether HTTP, a command, a job, or a schedule called it unless that distinction is part of the business behavior.
Infrastructure Access
Services should receive infrastructure through constructor injection.
Prefer generated accessors and interfaces at the consumer boundary:
- cache accessors for derived data
- storage disks for files and blobs
- queues for background work
- event buses for fan-out
- repositories for persistence
- metrics wrappers for application metrics
Avoid importing backend driver packages into services.
Transactions and Consistency
Keep consistency decisions close to the service method that owns the workflow.
If a workflow writes to the database, dispatches a job, updates cache, and publishes an event, document the ordering and failure behavior in the service or feature docs. Do not hide consistency policy in controllers or middleware.
Next Steps
- Controllers explains HTTP callers.
- Commands explains CLI callers.
- Events versus Queues explains async callers.