Skip to content

Responsibilities

Anvil has a narrow job: compile project shape into runtime wiring.

That boundary is what keeps the tool useful. Anvil removes generated glue; it does not take over your domain model, deployment model, or framework choices. Domain decisions stay in the application. Framework decisions stay in drivers. Integration decisions stay in plugins.

Anvil handles protocol-neutral wiring:

  • Scanning configured Go packages
  • Detecting SDK marker types with go/types
  • Validating controllers, groups, routes, handlers, middleware, request models, validation tags, dependency providers, and protocol markers
  • Building deterministic compiler plans
  • Detecting dependency graph cycles before the application starts
  • Emitting generated Go wiring
  • Emitting the anvil.manifest.v1 compiler manifest
  • Generating OpenAPI and testbed artifacts from the manifest
  • Running built-in compiler rules
  • Running configured Starlark rules over the manifest
  • Exposing app lifecycle hooks
  • Exposing a global event bus
  • Exposing a global error pipeline
  • Running the HTTP-family edge listener when HTTP, GraphQL, or gRPC drivers are registered
  • Classifying HTTP-family requests before dispatching them to selected drivers

For HTTP, generated code handles route registration, request binding, validation calls, middleware chain order, and WebSocket route registration.

Your application provides the business shape:

  • Domain entities and value objects
  • Command/query handlers
  • Service interfaces
  • Stores, repositories, transactions, and outbox logic
  • DTOs and request structs
  • Domain errors and error classification rules
  • Response bodies, response status codes, and response headers
  • Logging fields and observability policy
  • Concrete dependency implementations
  • Deployment configuration
  • Which drivers and plugins are imported

Anvil works with n-tier, hexagonal, CQRS, clean architecture, or a smaller package layout. It wires the architecture you choose instead of asking you to reshape the application around Anvil.

Drivers adapt Anvil contracts to concrete protocols and frameworks. A driver is where framework-specific configuration belongs.

Examples:

  • github.com/TDB-Group/anvil-http/std provides net/http server behavior
  • github.com/TDB-Group/anvil-http/fiber provides Fiber config and Fiber context adaptation
  • github.com/TDB-Group/anvil-http/gin provides Gin config and Gin context adaptation
  • github.com/TDB-Group/anvil-queue/redis provides Redis connection and broker behavior

Anvil does not add wrapper options for framework-specific settings such as Fiber body limits, Gin engine options, or broker client configuration. Driver constructors accept the native configuration object where that fits the driver.

Plugins are for cross-cutting infrastructure:

  • Registering providers such as S3 clients or telemetry exporters
  • Adding boot and shutdown hooks
  • Observing mapped errors for Sentry, Datadog, Honeycomb, audit logs, or metrics
  • Subscribing to the event bus
  • Publishing application or integration events
  • Extending the error pipeline

Event bus subscribers are also plugin or application code. The bus calls subscribers synchronously and does not recover panics. Use it for deterministic in-process fanout, not for hiding slow work.

Anvil inspects types, struct fields, and tags during compilation. Boot-time wiring can use reflection to construct the dependency graph and inject components, but that work finishes before transports accept traffic.

At request time, execution follows the generated route function:

  1. The Anvil edge listener accepts HTTP-family requests.
  2. The edge classifies the request as gRPC, GraphQL, or HTTP.
  3. The selected driver creates the protocol context, such as sdk.Ctx, sdk.GraphQLCtx, or sdk.GRPCCtx.
  4. Generated code binds and validates request data when that protocol supports generated binding.
  5. Generated code executes middleware in deterministic order.
  6. The handler, resolver, RPC method, or job runs.
  7. The selected driver encodes the returned body or maps the returned error.

If a handler calls ctx.Native(), that handler has intentionally opted into the current driver. This is useful for framework-specific behavior, but it reduces driver portability.