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 Agent Skills 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                # all skills
npx skills add morzecrew/forze@forze-wiring   # just one

A few of them:

Skill Covers
forze-wiring Runtime, DepsRegistry, lifecycle, governed aggregates, pipeline stages
forze-framework-usage ExecutionContext, ports, transactions, identity context, the query DSL
forze-domain-aggregates Document aggregates, mixins, validators, logical specs, composition DTOs
forze-deps-consumption Plain vs routed deps, route=spec.name, built-in *DepsModule, merge debugging
forze-custom-deps Custom DepKey and DepsModule for private integrations

All 21, with descriptions, are 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 forze-custom-deps skill 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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

forze-0.6.0.tar.gz (5.0 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

forze-0.6.0-py3-none-any.whl (3.4 MB view details)

Uploaded Python 3

File details

Details for the file forze-0.6.0.tar.gz.

File metadata

  • Download URL: forze-0.6.0.tar.gz
  • Upload date:
  • Size: 5.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for forze-0.6.0.tar.gz
Algorithm Hash digest
SHA256 3aa4deef4b3f83fe1a3fb57793750a8a4214c2701d5d20cf3b6e63998df32a8c
MD5 24ee19960d168def56feb70fa019f116
BLAKE2b-256 decf0e4c35619aa94a62ef23605445f4200ccad43854b0bed9c145ecc579039a

See more details on using hashes here.

Provenance

The following attestation bundles were made for forze-0.6.0.tar.gz:

Publisher: release.yaml on morzecrew/forze

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file forze-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: forze-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 3.4 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for forze-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a9c3f9faba773b36d0ab5d43c7ae0e92d50018e361f072521646d75c5f7ebc8d
MD5 4c0b2be4404e3e4dd336cf7e6d4d5a19
BLAKE2b-256 8a7bdd23f6067c710bbae5aa847a48b95d7b04744853f79ae3fbe5b7c37a67aa

See more details on using hashes here.

Provenance

The following attestation bundles were made for forze-0.6.0-py3-none-any.whl:

Publisher: release.yaml on morzecrew/forze

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.7.0

2 files

This release

0.6.0 This release

2 files

0.5.1

2 files

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.14

2 files

0.1.13

2 files

0.1.12

2 files

0.1.11

2 files

0.1.10

2 files

0.1.9

2 files

0.1.8

2 files

0.1.7

2 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