Skip to content

Kernel

Der Koffer für generierte Jardis-Domains: ein immutabler Service-Container plus ein optionaler ENV-Packer.

Einführung

Jede generierte Domain braucht Infrastruktur: eine Datenbankverbindung, Cache, Logger, Event-Dispatcher und mehr. jardiscore/kernel bündelt all das in einen Koffer (DomainKernel), einen immutablen Container, den die Domain im Konstruktor entgegennimmt: new Ecommerce($kernel).

Das Package macht bewusst wenig: Es liefert den Koffer und einen optionalen Packer, der ihn aus .env baut. Mehr nicht. Der Koffer baut selbst nichts und liest kein ENV; er ist ein reiner, unveränderlicher Konsument.

  • DomainKernel — der Koffer: immutabel, elf Services per Constructor Injection, implementiert DomainKernelInterface
  • BuildDomainKernelFromEnv — optionaler Packer: __invoke(string $configPath): DomainKernel, komponiert zehn Handler-Closures und liest die .env-Kaskade

Kernel-Entkopplung (2026-07)

DomainApp, BoundedContext, ServiceRegistry und die Response-Klassen (ContextResponse, DomainResponse, DomainResponseTransformer) sind aus diesem Package entfernt. Der Builder generiert sie jetzt pro Domain als {Domain}Context (1:1-Port des früheren BoundedContext) und {Domain}\Response\*. ResponseStatus liegt jetzt in jardissupport/contracts. Greife in neuem Code nie auf DomainApp, BoundedContext oder ServiceRegistry zu. Sie existieren nicht mehr. Der generierte {Domain}Context bietet die äquivalente Oberfläche.

Installation

bash
composer require jardiscore/kernel

GitHub: jardisCore/kernel

Abhängigkeiten:

PackageZweck
jardissupport/contractsInterface-Contracts (PSR + Jardis), DomainKernelInterface, ResponseStatus, GeneratedContextInterface
jardissupport/classversionVersionierte Klassen-Auflösung
jardissupport/dotenvENV-Kaskade laden
jardissupport/factoryPSR-11 Container + Reflection-Instanziierung

Optional (composer suggest, vom Packer genutzt, degradiert zu null wenn abwesend):

jardisadapter/{cache,dbconnection,eventdispatcher,filesystem,http,logger,mailer}, ext-redis

DomainKernel — der Koffer

Immutabler Service-Container: alle Services werden im Konstruktor injiziert, danach ist nichts mehr veränderbar. Sicher für Long-Running-Prozesse wie Worker oder Server.

php
use JardisCore\Kernel\DomainKernel;

$kernel = new DomainKernel(
    domainRoot: '/path/to/config',       // erforderlich, darf nicht leer sein
    container: $factory,                 // ?ContainerInterface
    cache: $cache,                       // ?CacheInterface
    logger: $logger,                     // ?LoggerInterface
    eventDispatcher: $dispatcher,        // ?EventDispatcherInterface
    eventListenerRegistry: $registry,    // ?EventListenerRegistryInterface
    httpClient: $client,                 // ?ClientInterface
    connection: $pool,                   // ConnectionPoolInterface|PDO|null
    mailer: $mailer,                     // ?MailerInterface
    filesystem: $filesystemService,      // ?FilesystemServiceInterface
    env: ['db_host' => 'localhost'],     // array — private ENV, hat Vorrang vor $_ENV
);

$ecommerce = new Ecommerce($kernel);     // generierte Domain-Fassade — kein extends

Alle Parameter außer domainRoot sind optional. domainRoot darf nicht leer sein, sonst wirft der Konstruktor. Der Koffer ist nach Erstellung immutable.

Die elf Zugriffe

MethodRückgabeAnmerkung
domainRoot()string
env(string $key)mixedcase-insensitive; private ENV > $_ENV, lowercase gespeichert
container()Factoryumschließt immer den injizierten Container (nicht nur ContainerInterface)
cache()?CacheInterface
logger()?LoggerInterface
eventDispatcher()?EventDispatcherInterface
eventListenerRegistry()?EventListenerRegistryInterfacemit der Kernel-Entkopplung ergänzt — gepaart mit eventDispatcher(), dieselbe Provider-Instanz. Ein generierter {Agg}EventRouter registriert sich darauf; ohne Registry bleibt Event-Routing schlicht inaktiv (kein Fehler)
httpClient()?ClientInterface
dbConnection()ConnectionPoolInterface|PDO|null
mailer()?MailerInterface
filesystem()?FilesystemServiceInterface

container() liefert grundsätzlich eine Factory: wird ein externer Container übergeben, wird er eingebettet. So steht die Reflection-basierte Instanziierung immer zur Verfügung.

Sharing über mehrere Domains ist explizit

Es gibt keinen statischen Service-Registry mehr (ServiceRegistry ist entfernt). Wer Services teilen will, übergibt denselben Koffer an mehrere Domains:

php
$kernel = (new BuildDomainKernelFromEnv())(__DIR__ . '/config');

