Skip to main content

Forze

Domain-Driven Design and Hexagonal Architecture for backend services

PyPI Python License OpenSSF Scorecard codecov CodeFactor Socket Badge

Forze is a Python toolkit for building backend services with Domain-Driven Design and Hexagonal Architecture.

Your domain and application code imports nothing from a web framework, a database driver, or a transport: it talks to ports, and exactly one place in the service names an adapter. Forze supplies those ports, the runtime that resolves them, and the wiring that keeps the rule true as the service grows.

Quick start

uv add forze
import asyncio
from uuid import UUID

import structlog

from forze import (
    CreateDocumentCmd,
    Document,
    DocumentSpec,
    DocumentWriteTypes,
    ExecutionContext,
    ReadDocument,
    build_runtime,
    configure_logging,
)
from forze_mock import MockDepsModule

log = structlog.get_logger("hexagon")


# Domain — plain models. Nothing here knows about HTTP, SQL, or a broker.
class Order(Document):
    item: str


class CreateOrder(CreateDocumentCmd):
    item: str


class ReadOrder(ReadDocument):  # adds id, rev, created_at, last_update_at
    item: str


# The port — one spec names the aggregate and the types that cross its boundary.
ORDERS = DocumentSpec(
    name="orders",
    read=ReadOrder,
    write=DocumentWriteTypes(domain=Order, create_cmd=CreateOrder),
)


# Application — speaks to the port, never learns which storage answers it.
async def place_order(ctx: ExecutionContext, item: str) -> ReadOrder:
    return await ctx.document.command(ORDERS).create(CreateOrder(item=item))


async def read_order(ctx: ExecutionContext, order_id: UUID) -> ReadOrder:
    return await ctx.document.query(ORDERS).get(order_id)


async def main() -> None:
    # Wiring — the only place an adapter is named. A real backend replaces this module
    # (Postgres takes a client, its relation config and a lifecycle module — see
    # examples/recipes/crud_fastapi), and nothing above this line changes.
    runtime = build_runtime(MockDepsModule())
    async with runtime.scope():
        ctx = runtime.get_context()
        placed = await place_order(ctx, item="widget")
        order = await read_order(ctx, placed.id)
        log.info("stored and read back", id=str(order.id), item=order.item, rev=order.rev)


if __name__ == "__main__":
    # Configure logging only when run as a script, so imports and tests stay unaffected.
    configure_logging(level="info", logger_names=["hexagon", "forze"])
    asyncio.run(main())

That file is examples/hexagon/app.py, copied verbatim and run by CI on every commit — uv run python -m examples.hexagon.app. The in-memory adapter it wires needs no extras. A real backend replaces that one module — for Postgres, a client, the relation config for the aggregate and a lifecycle module, as in recipes/crud_fastapi — and the domain, the spec and the two application functions above it stay exactly as they are.

What Forze does not do

  • No ORM. Nothing here models tables, relations, or migrations. The Postgres integration is a driver-level client behind the same port Mongo, Firestore and the in-memory mock implement; your DDL stays yours.
  • No dependency-injection container. Deps are values you register in modules and resolve by key. Nothing scans your code and nothing autowires by type, so the wiring is something you can read — and check_wiring dry-runs every registered operation before you serve traffic.
  • No web framework, and none in the core. The core installs eleven libraries — none of them a server, a driver, or a client. import forze loads three modules; the runtime is pulled in only when you touch a name that needs it.
  • No code generation and no scaffolding. There is no forze new and no forze generate. The optional CLI has two commands — dst (deterministic simulation) and mock (serve an app on in-memory backends) — and neither writes code into your project.
  • No opinion about your directory layout. The docs suggest one; nothing enforces it. What the library enforces is the dependency direction, not your folder names.

When not to use it

  • A service that is CRUD over one table. There is no domain to isolate, and the ports will cost you more than they return.
  • A team that has not agreed on its domain vocabulary. Forze gives that agreement a place to live; it cannot substitute for having one.
  • You want batteries and one blessed way to do things. That is a full-stack framework, and Forze is deliberately not one — it assumes you assemble the service yourself.

Skills for AI agents using Forze

Forze ships an Agent Skill for applications that use Forze as a dependency, so an assistant working in your service repo wires ports, specs, and handlers the way the contracts actually expect rather than inventing a plausible shape.

npx skills add morzecrew/forze

