Skip to content

How Tenchi works

Tenchi has one architecture: typed contracts at the boundary, plain use cases at the center, and explicit dependency wiring around them. Five pieces are enough to serve an HTTP API. Optional capabilities reuse this structure as you add other callers, operational checks, or AI behavior.

Contract

A frozen declaration of one HTTP operation: method, path, validated inputs, successful responses, and the application errors callers may observe. A contract contains no handler logic.

Route

A binding between a contract and a use case. route() inspects the use-case signature immediately, so a mismatched parameter name or annotation fails while the application is composed rather than on the first request.

Use case

A plain async function that implements one application action. Its parameters are named boundary values such as request, params, query, headers, and context. It never receives a Starlette request, so you can call it directly from tests and scripts. Jobs, tasks, tools, and execute() supply request and context; a function that also needs params, query, or headers requires an input adapter for those callers.

Port and adapter

A port is an interface the application needs, declared with typing.Protocol. The feature owns the port; infrastructure supplies an adapter that implements it. A SQL repository and a memory test double are both adapters for the same port, so the application stays independent of any database driver, HTTP SDK, cache, or queue.

Context

A frozen dataclass carrying the dependencies and verified identity available to a use case. The application constructs it explicitly at the server composition root. Tenchi has no service locator or dependency-injection container.

Boundary flow

HTTP request
  -> route match
  -> request id and context factory / request scope
  -> boundary hooks
  -> size, media-type, and Pydantic input validation
  -> plain use case
  -> response presenter and header projector
  -> Pydantic response validation
  -> HTTP response

The typed client runs the complementary checks: it serializes inputs from the same contract and validates the status, media type, body, headers, and declared application errors returned by a server.

Optional extensions

None of the following is required to define and serve an HTTP API. Each one gives an existing use case another entrypoint or another rule, and each has its own guide.

The key tradeoff

Tenchi asks for more structure than a one-file microframework example. In return, an endpoint's validation, client behavior, OpenAPI, compatibility policy, and application boundary do not become separate models as the service grows.