Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Overview

Explanation

Architecture overview

How the Cluster ERP server, its 28 modules, the EventBus, the web app, the desktop client, and the PostgreSQL database fit together.

This page shows the main parts of Cluster ERP and how they connect: one FastAPI server, 28 modules loaded into it, an in-process EventBus between the modules, two clients, and a PostgreSQL database. It is a map, not a manual. Each part links to the page that documents it.

In short

  • One FastAPI application is built by create_app and serves both the API and the web app (src/cluster_erp/server/app.py:1287, src/cluster_erp/server/app.py:1633-1644).
  • The module registry discovers the packages under cluster_erp.modules and loads each one into the application (src/cluster_erp/core/module_registry.py:19, src/cluster_erp/core/module_registry.py:58-72).
  • Modules talk to each other only through the EventBus and the contracts in core/contracts.py (src/cluster_erp/core/event_bus.py:43-49, src/cluster_erp/core/contracts.py:1-6).
  • Every request that touches business data gets one SQLAlchemy session on PostgreSQL through asyncpg (src/cluster_erp/server/dependencies.py:160-200, src/cluster_erp/core/database.py:95-102).
  • The web app and the desktop client draw their screens from module metadata served at GET /api/v1/_meta (src/cluster_erp/server/app.py:1518-1556).

How it works

The diagram shows the parts and the direction of the calls between them.

flowchart LR
 browser["Browser: webapp/"]
 desktop["Desktop client: PySide6"]
 subgraph server["Server process"]
 app["create_app: FastAPI"]
 mw["Middleware stack"]
 shared["Shared routes: server/routes"]
 mods["28 modules: ModuleBase"]
 bus["EventBus"]
 core["core: models, metadata, change_history"]
 end
 db[("PostgreSQL")]
 storage[("File storage")]
 browser -->|"HTTP /app and /api/v1"| app
 desktop -->|"HTTP /api/v1"| app
 app --> mw
 mw --> shared
 mw --> mods
 mods <-->|"Event and Query contracts"| bus
 shared --> core
 mods --> core
 core --> db
 shared --> storage

In prose, the diagram says the following:

  1. A browser loads the web app from /app and calls the API under /api/v1. The desktop client calls the same API with httpx (src/cluster_erp/desktop/core/api_client.py:7, src/cluster_erp/desktop/core/api_client.py:86-87).
  2. Each request passes the middleware stack before it reaches a route. See request pipeline and middleware.
  3. Routes belong either to a module (prefix /api/v1/<module>, src/cluster_erp/core/module_base.py:36-38) or to the shared routes in src/cluster_erp/server/routes/ that create_app mounts directly (src/cluster_erp/server/app.py:1432-1450). See shared server routes.
  4. Modules exchange data through the EventBus with frozen dataclass contracts. See event bus and contracts reference.
  5. Modules and shared routes use the platform core in src/cluster_erp/core/: the base model, metadata classes, change history, money helpers, search, and storage.
  6. Data lives in PostgreSQL. Uploaded files live in a storage backend, local disk or S3, created at start-up (src/cluster_erp/server/app.py:65-75). See attachments, notes, and storage.

Server and start-up

python -m cluster_erp.server.main runs uvicorn with the factory flag (src/cluster_erp/server/main.py:39-41). The lifespan context creates the database handle, the EventBus, the module registry, and the storage backend, then loads the modules and runs start-up passes (src/cluster_erp/server/app.py:58-196). See server startup and app factory.

Modules

Each module subclasses ModuleBase and implements initialize, register_routes, register_events, shutdown, and get_metadata (src/cluster_erp/core/module_base.py:61-74). See module lifecycle and the modules section.

Metadata and the clients

get_metadata returns a ModuleMeta object that describes screens, forms, lists, and actions (src/cluster_erp/core/metadata.py:719). The server serializes all modules' metadata into one document with an ETag (src/cluster_erp/server/app.py:1518-1556). See metadata model, the web app section, and the desktop section.

Database

Database wraps one async SQLAlchemy engine and one session factory (src/cluster_erp/core/database.py:53-105). The schema comes from Alembic revisions in migrations/ (alembic.ini:2). In multi-tenant mode each tenant has its own database (src/cluster_erp/config/settings.py:78-84). See database and sessions and multi-tenancy.

Why it is built this way

The project's written engineering rules forbid direct communication between modules and require the EventBus for it. It also forbids breaking the "app-level plus middleware" architecture and requires a metadata-driven web app. The code comment in src/cluster_erp/server/app.py:1573-1575 states why the server also serves the web app: the browser reaches the API on the same origin with no CORS.

Rules and boundaries

  • No module imports another module, except cluster_erp.modules.auth.guards. The test tests/test_architecture_boundaries.py:46-57 enforces this.
  • Every module has a module.py and at least one route decorator. The test tests/test_architecture_boundaries.py:60-72 enforces this.

Interaction with other parts

The platform section documents the core in depth. The identity and access section covers authentication, permissions, and tenancy. The API section lists the HTTP routes. The operations section covers deployment.

Limits and open points

  • The desktop client's default API address is a local development address (src/cluster_erp/desktop/core/api_client.py:82). How a production desktop install is pointed at a server is documented in the desktop section.

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13