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
composer require jardiscore/kernelGitHub: jardisCore/kernel
Abhängigkeiten:
| Package | Zweck |
|---|---|
jardissupport/contracts | Interface-Contracts (PSR + Jardis), DomainKernelInterface, ResponseStatus, GeneratedContextInterface |
jardissupport/classversion | Versionierte Klassen-Auflösung |
jardissupport/dotenv | ENV-Kaskade laden |
jardissupport/factory | PSR-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.
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 extendsAlle Parameter außer domainRoot sind optional. domainRoot darf nicht leer sein, sonst wirft der Konstruktor. Der Koffer ist nach Erstellung immutable.
Die elf Zugriffe
| Method | Rückgabe | Anmerkung |
|---|---|---|
domainRoot() | string | |
env(string $key) | mixed | case-insensitive; private ENV > $_ENV, lowercase gespeichert |
container() | Factory | umschließt immer den injizierten Container (nicht nur ContainerInterface) |
cache() | ?CacheInterface | |
logger() | ?LoggerInterface | |
eventDispatcher() | ?EventDispatcherInterface | |
eventListenerRegistry() | ?EventListenerRegistryInterface | mit 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:
$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.
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.$configPathist zugleich derdomainRoot()des gepackten Koffers. - ENV-Kaskade über
JardisSupport\DotEnv\DotEnv::loadPrivate(), dieselbeload()/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 eineloadEnv-Closure. - Event-Dispatcher + Registry sind ein Paar: ein
ListenerProviderwird gebaut und sowohl alseventDispatcher()(umschlossen) wie alseventListenerRegistry()(unverändert) übergeben. Ein generierter{Agg}EventRouterist so für den Dispatcher sichtbar. - Jeder Adapter ist optional (
composer suggest): jede Handler-Closure degradiert viaclass_exists()-Guard zunull, 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
Factoryals Fallback. Wer einen eigenen PSR-11-Container braucht, baut denDomainKerneldirekt 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-Nahthandle()/context()(jetztprotected, familien-intern) sowieresource()/payload()/version()/result().implements JardisSupport\Contract\Kernel\GeneratedContextInterface: keinextends BoundedContext, keine Package-Basisklasse.{Domain}\Response\— das generierte Response-Trio (ContextResponse,DomainResponse,DomainResponseTransformer), 1:1 aus dem früherensrc/Response/*portiert.ResponseStatusliegt injardissupport/contracts.- Die Domain-Fassade (z. B.
Ecommerce) istfinal, 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).