$ecommerce = new Ecommerce($kernel);   // dieselbe Koffer-Instanz
$billing   = new Billing($kernel);     // dieselbe Instanz → dieselbe Connection, Cache, …

Eine Domain, die eigene Services braucht, baut ihren eigenen Koffer. Es gibt keinen impliziten Fallback mehr.

BuildDomainKernelFromEnv — der ENV-Packer

Optionaler Packer, der einen Koffer aus einer .env-Kaskade zusammenstellt. Eine invokable Klasse, kein statischer Factory-Aufruf.

php
use JardisCore\Kernel\Bootstrap\BuildDomainKernelFromEnv;

$packer = new BuildDomainKernelFromEnv();
$kernel = $packer(__DIR__ . '/config');   // liest config/.env (+ Kaskade) via DotEnv::loadPrivate()

$ecommerce = new Ecommerce($kernel);
  • Eine invokable Klasse__invoke(string $configPath): DomainKernel. $configPath ist zugleich der domainRoot() des gepackten Koffers.
  • ENV-Kaskade über JardisSupport\DotEnv\DotEnv::loadPrivate(), dieselbe load()/load?()-Kaskade wie bei jeder anderen Jardis-Konfiguration. Templates: docs/env-examples/.
  • Komponiert zehn Handler-Closures im Konstruktor ((new Handler())->__invoke(...), Closure-Orchestrator, keine eigene Logik im Body): BuildConnectionFromEnv, ExtractPdoFromConnection, BuildRedisFromEnv, BuildCacheFromEnv, BuildLoggerFromEnv, BuildEventListenerProviderFromEnv, BuildEventDispatcherFromProvider, BuildHttpClientFromEnv, BuildMailerFromEnv, BuildFilesystemFromEnv, plus eine loadEnv-Closure.
  • Event-Dispatcher + Registry sind ein Paar: ein ListenerProvider wird gebaut und sowohl als eventDispatcher() (umschlossen) wie als eventListenerRegistry() (unverändert) übergeben. Ein generierter {Agg}EventRouter ist so für den Dispatcher sichtbar.
  • Jeder Adapter ist optional (composer suggest): jede Handler-Closure degradiert via class_exists()-Guard zu null, wenn ihr Adapter fehlt oder ihr ENV unkonfiguriert ist. Für einen fehlenden optionalen Service wirft nichts.
  • Container-Wiring ist außerhalb des Scopes: der gepackte Koffer nutzt die nackte Factory als Fallback. Wer einen eigenen PSR-11-Container braucht, baut den DomainKernel direkt statt über den Packer.

Die generierte Seite (nicht in diesem Package)

Alles unterhalb des Koffers wird pro Domain vom Builder generiert, Details im Platform-Workflow (Get Started):

  • {Domain}Context — die generierte, hermetische Basis, die jede BC-/Aggregate-Fassade der Domain erweitert. Trägt die Kernel-Naht handle()/context() (jetzt protected, familien-intern) sowie resource()/payload()/version()/result(). implements JardisSupport\Contract\Kernel\GeneratedContextInterface: kein extends BoundedContext, keine Package-Basisklasse.
  • {Domain}\Response\ — das generierte Response-Trio (ContextResponse, DomainResponse, DomainResponseTransformer), 1:1 aus dem früheren src/Response/* portiert. ResponseStatus liegt in jardissupport/contracts.
  • Die Domain-Fassade (z. B. Ecommerce) ist final, hält nur den Koffer (DomainKernelInterface $kernel) und registriert jeden Aggregate-Event-Router über $kernel->eventListenerRegistry().

Wer generierten Code erweitert oder verdrahtet, arbeitet nicht mehr mit diesem Package, sondern mit dem generierten {Domain}Context.

HTTP-Delivery

Der Koffer ist reine Laufzeit. Er kennt kein HTTP. Die Request/Response-Schicht darum herum (FastRoute-Router, PSR-15-Pipeline, DomainResponse → PSR-7-Envelope) liefert App (jardiscore/app).

Architektur

BuildDomainKernelFromEnv (optionaler ENV-Packer)
    ↓ packt
DomainKernel — der Koffer (immutabel, Constructor Injection, 11 Zugriffe)
    ↓ new {Domain}($kernel)
{Domain}Context (generiert — platform-implementation)
    handle()/context() (Kernel-Naht, protected) · resource()/payload()/version()/result()
    ↓ result()
ContextResponse (generiert) → DomainResponseTransformer (generiert) → DomainResponse (generiert)

Alles unterhalb der DomainKernel-Linie wird pro Domain vom Builder generiert, nicht von diesem Package bereitgestellt.

Dependency-Richtung:

DomainKernel → PSR-Interfaces (aus jardissupport/contracts)
             → DotEnv, Factory, ClassVersion (aus jardissupport/*)
Bootstrap\   → jardisadapter/* (nur im Bootstrap-Namespace erlaubt)

Der Koffer-Kern (DomainKernel + die Contract-Interfaces) bleibt adapterfrei: nur jardissupport/contracts + PSR. Adapter-Importe sind ausschließlich im Bootstrap\-Sub-Namespace legitim (Application-Wiring, kein Domain-Code).