Pharos RS is a lightweight Rust framework for building domain-driven, CQRS-friendly, event-driven applications.
The public API is intentionally small: a domain core, an application-contract crate, and a convenience facade that reexports the stable surface so users can get started quickly without learning every crate at once.
- Domain modeling primitives:
Entity,AggregateRoot,ValueObject,DomainEvent - Validated value objects via
value_object!; strongly typed UUID v7 IDs viaid_type! Money/Currencyini128minor units — checked arithmetic, lossless allocation, crypto magnitudes (wei) included- Command/query handlers with validation + tracing applied by the
dispatchseam - Internal-only commands (
#[command(internal)]): the HTTP entry points refuse to route them, keeping saga-issued payouts/refunds off the wire while in-process dispatch still works - Repository abstraction with optimistic concurrency control
- Atomic aggregate save + outbox in one transaction (
pharos_app::{TransactionalStore, TransactionalRepository, save_and_enqueue_in}) — backend-agnostic (the trait and the composing function never name a concrete connection type);pharos-memory'sInMemoryUnitOfWorkis the shippedTransactionalStoreimplementation for tests, production needs your own (see Writing an adapter) - In-process domain event bus with configurable error policy, retry, and dead-letter decorators
- Integration event envelope with typed correlation/causation, tenant, trace and schema metadata
- Schema evolution through JSON upcasters (
VersionedJsonCodec) - Outbox dispatcher with per-key ordered concurrency and a failed→DLQ sweep
- Idempotent consumers in one call (
process_idempotent) - Durable event sourcing and sagas on PostgreSQL (
PgEventStore,PgSnapshotStore,PgSagaStore) - Saga deadlines: schedule timeouts on
Start/Advanceand sweep them withSagaRunner::run_due_timeouts - Saga commands go through a durable outbox, not a direct dispatcher call (
SagaRunner+DurableCommandPublisher+pharos_messaging::OutboxDispatcher):SagaTransition::Failcarries follow-upcommands, enqueued right after the terminal transition is saved — the save and the enqueue are still two separate steps, not one transaction, so a crash between them still drops the compensation, but once enqueued a command is durable and retried on delivery failure instead of being lost on the first failed dispatch - Cross-context sagas:
SagaRunner::handle_anyfolds events from several bounded contexts into one saga instance without changing theSagatrait - Tower as the cross-cutting pipeline seam (timeouts, limits, authorization)
- Observability with
tracingspans andmetricscounters throughout
| Crate | Purpose |
|---|---|
pharos-core |
Domain primitives: entities, aggregates, repositories, value objects, and domain events |
pharos-app |
Application contracts: command/query handlers, event bus, integration events, upcasters |
pharos-messaging |
Broker-facing contracts: messages, publishers/consumers, retry, outbox/inbox, DLQ |
pharos-macros |
Derive macros and id_type! for reducing boilerplate |
pharos |
Convenience facade that reexports the stable API and prelude |
flowchart TD
Domain[Domain Model]
Core[pharos-core]
Macros[pharos-macros]
App[pharos-app]
Example[examples/order]
Domain --> Core
Macros --> Core
App --> Core
Example --> Core
Example --> App
Core --> Entity[Entity]
Core --> Aggregate[AggregateRoot]
Core --> DomainEvent[DomainEvent]
Core --> Repository[Repository]
App --> CQRS[Command and Query Handlers]
App --> Eventing[Event Bus and Handlers]
App --> Outbox[Outbox and Inbox Contracts]
App --> Messaging[Messaging Contracts]
pharos-rs/
├── crates/
│ ├── pharos-core # domain primitives
│ ├── pharos-macros # derive macros + id_type!
│ ├── pharos-messaging # broker contracts, retry, outbox/inbox, DLQ, consumer groups
│ ├── pharos-app # CQRS, EventBus, integration events, upcasters, Tower adapters
│ ├── pharos-memory # in-memory adapters for tests and local development
│ ├── pharos-axum # Axum extractors/helpers for handlers
│ ├── pharos-saga # saga/process-manager primitives
│ ├── pharos-es # event sourcing primitives
│ ├── pharos-proto # Protobuf binary serialization for integration events
│ ├── pharos-testing # EventCapture, test helpers, and the adapter conformance kit
│ └── pharos # convenience meta-crate (re-exports + prelude)
└── examples/
├── order
├── multi-tenant
└── modular-monolith
pharos-rs ships no database, broker, or ORM dependency — only the traits
above and pharos-memory's in-process implementations of them, for tests and
local development. Bringing your own storage is a matter of implementing a
couple of traits and proving them correct with pharos-testing's conformance
kit; see docs/guide/writing-an-adapter.md
(SeaORM as the running example) and
docs/guide/reference-schema.sql (a
copyable PostgreSQL schema to start from).
Most applications should start with the pharos facade and import from its prelude:
Pharos RS is not published to crates.io (every crate sets
publish = false) and is currently at 0.7.0, pre-1.0. Depend on it by git revision and pin a commit — there is no semver resolution or docs.rs to fall back on. Coming from 0.4? See the 0.4→0.5 migration guide. Pinned to a commit before the storage/broker crates andpharos-initwere removed? See the 0.6→0.7 migration guide.
pharos = { git = "https://github.com/lisboaigor/pharos-rs", rev = "<commit>", features = ["macros"] }use pharos::prelude::*;If you want lower-level control, depend on pharos-core, pharos-app, or pharos-macros directly.
| Feature (crate) | Default | Enables |
|---|---|---|
macros (pharos) |
yes | #[derive(...)] and id_type! (pharos-macros) |
tower (pharos-app) |
no | CommandHandlerService/QueryHandlerService (pipeline seam) |
retry (pharos-app) |
no | Retrying event-handler decorator (Tokio timer) |
tenant-task-local (pharos-app) |
no | CURRENT_TENANT task-local (explicit TenantContext is canonical) |
- Documentation index
- 30-minute tutorial
- Complete usage guide
- Architecture guide — event-driven model, outbox, inbox, patterns
- Crate reference — per-crate API tables
- Production operations
- Observability setup
- Decision matrix
- Cookbook
- Pitfalls
| Example | Shows |
|---|---|
examples/order |
Canonical DDD/CQRS/outbox suite — run with cargo run -p order |
examples/multi-tenant |
TenantContext + per-tenant repositories and row-level isolation — cargo run -p multi-tenant |
examples/modular-monolith |
Two bounded contexts in one process via the in-process event bus — cargo run -p modular-monolith |
# build
cargo build --workspace
# test
cargo test --workspace --all-features
# docs
cargo docs # alias for: cargo doc --workspace --no-deps- Explicit rather than magical
- Framework-light
- Idiomatic Rust
- Compatible with DDD and CQRS patterns
- Extensible through traits and adapters
- Useful for modular monoliths and as a foundation for distributed event-driven systems
Pharos RS stands on top of the Rust ecosystem. Thanks to the maintainers and contributors of these third-party libraries used directly across this workspace:
- axum
- chrono
- criterion
- dashmap
- futures
- garde
- http
- indoc
- metrics
- proc-macro2
- prost
- quote
- serde
- serde_json
- syn
- thiserror
- tokio
- tower
- trait-variant
- tracing
- tracing-subscriber
- uuid
Licensed under either of Apache License, Version 2.0 or MIT license at your option.