One skill, forze-skills: a routing index plus 45 reference files it reads only when a task needs them. The index is keyed by task and names every reference a task needs, because these are procedures rather than independent rules — an aggregate declared without its backend config is an app that does not start.

I want to… It routes to
Bootstrap a new service architecture → spec naming → deps resolution → runtime lifecycle
Add a governed aggregate models → document spec → AggregateKit → backend config → mock tests
Write a custom handler execution context → handlers → query DSL
Expose it over HTTP FastAPI setup → generated routes → identity
Simulate under faults DST simulation → invariants → mock tests

The full index is in skills/README.md.

Writing an adapter

Every port is a Protocol under forze.application.contracts.<plane>. An adapter is a class that satisfies one — some planes ship a base that does the boilerplate, such as DocumentAdapter — plus a DepsModule that binds it to the key handlers resolve. That is the whole mechanism the shipped integrations use; being in this repo buys them nothing extra. The smallest one to read end to end is forze_vault; the pattern is documented in the wiring guide and the skill's deps-custom-module reference above.

You do not have to take the contract on faith where it is hardest to satisfy: forze_dst ships backend-agnostic batteries for transactional isolation (the classic anomalies, with the verdict each level owes) and for outbox → inbox delivery across a crash. Bring your backend and run them.

How far the seam is proven, plainly: document, storage and messaging ports each have several independent backends, which is what makes those seams credible. HTTP does not — FastAPI is the only web-framework adapter shipped, so on Litestar, Django-Ninja, Robyn or bare ASGI you are writing the first alternative implementation, and you should expect it to surface one or two places where framework-shaped assumptions leaked into the edge.

Examples

Every example under examples/ is a module you can run and is executed by a test, so none of them can quietly rot. Most need no Docker.

Example Shows
hexagon/ The slice above: domain, port, wiring, no transport
quickstart/ A CRUD HTTP API over the same document shape
recipes/order_fulfillment/ Saga → aggregate event → outbox → relay → inbox → downstream, in-process
recipes/analytics_duckdb/ A named, typed analytics query over a local data lake
recipes/mcp_server/ An aggregate served over MCP, every operation a tool

Twenty-odd more recipes — caching, idempotency, realtime, secrets rotation, durable workflows — are listed in examples/README.md.

Documentation

Full documentation: morzecrew.github.io/forze.

Stability

Forze is 0.x, and pre-1.0 here means what SemVer says it means: a minor release may change public contracts. What you get in exchange is a written record — every breaking change lands in CHANGELOG.md naming the contract that moved and the migration it needs, including SQL where a schema is involved.

There is no deprecation window yet: a contract that moves, moves in that release with its note, rather than shipping beside the old one for a cycle first. Read the changelog before upgrading a minor.

Python 3.13 and 3.14 are supported.

Contributing

Contributions, issues, and feature requests are welcome. See CONTRIBUTING.md for details.

Security

Please report security vulnerabilities privately as described in SECURITY.md.

License

Forze is licensed under the MIT License — see LICENSE for details.

Metadata

Release files for forze 0.13.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for forze 0.13.0
File Size Uploaded
forze-0.13.0.tar.gz 6.2 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for forze 0.13.0
File Interpreter ABI Platform
forze-0.13.0-py3-none-any.whl Python 3 none any Details

Total release size: 10.2 MB

Release files / forze-0.13.0.tar.gz

Download URL forze-0.13.0.tar.gz
Size 6.2 MB
Tags Source
SHA-256 checksum
How to use checksums
4abf896db591dfb9a4a8eb2d9dba9848277fbf9f031a437d7a2c8f78d3d34d13
BLAKE2b-256 checksum
How to use checksums
1c9ec90e59f70ab136004d916d8178831447f534216a7776d27d44a2e8aea9cd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release files / forze-0.13.0-py3-none-any.whl

Download URL forze-0.13.0-py3-none-any.whl
Size 4.0 MB
Tags Python 3
SHA-256 checksum
How to use checksums
cf4ec02ea59a61b882ab7b62019dfe6d256622c9d5bb497258f4a885db447482
BLAKE2b-256 checksum
How to use checksums
b38d711ff0d681140af2fc04a94b754abe9cb30f4a3cd90e10695adab4a8ac23
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.13.0 This release

2 release files

0.12.0

2 release files

0.10.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.13

2 release files

0.1.12

2 release files

0.1.11

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page