Capability-First System Design

· SysMARA Team

Most backend development starts with routes. You need a way to create users, so you create POST /users. You need a way to list invoices, so you create GET /invoices. The route defines the entry point, and you build the handler, service, and data access code behind it.

This is backwards. The route is a transport detail. The thing that matters — the operation your system performs, with its inputs, outputs, constraints, and side effects — gets defined implicitly as the implementation behind the route. Two developers building the same feature might create different routes, different request shapes, and different response formats, but the underlying capability is the same.

What Is a Capability

A capability is a named operation on your system. It has:

The route — POST /api/subscriptions/:id/cancel — is derived from the capability, not the other way around. The capability compiler reads the capability spec and generates the route handler, including input validation, policy checks, invariant enforcement, and the hook where your business logic executes.

Why This Order Matters

When the capability is the primary artifact, several things become possible that are difficult or impossible in route-first design.

The system is understandable without reading code. A list of capabilities is a functional specification of what the system can do. You do not need to trace through route registrations, middleware chains, and controller methods to understand the system's surface area. The capabilities are declared, named, and described. An AI agent querying the system graph can enumerate every operation, its constraints, and its relationships in seconds.

Code generation is deterministic. Because the capability spec fully describes the operation's structure, the compiler can generate the route handler, validation logic, and test scaffold deterministically. Given the same spec, it produces the same output every time. There is no ambiguity about where the handler goes, how inputs are validated, or where policy checks run. The generated code is a function of the spec, and the spec is the source of truth.

Impact analysis is precise. When you ask "what would break if I change the subscription entity?" the system graph traverses from the entity to every capability that binds to it, every invariant that constrains it, and every flow that includes it. This traversal is possible because capabilities explicitly declare their entity bindings. In a route-first system, answering the same question requires static analysis of source code, which is slow, incomplete, and often wrong.

Capabilities Compose

Capabilities can reference other capabilities. A close_account capability might declare that it triggers cancel_subscription and export_user_data as sub-operations. These relationships are edges in the system graph, not implicit function calls buried in service code.

This composability means you can reason about operation dependencies at the architectural level. Before implementing close_account, you know exactly which other capabilities it depends on, which entities it will affect transitively, and which invariants from those sub-operations also apply.

The Route Becomes a Detail

In capability-first design, the HTTP route is generated, not designed. The compiler follows a consistent convention: the module name provides the URL prefix, the operation type determines the HTTP method, and the capability name provides the path segment. But the route could just as easily be a GraphQL resolver, a gRPC method, or a message queue consumer. The capability is transport-agnostic because it describes what the system does, not how it is invoked.

This does not mean you cannot customize routes. You can override the generated route configuration. But the default is generated from the spec, and most of the time the default is correct. The energy that would have gone into designing URL structures goes instead into describing capabilities accurately.

A Different Starting Point

Capability-first design changes where you start. Instead of "I need a POST endpoint," you start with "I need an operation called cancel_subscription that takes a subscription ID, checks the owner's authorization, enforces the unpaid invoice invariant, cancels the subscription, prorates the final invoice, and sends a confirmation email."

That description contains everything the compiler needs to generate the handler. It contains everything an AI agent needs to understand the operation before modifying it. And it contains everything the impact analysis engine needs to trace the operation's effects through the system.

The route will take care of itself.