Skip to content

Highlights

What one click on Build generates -- and everything that comes with it out of the box.

Build Artifacts

ArtifactDescription
Domain facade {Domain}Entry point -- holds the Koffer (kernel bag), opens the Bounded Contexts
{Domain}ContextGenerated base class of the context family -- kernel seam and versioning
BC facadeThe outer door: {agg}() (read facade), process(), and rule-guarded Commands
EntitiesTyped 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 PipelineQuery, Transform, Validate, Persist -- four stages, one source of truth
Command DTOs & HandlerTyped input objects + orchestrators with steps (Initialize, Validate, Hydrate, Apply, Persist)
Query DTOs & HandlerSingle aggregates and paginated lists with SQL JOINs
ProcessesBC-level orchestration as a graph -- DTO, orchestrator, and node stubs
Rules layerGuard in front of the Command; a violation ends as a DomainResponse with status 422
Domain EventsLifecycle (Created/Removed) and operation events -- model-driven, announced
FieldMapBidirectional mapping DTO <-> database
API SpecificationsOpenAPI 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.
php
$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.

php
$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}Context builds 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:

FormatPurpose
OpenAPI 3.1REST API with DomainResponse envelope
AsyncAPI 3.0Event channels and payload schemas for all Domain Events
Protocol Buffers 3gRPC 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.