jardiscore/kernel

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

Maintainers

Package info

github.com/jardisCore/kernel

pkg:composer/jardiscore/kernel

Statistics

Installs: 32

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-04-01 08:54 UTC

This package is auto-updated.

Last update: 2026-04-01 08:55:51 UTC


README

Build Status License: PolyForm Shield PHP Version PHPStan Level PSR-12

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 Domain() — 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\Domain;

class Ecommerce extends Domain
{
    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()->connection();

        $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 Domain
{
    protected function connection(): 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 Domains (first-write-wins).
null No local service. Use the shared one from another Domain if available.
false Explicitly disabled. Don't use shared fallback either.
class Ecommerce extends Domain
{
    protected function connection(): 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 Domain
{
    protected function connection(): 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 Domain, 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->connection();       // PDO instance
$kernel->container();        // Factory (always available)

Architecture

Domain                          Entry point. Lazy bootstrap. Service sharing.
    ├── domainRoot()            Auto-detected via Reflection
    ├── classVersion()          Built from classVersionConfig()
    ├── cache/logger/...()      Three-state: object | null | false
    ├── 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.
    └── connection()            ConnectionPoolInterface | PDO | null

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

License

Jardis is source-available under the PolyForm Shield License 1.0.0. Free for virtually every purpose — including commercial use. The only restriction: don't build a competing framework.

Jardis — Development with Passion Built by Headgent Development