A production-ready, event-sourced, multi-tenant ERP platform built in Rust.
ForgeERP is an open-source micro-ERP designed as a modular platform for building enterprise resource planning features (inventory, sales, purchasing, accounting, and more) with clean domain boundaries, strong architectural guarantees, and a focus on developer experience. Built on event sourcing and CQRS principles, it provides a complete audit trail, deterministic business logic, and the flexibility to evolve your domain model over time without data migration headaches.
- Why Event Sourcing?
- Why Multi-Tenancy?
- Why AI is Non-Invasive
- Features
- Quickstart
- Architecture
- Benchmarks
- Roadmap
- Contributing
- License
Event sourcing isn't just a storage pattern—it's a domain modeling philosophy that makes your business logic explicit, testable, and auditable.
Complete audit trail: Every state change is an immutable event. Want to know why stock was adjusted at 3:47 PM on Tuesday? The event log tells you exactly what happened, who did it, and in what context. This isn't just logging—it's your source of truth.
Deterministic business logic: Aggregates are pure functions. Given the same command and event history, they always produce the same result. This makes testing trivial and bugs reproducible. Your business rules are code, not database constraints.
Time travel and debugging: Replay events up to any point in time. Rebuild read models from scratch. Test scenarios against historical data. Event sourcing gives you superpowers that CRUD simply cannot match.
Schema evolution without migrations: Need to change your domain model? Add new event types. Old events remain compatible. New projections can handle both old and new event versions. No ALTER TABLE required.
Performance characteristics: Event sourcing trades write complexity for read flexibility. Writes are append-only (fast). Reads can be optimized via projections (fast). The event store scales linearly. And you can always rebuild projections if you need different query patterns.
ForgeERP's event sourcing implementation is production-ready: optimistic concurrency, tenant isolation, idempotent projections, and graceful error handling are all built in.
Multi-tenancy isn't just about cost efficiency—it's about data isolation and security by design.
Defense in depth: Tenant isolation is enforced at multiple layers: JWT tokens extract tenant context, command dispatchers validate tenant IDs, event stores key streams by (tenant_id, aggregate_id), and projections filter by tenant. One layer failing doesn't compromise security.
Resource efficiency: Shared infrastructure (compute, storage, network) means lower operational costs. But this isn't "shared database with a WHERE clause"—each tenant's data is logically separated at the event stream level.
Compliance ready: GDPR, SOC 2, and similar frameworks require tenant data isolation. ForgeERP's architecture makes this a structural guarantee, not an afterthought.
Operational simplicity: One deployment serves all tenants. Scaling, monitoring, and maintenance happen once. But each tenant's data remains isolated and secure.
ForgeERP's multi-tenancy is enforced at the event level. Events carry tenant_id in their envelope. Streams are keyed by tenant. Cross-tenant access is architecturally impossible.
AI features should enhance your system, not complicate it.
Separate, optional boundary: ForgeERP's AI subsystem (forgeerp-ai) is a boundary crate with no dependencies on domain aggregates. AI jobs consume read model snapshots and emit insights—they don't mutate domain state or emit domain events.
Failures don't affect core workflows: If anomaly detection fails, your inventory commands still execute. If insights aren't generated, your business logic is unaffected. AI is a read-only analytics layer, not a critical path dependency.
Deterministic and testable: AI jobs are pure functions. Given the same snapshot, they produce the same result. No randomness, no I/O during execution. This makes AI logic testable and debuggable.
Tenant-isolated execution: AI runners are tenant-scoped. Each tenant's insights are computed independently, with no cross-tenant data leakage.
ForgeERP's first AI feature is inventory anomaly detection: it analyzes stock movement patterns using rolling z-scores, flagging unusual adjustments. This runs in the background, triggered by projection updates, and results are exposed via API endpoints. The core inventory system knows nothing about it.
- ✅ Event-sourced aggregates: Deterministic business logic with complete audit trails
- ✅ CQRS read models: Optimized projections for fast queries
- ✅ Multi-tenant architecture: Tenant isolation enforced at the event level
- ✅ Optimistic concurrency: Version-based conflict detection and resolution
- ✅ Command dispatcher: Reusable orchestration engine for event-sourced workflows
- ✅ Projection system: Idempotent, rebuildable read model builders
- ✅ Event bus: Pub/sub distribution with at-least-once delivery guarantees
- ✅ JWT authentication: Token-based auth with tenant-scoped claims
- ✅ RBAC authorization: Role-based permissions with command-level checks
- ✅ Structured logging: JSON logs with
tracingandRUST_LOGfiltering - ✅ Type-safe identifiers: UUIDv7-backed
TenantId,AggregateId,UserId
- ✅ InventoryItem aggregate: Event-sourced inventory management
- ✅ Stock adjustments: Add or remove stock with validation
- ✅ Business invariants: Stock cannot go negative
- ✅ Read model projection: Fast stock queries via
InventoryStockProjection - ✅ REST API:
POST /inventory/items,POST /inventory/items/{id}/adjust,GET /inventory/items/{id}
- ✅ Product aggregate: Event-sourced product catalog management
- ✅ Product lifecycle: Draft → Active → Archived status transitions
- ✅ SKU management: SKU field with validation (uniqueness per tenant requires infrastructure support)
- ✅ Optional pricing metadata: Base price and currency (no accounting integration yet)
- ✅ Business invariants: Archived products cannot be activated or sold
- ✅ Commands: CreateProduct, ActivateProduct, ArchiveProduct
- ✅ Events: ProductCreated, ProductActivated, ProductArchived
- ✅ Inventory anomaly detection: Background job that flags unusual stock movements
- ✅ Z-score analysis: Rolling window statistics for pattern detection
- ✅ Insight storage: Results stored separately from domain events
- ✅ API exposure:
GET /inventory/anomalies,GET /inventory/{id}/insights - ✅ Real-time updates: SSE stream for insight notifications
- ✅ In-memory event store: For development and testing
- 🚧 Postgres event store schema: Designed and documented (migrations in
docker/migrations/) - ✅ In-memory event bus: Local pub/sub
- ✅ Redis event bus (optional): Distributed pub/sub via Redis
- ✅ Projection workers: Background workers with graceful shutdown
- ✅ Tenant-scoped workers: Per-tenant isolation for background processing
- ✅ Local cache: In-memory read model cache
- ✅ Offline mode: Connectivity detection and read-only offline behavior
- ✅ Sync client: Explicit sync/reconnect (Tauri feature)
- ✅ Cargo workspace: Modular crate structure
- ✅ Comprehensive Rustdoc: Architecture-focused documentation
- ✅ Unit tests: Domain logic tests with no mocks
- ✅ Property-based tests: Invariant validation via
proptest - ✅ Integration tests: Full event-sourced pipeline tests
- ✅ Black-box API tests: End-to-end HTTP contract validation
- ✅ Benchmarks: Performance characterization with
criterion
- Rust (edition 2024 toolchain)
- Docker (optional, for Docker Compose setup)
# Clone the repository
git clone https://github.com/Ahmadnoorkhan1/forgeerp.git
cd forgeerp
# Run the API
cargo run -p forgeerp-apiThe API starts on http://localhost:8080. Check health:
curl http://localhost:8080/health# Create environment file
cp .env.example .env
# Build and start services
docker compose up --buildThis starts:
- API on
localhost:8080 - Postgres on
localhost:5432(with named volume) - Redis on
localhost:6379(with named volume)
Database Migrations: Run migrations automatically:
# Migrations run automatically when using docker compose up
# Or run manually:
docker compose run --rm migrateSee MIGRATIONS.md for complete migration documentation.
# Get a JWT token (see crates/api/README.md for auth setup)
TOKEN="your-jwt-token"
# Create an inventory item
curl -X POST http://localhost:8080/inventory/items \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Widget A", "initial_quantity": 100}'
# Adjust stock
curl -X POST http://localhost:8080/inventory/items/{item_id}/adjust \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"delta": -25}'
# Query current stock
curl http://localhost:8080/inventory/items/{item_id} \
-H "Authorization: Bearer $TOKEN"See crates/api/README.md for full API documentation and authentication setup.
ForgeERP follows strict architectural boundaries with event sourcing and CQRS at its core.
graph TB
subgraph "API Layer"
API[HTTP API]
Auth[Auth Middleware]
end
subgraph "Application Layer"
Dispatcher[CommandDispatcher]
end
subgraph "Domain Layer"
Aggregate[Aggregates]
Events[Domain Events]
end
subgraph "Infrastructure Layer"
EventStore[Event Store]
EventBus[Event Bus]
Projections[Projections]
ReadModel[Read Model]
end
API --> Auth
Auth --> Dispatcher
Dispatcher --> Aggregate
Aggregate --> Events
Dispatcher --> EventStore
Dispatcher --> EventBus
EventBus --> Projections
Projections --> ReadModel
API --> ReadModel
sequenceDiagram
participant Client
participant Dispatcher
participant EventStore
participant Aggregate
participant EventBus
Client->>Dispatcher: Command
Dispatcher->>EventStore: Load events
EventStore-->>Dispatcher: Event history
Dispatcher->>Aggregate: Rehydrate (apply events)
Dispatcher->>Aggregate: Handle command
Aggregate-->>Dispatcher: New events
Dispatcher->>EventStore: Persist events
EventStore-->>Dispatcher: Committed events
Dispatcher->>EventBus: Publish events
Dispatcher-->>Client: Success
These are non-negotiable boundaries enforced across the codebase:
corehas zero infrastructure dependencies: Domain logic is pure and testableeventsdefines mechanics, not business: Event sourcing primitives are genericinfradepends oncore+events, never the reverse: Dependencies point inward- API only orchestrates: HTTP layer maps requests to commands, nothing more
- Multi-tenancy is enforced at the event level: Events carry
tenant_idin their envelope - Events are immutable, versioned, append-only: This is the foundation
See docs/ARCHITECTURE.md for detailed diagrams including projection rebuild flow, AI insight flow, multi-tenancy isolation, Postgres event store schema, and the Postgres command → DB commit flow.
ForgeERP is a Cargo workspace with focused crates:
crates/core: Domain foundation (IDs, errors, aggregate semantics)crates/events: Event sourcing + CQRS mechanicscrates/auth: Authentication + authorization (JWT, RBAC)crates/infra: Infrastructure adapters (event store, event bus, projections)crates/inventory: First ERP module (Inventory domain)crates/products: Products/Catalog module (Product domain)crates/ai: Optional AI/ML subsystem (anomaly detection)crates/api: HTTP gateway (Axum server)crates/desktop: Optional desktop client foundation (Tauri)crates/observability: Shared tracing/logging setup
Each crate has its own README. Start with crates/core/README.md to understand the domain foundation.
ForgeERP is built for performance. Here's what the numbers say:
Event-sourced command execution: ~8.6 µs per command (create + adjust stock)
- Includes: event loading, aggregate rehydration, command handling, event persistence, bus publication
- This is the full pipeline, not just a database write
Naive CRUD simulation: ~55 ns per operation
- Direct key-value update with no history, no validation, no event distribution
- This is an unfair comparison (CRUD has no features), but it shows event sourcing overhead is minimal
Verdict: Event sourcing adds ~8.5 µs overhead for complete auditability, deterministic logic, and multi-tenant isolation. For most ERP workloads, this is negligible compared to network latency and business logic execution time.
Batch append performance (in-memory store):
- Single event: ~100k events/second
- Batch of 100 events: ~1M events/second
- Batch of 1000 events: ~10M events/second
Event sourcing scales with batching. Real-world workloads batch events naturally (multiple events per command).
Rebuilding inventory stock projection from 10,000 events: ~5ms
Projections are fast to rebuild. If you change your read model schema or fix a projection bug, rebuilding from scratch is trivial. This is a key advantage of event sourcing: read models are disposable.
Benchmarks use criterion with statistical rigor. See crates/infra/benches/event_sourcing_benchmarks.rs for full implementation. Results are from in-memory implementations; production stores (Postgres, Redis) will have different characteristics but similar relative performance.
Honest assessment: Event sourcing isn't free, but it's fast enough for ERP workloads. The benefits (auditability, testability, schema evolution) far outweigh the microsecond-level overhead for most use cases.
ForgeERP is actively developed with a clear vision. Here's what's planned:
- Persistent event store: Postgres-backed event store implementation (schema designed ✓, Rust implementation in progress)
- Durable event bus: Redis Streams or Kafka adapter for production event distribution
- More ERP modules: Sales orders, purchase orders, basic accounting
- Database migrations: SQL migration system for read model schemas
- Production observability: Metrics (Prometheus), distributed tracing (OpenTelemetry)
- API documentation: OpenAPI/Swagger specs for all endpoints
- Advanced authorization: Policy-based permissions, custom roles
- Event versioning strategy: Schema migration tooling for event evolution
- Read model persistence: Postgres-backed read model stores
- Background job system: Scheduled jobs, retries, dead-letter queues
- Multi-region support: Event replication, eventual consistency patterns
- Desktop client: Full Tauri app with offline sync
- GraphQL API: Alternative query interface for complex reads
- Event sourcing patterns library: Reusable patterns (sagas, process managers)
- Multi-currency support: Financial aggregates with currency handling
- Reporting engine: Built-in analytics and reporting
- Plugin system: Extensible architecture for custom modules
- Full ERP suite out of the box: ForgeERP is a platform, not a product. We provide the building blocks; you build your domain.
- ORM or database abstractions: We use event sourcing. If you want ActiveRecord, use Rails.
- Admin UI: Focus is on the API and domain model. UIs are out of scope (for now).
- Hosted service: This is self-hosted software. Cloud offerings may come later, but it's not the priority.
Honest assessment: ForgeERP is production-ready for inventory management today. Other modules need work. The architecture is solid, the patterns are proven, but the feature set is intentionally minimal. We're building the platform first, features second.
Contributions are welcome. We value:
- Clear boundaries: Respect crate dependencies (core → events → infra → api)
- Event sourcing discipline: Aggregates are pure, events are immutable, projections are idempotent
- Test coverage: Domain logic should have unit tests, integration tests cover the pipeline
- Documentation: Rustdoc comments explain "why", not just "what"
- Read the architecture docs: Start with
docs/ARCHITECTURE.md - Pick an issue: Check open issues or start a discussion
- Keep PRs focused: One feature, one PR
- Run tests:
cargo test --workspace - Check documentation:
cargo doc --workspace --open
# Run all tests
cargo test --workspace
# Run benchmarks
cargo bench -p forgeerp-infra
# Check documentation
cargo doc --workspace --no-deps
# Run linter
cargo clippy --workspace -- -D warningsSee individual crate READMEs for module-specific guidance.
MIT License. See LICENSE for details.
Built with Rust. Powered by event sourcing. Designed for scale.