Highlights
What one click on Build generates -- and everything that comes with it out of the box.
Build Artifacts
| Artifact | Description |
|---|---|
Domain facade {Domain} | Entry point -- holds the Koffer (kernel bag), opens the Bounded Contexts |
{Domain}Context | Generated base class of the context family -- kernel seam and versioning |
| BC facade | The outer door: {agg}() (read facade), process(), and rule-guarded Commands |
| Entities | Typed attributes, snapshots, change tracking, schema validation |
| Aggregate (READ/WRITE separated) | READ facade for queries and lists; WRITE facade (Commands, event()) family-internal -- the hermetic aggregate tree |
| Repository Pipeline | Query, Transform, Validate, Persist -- four stages, one source of truth |
| Command DTOs & Handler | Typed input objects + orchestrators with steps (Initialize, Validate, Hydrate, Apply, Persist) |
| Query DTOs & Handler | Single aggregates and paginated lists with SQL JOINs |
| Processes | BC-level orchestration as a graph -- DTO, orchestrator, and node stubs |
| Rules layer | Guard in front of the Command; a violation ends as a DomainResponse with status 422 |
| Domain Events | Lifecycle (Created/Removed) and operation events -- model-driven, announced |
| FieldMap | Bidirectional mapping DTO <-> database |
| API Specifications | OpenAPI 3.1, AsyncAPI 3.0, Protocol Buffers 3 |
For an aggregate with five entities, this results in several dozen files -- consistent, in one place.
DDD from the Ground Up
Domains, Bounded Contexts, Aggregates, Entities, Commands, Queries, Events -- the entire DDD architecture stands after the build. No manual wiring, no piecing together patterns from different libraries.
- Domain = collection of Bounded Contexts. Each BC is a closed unit with its own API.
- Aggregate = consistency boundary. The hermetic aggregate tree is the sole door to the entity graph -- writes exclusively via Commands.
- CQRS = reads and writes separated. Reads via the read facade of the aggregate, writes via a process -- by construction, not by convention.
- Events = decoupled communication. Domain Events are created model-driven and announced from a process node.
$ecommerce = new Ecommerce($kernel);
// Writing via a process
$response = $ecommerce->sales()->process()->placeOrder($dto);
// Reading via the read facade of the aggregate
$order = $ecommerce->sales()->order()->getOrderById($orderId);Hexagonal Architecture as Standard
The generated code follows Hexagonal Architecture -- dependency arrows point inward, infrastructure stays outside.
Domain Layer (Core) -- Entities, Aggregates, Events
Application Layer (Support) -- Commands, Queries, Repositories, Validation
Infrastructure (Adapter) -- Database, Cache, HTTP, Messaging- Core never imports adapter code. By construction, not by code reviews.
- All infrastructure behind interfaces. Database, cache, logger, events -- swappable without changing a single line of domain code.
- PSR-compatible. PSR-3 (Logger), PSR-6/16 (Cache), PSR-7/18 (HTTP), PSR-11 (Container), PSR-14 (Events), PSR-15 (Middleware).
Versioning next Level
Evolve generated code without forking it. The ClassVersion system solves this structurally:
src/Ecommerce/Sales/Command/Handler/Step/
+-- HydrateCreateOrder.php <-- generated (v1)
+-- v2/
+-- HydrateCreateOrder.php <-- custom override (v2)- Automatic resolution. If
v2/exists -- it's used. Otherwise the base class takes over. - Parallel versions. Multiple behavior versions of a class live in the same build; the active one is resolved per call via namespace injection.
- Fallback chains. v3 falls back to v2, v2 to base -- evolve sprint by sprint.
- What can be versioned? Command Steps, Repository Pipeline, Aggregate Steps, FieldMap, Entity Validators, Registries.
No breaking change. No feature flag. No big-bang release.
Response System API Ready
Every domain operation delivers a DomainResponse -- immutable, aggregated, context-keyed.
$response->isSuccess(); // true/false
$response->getStatus(); // 200, 201, 400, 404, 422, 500
$response->getData(); // ['OrderContext' => [...], 'InventoryContext' => [...]]
$response->getEvents(); // ['OrderContext' => [OrderCreated(...)]]
$response->getErrors(); // ['OrderContext' => ['...']]
$response->getMetadata(); // Duration, Timestamp, Version, Contexts- Context-keyed. With nested BC calls, all data stays separated by context.
- Immutable & aggregated. The generated
{Domain}Contextbuilds up the response and returns it final and immutable. - HTTP-like codes. Status as an enum; 422 signals a violated business rule. App translates the response into the
{status, data, errors, meta}envelope.
3 API Formats Always There
From the same definitions, Jardis generates three API specifications -- always in sync, no drift:
| Format | Purpose |
|---|---|
| OpenAPI 3.1 | REST API with DomainResponse envelope |
| AsyncAPI 3.0 | Event channels and payload schemas for all Domain Events |
| Protocol Buffers 3 | gRPC services with typed messages |
The internal and external interface are identical -- whether via method call or HTTP/gRPC.
Pipeline Consistency as It Should Be
Every read and write operation flows through a consistent pipeline:
Query -> Transform -> Validate -> Persist- Query: Load root entity by PK, load child entities in FK order.
- Transform: Convert flat data into nested aggregate structure.
- Validate: Check only changed entities -- not everything, just what's relevant.
- Persist: Transaction-safe, FK-safe ordering, cascading deletes.
Query output = Transform input = Persist expectation. By construction, not by convention.