jardiscore/kernel

DDD Kernel for PHP — BoundedContext, ContextResponse, DomainResponse, DomainKernel

Maintainers

Package info

github.com/jardisCore/kernel

Homepage

pkg:composer/jardiscore/kernel

Statistics

Installs: 97

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-04-18 10:43 UTC

This package is auto-updated.

Last update: 2026-04-18 10:43:58 UTC


README

Build Status Latest Version License: MIT PHP Version PHPStan Level PSR-12 Coverage PSR-3 PSR-11 PSR-14 PSR-16 PSR-18

Part of the Jardis Business Platform — Enterprise-grade PHP components for Domain-Driven Design

The DDD kernel you always wanted. Eight files. Zero magic. Full control.

Why Jardis Kernel?

Most DDD frameworks force you into their world. Jardis Kernel gives you the building blocks and stays out of the way.

  • new DomainApp() — done. No configuration, no YAML, no service files. Your domain boots itself.
  • Plain PDO works. Pass a PDO, get going. Need connection pooling later? Swap in a ConnectionPool. Same API.
  • Services shared across domains automatically. Five domains, one database connection. First-write-wins. Zero plumbing.
  • Every service is optional. Need a cache? Override one method. Don't need one? Don't touch anything.
  • ClassVersion built in. Versioned classes via namespace injection — the foundation for the Jardis Builder.
  • Immutable kernel. Once built, nothing changes. Safe for application servers, workers, long-running processes.

Installation

composer require jardiscore/kernel

Quickstart

1. Create your Domain

use JardisCore\Kernel\DomainApp;

class Ecommerce extends DomainApp
{
    public function order(): OrderContext
    {
        return new OrderContext($this->kernel());
    }
}

2. Define a Bounded Context

use JardisCore\Kernel\BoundedContext;
use JardisCore\Kernel\Response\DomainResponseTransformer;

class PlaceOrder extends BoundedContext
{
    public function __invoke(): DomainResponse
    {
        $order = $this->payload();
        $pdo = $this->resource()->dbConnection();

        $stmt = $pdo->prepare('INSERT INTO orders (customer, total) VALUES (?, ?)');
        $stmt->execute([$order['customer'], $order['total']]);

        $this->result()->addData('orderId', (int) $pdo->lastInsertId());
        $this->result()->addEvent(new OrderPlaced($order));

        return (new DomainResponseTransformer())->transform($this->result());
    }
}

3. Use it

$shop = new Ecommerce();
$response = $shop->order()->placeOrder(['customer' => 'Acme', 'total' => 99.90]);

$response->isSuccess();   // true
$response->getData();     // ['PlaceOrder' => ['orderId' => 42]]
$response->getEvents();   // ['PlaceOrder' => [OrderPlaced {...}]]

That's it. No bootstrap file. No container setup. No framework.

Provide a Database

The simplest way — a plain PDO:

class Ecommerce extends DomainApp
{
    protected function dbConnection(): ConnectionPoolInterface|PDO|false|null
    {
        return new PDO('mysql:host=localhost;dbname=shop', 'root', '');
    }
}

That's all you need. A PDO. Works everywhere.

Provide Services

Override protected methods to add infrastructure. Every method uses three-state logic:

Return Meaning
object Use this service. Share it with other DomainApps (first-write-wins).
null No local service. Use the shared one from another DomainApp if available.
false Explicitly disabled. Don't use shared fallback either.
class Ecommerce extends DomainApp
{
    protected function dbConnection(): ConnectionPoolInterface|PDO|false|null
    {
        return new PDO('mysql:host=localhost;dbname=shop', 'root', '');
    }

    protected function logger(): LoggerInterface|false|null
    {
        return new MyLogger('/var/log/shop.log');
    }

    protected function classVersionConfig(): ClassVersionConfig
    {
        return new ClassVersionConfig(
            version: ['v1' => ['v1'], 'v2' => ['v2', 'current']],
            fallbacks: ['v2' => ['v1']],
        );
    }
}

Multi-Domain Service Sharing

Multiple domains in one application share services automatically:

$ecommerce = new Ecommerce();   // Builds PDO, registers it shared
$billing   = new Billing();     // Gets the same PDO — zero config
$analytics = new Analytics();   // Same. First-write-wins.

A domain that needs its own connection? Override the method. A domain that wants no connection at all? Return false.

Advanced: ConnectionPool (optional)

For application servers and read replicas, install jardisadapter/dbconnection and use ConnectionPool instead of plain PDO:

composer require jardisadapter/dbconnection
use JardisAdapter\DbConnection\ConnectionPool;
use JardisAdapter\DbConnection\Factory\ConnectionFactory;

class Ecommerce extends DomainApp
{
    protected function dbConnection(): ConnectionPoolInterface|PDO|false|null
    {
        $factory = new ConnectionFactory();

        return new ConnectionPool(
            writer: $factory->mysql('primary', 'user', 'pass', 'shop'),
            readers: [
                $factory->mysql('replica1', 'user', 'pass', 'shop'),
                $factory->mysql('replica2', 'user', 'pass', 'shop'),
            ],
        );
    }
}

ConnectionPool provides lifecycle management, health checks, round-robin load balancing, and automatic writer fallback when no readers are available. The rest of your code doesn't change.

Direct Kernel Usage

For full control without DomainApp, use DomainKernel directly:

use JardisCore\Kernel\DomainKernel;

$kernel = new DomainKernel(
    domainRoot: __DIR__ . '/src',
    connection: new PDO('mysql:host=localhost;dbname=shop', 'root', ''),
    logger: $myLogger,
    env: ['app_env' => 'production'],
);

$kernel->env('APP_ENV');     // 'production' (case-insensitive)
$kernel->dbConnection();     // PDO instance
$kernel->container();        // Factory (always available)

Architecture

DomainApp                       Entry point. Lazy bootstrap. Service sharing.
    ├── domainRoot()            Auto-detected via Reflection
    ├── classVersion()          Built from classVersionConfig()
    ├── cache()                 Three-state: object | null | false
    ├── logger()                   ↓
    ├── eventDispatcher()          ↓
    ├── httpClient()               ↓
    ├── dbConnection()             ↓
    ├── mailer()                   ↓
    ├── filesystem()               ↓
    ├── factory()               Factory + ClassVersion + DI Container
    └── loadEnv()               domainRoot/.env → private ENV

DomainKernel                    Immutable. Constructor injection only.
    ├── env(key)                Case-insensitive. Private > $_ENV
    ├── container()             Always Factory. Wraps external container.
    ├── cache()                 ?CacheInterface
    ├── logger()                ?LoggerInterface
    ├── eventDispatcher()       ?EventDispatcherInterface
    ├── httpClient()            ?ClientInterface
    ├── dbConnection()          ConnectionPoolInterface | PDO | null
    ├── mailer()                ?MailerInterface
    └── filesystem()            ?FilesystemServiceInterface

BoundedContext                  Use case handler.
    ├── handle(class, ...args)  Smart resolution: ClassVersion → Factory → Container
    ├── resource()              Access to DomainKernel
    ├── payload()               Request data
    └── result()                Lazy ContextResponse

ContextResponse → DomainResponseTransformer → DomainResponse
    Mutable accumulator    Recursive aggregation    Immutable answer

Related Packages

Included dependencies:

Package Purpose
jardissupport/contract Interface contracts (DomainKernelInterface, etc.)
jardissupport/classversion Versioned class resolution via namespace injection
jardissupport/factory PSR-11 Container + class instantiation
jardissupport/dotenv ENV file loading

Optional (composer suggest):

Package Purpose
jardisadapter/dbconnection ConnectionPool with read/write splitting, health checks, load balancing

Kernel and Foundation

Jardis Kernel is a platform — it provides the building blocks but leaves service assembly to you. Its sister package Jardis Foundation (jardiscore/foundation) builds on top of Kernel and turns it into a ready-to-run solution:

Kernel Foundation
Approach Override protected methods, wire services yourself Everything configured via .env
Entry point class MyApp extends DomainApp class MyApp extends JardisApp
Services You build them Auto-assembled from ENV variables
Dependencies Minimal (4 packages + PSR interfaces) Full Jardis ecosystem (Cache, Logger, DbConnection, EventDispatcher, HTTP)
Use case Custom setups, libraries, testing Production DDD projects

When to use which: Start with Foundation for most projects — it handles all infrastructure wiring. Use Kernel directly when you need full control or want to integrate Jardis into an existing DI setup.

Platform for the Jardis Builder

Both Kernel and Foundation serve as the runtime platform for code generated by the Jardis Builder. The Builder generates DDD project structures — Aggregates, BoundedContexts, Repositories, Commands, Queries — that run directly on these platforms:

Jardis Builder (development-time)
    │
    │  generates
    ▼
DDD Project Code (Aggregates, BoundedContexts, Repositories, ...)
    │
    │  runs on
    ▼
Foundation (JardisApp)  ──extends──▶  Kernel (DomainApp)
    ENV-driven                        Manual wiring
    Production-ready                  Full control

ClassVersion — built into the Kernel — is the foundation for the Builder's versioned class resolution. It enables namespace-based class versioning so generated code can evolve without breaking existing consumers.

Documentation

Full documentation, guides, and API reference:

docs.jardis.io/en/core/kernel

License

Jardis is open source under the MIT License. Free for any purpose — commercial or non-commercial.

Jardis — Development with Passion Built by Headgent Development

KI-gestützte Entwicklung

Dieses Package liefert einen Skill für Claude Code, Cursor, Continue und Aider mit. Installation im Konsumentenprojekt:

composer require --dev jardis/dev-skills

Mehr Details: https://docs.jardis.io/skills