Skip to content

Latest commit

 

History

History
457 lines (324 loc) · 24.2 KB

KERNEL.md

File metadata and controls

457 lines (324 loc) · 24.2 KB

Kernel components

Cross-cutting infrastructure Serenade owns. Products register domain services and bundles on top.

Component map

Component Responsibility
Kernel / lifecycle Boot, environment, bundle registration order, shutdown
Dependency injection Service container, autowiring-style resolution, scoped services
Event dispatcher Sync domain and infrastructure events; subscriber tags
HTTP foundation Request/response types, attributes, lifecycle (framework-agnostic core)
HTTP client Outbound HttpClient (serenade-http-client); see HTTP_CLIENT.md
Lock Named locks (serenade-lock); see LOCK.md
RateLimiter Policies + storage (serenade-rate-limiter); see RATE_LIMITER.md
Scheduler Cron / interval runner (serenade-scheduler); see SCHEDULER.md
HTTP kernel Middleware pipeline, controller/action resolution
Routing Route collection, requirements, method constraints
Configuration Layered config (defaults, env, TOML package files; YAML still accepted)
Console CLI application (bin/console analogue), commands, optional rich TUI
Cache PSR-like pools (memory / filesystem / Redis, tags) plus HTTP response cache headers in serenade-http
Session Session bag, flash, SessionStore, cookie session, HTTP middleware (serenade-session)
Security AuthN/Z hooks, firewall, voters, CSRF token manager
Form HTML forms: bind, CSRF by default, XSS-safe render (FORMS.md)
Translation Translator, catalogues, locale negotiation, ICU format helpers (I18N.md)
Messenger Command/query/event bus; async transport adapters
Serializer DTO ↔ JSON (and other formats); normalizers
Validator Constraint validation on DTOs and commands
Contracts Stable traits for persistence, clock, id generation, etc.
Observability Structured logging conventions (Monolog-like channels / var/log layout); tracing/metrics hooks
OpenAPI Document helpers + optional explorer UIs (serenade-openapi; apps own path schemas)
Testing Kernel test harness, fake container, event assertion helpers

Lifecycle

Created  →  register bundles (any order)
         →  compile (sort by dependencies, then `BundleInterface::build`)
         →  boot (`BundleInterface::boot`)
         →  shutdown (`BundleInterface::shutdown`, reverse dependency order)

serenade-kernel exposes Kernel, App, BundleInterface / Bundle, BundleRegistry, and Environment (dev / test / prod, plus Custom for names like staging or recette). Debug defaults on for dev and test only. An application with zero bundles is a valid boot.

Kernel::boot compiles first when the kernel is still in Created. After Shutdown the kernel is terminal.

Dependency injection

serenade-di provides ContainerBuilder → compile passes → Container.

Concept Role
ServiceDefinition Id, Scope (singleton / prototype), declared Reference dependencies
ParameterBag String parameters available to factories
CompilePass Extensible pipeline run before the container freezes
Container::get / get_as Resolve by id or alias; detects circular dependency at compile and resolve

Factories return Box<dyn Any + Send + Sync>; callers downcast with get_as.

Events

serenade-event provides a synchronous EventDispatcher.

Concept Role
Event Named payload (cart.updated, order.placed, …)
EventSubscriber Handles one event name; higher priority runs first
RegisterEventSubscribersPass Collects services tagged event.subscriber into event_dispatcher
RecordingSubscriber / assert_dispatched Test harness for dispatch order

Dispatch stays in-process. Async transports belong on the messenger component.

Request path

serenade-http owns framework-agnostic types. Thin server adapters copy bytes in and out.

HTTP adapter (Actix / Axum / …)
    → Request (method, path, headers, body, attributes)
    → UrlMatcher (RouteCollection; writes `_route` + path params)
    → Middleware stack (first registered layer is outermost)
    → Controller (`RequestHandler`)
    → Response

HttpKernel::handle always returns a Response. Handler and middleware errors go through ExceptionHandler (DefaultExceptionHandler maps HttpError status and message to text/plain).

AsyncHttpKernel is the async counterpart: controllers return a future (database I/O). Wrap sync handlers with AsyncHttpKernel::from_sync. Actix listen / app take AsyncHttpKernel; sync dispatch remains for HttpKernel.

Routing lives in serenade-http: Route / RouteCollection, UrlMatcher (404 / 405), and RouteLoader for bundles. Path segments {name} become request attributes.

HTTP adapters

Server crates stay thin:

  1. Map the server request (method, path, headers, body) into serenade_http::Request.
  2. Optionally run UrlMatcher::apply to set _route and path parameters.
  3. Call HttpKernel::handle.
  4. Map serenade_http::Response back to the server response type.

serenade-http-actix implements that bridge for Actix Web (from_actix, to_actix, dispatch / dispatch_async). For skeletons that only need to bind and serve a kernel, call serenade_http_actix::listen(addr, async_kernel) (or app(data) when composing Actix yourself).

serenade-http-axum is the same bridge for Axum (from_axum, to_axum, dispatch / dispatch_async, listen / router). Pick Actix or Axum at the app edge; the foundation crate stays unchanged.

HTTP ops (request-id, probes, drain)

Built-ins in serenade-http (see HTTP_OPS.md):

Helper Role
RequestIdMiddleware / AsyncRequestIdMiddleware Propagate or generate x-request-id
HealthMiddleware / AsyncHealthMiddleware /healthz + /readyz short-circuit
Readiness Shared ready bit; mark_not_ready before drain

Stop order: mark not ready → stop Actix/Axum listener → Kernel::shutdown() on the app kernel.

Messenger and jobs

Pattern Use
Command bus Mutations with one handler
Query bus Read models (optional CQRS)
Event bus Side effects after commit
Async transport Queue/worker integration (product chooses backend)

Sync in-process dispatch lives in serenade-messenger: implement Message with a stable NAME, mark as Command or Event, register handlers on MessageBus, then dispatch_command / dispatch_event. Commands require exactly one handler; events fan out (missing handlers are a no-op). Register Middleware layers with add_middleware (first registered is outermost); built-ins include LoggingMiddleware + LogSink and ValidationMiddleware + ValidateHook. DispatchContext exposes payload (&dyn Any) so validation hooks can inspect the message. Async backends implement Transport (send returns a boxed future); InMemoryTransport is the in-process adapter for tests and local workers. Durable Redis queues use WireEnvelope (message name + opaque bytes) and RedisTransport (feature redis: RPUSH/LPOP on a list key). Apps serialize payloads themselves; in-process Envelope stays Any and does not cross the wire (#11, Redis wire #209). Products wire long-running work through messenger transports rather than ad-hoc task spawning.

Serializer

DTO ↔ wire formats live in serenade-serializer (#12, TOON #125). See SERIALIZER.md.

Piece Role
Encoder / Decoder Intermediate serde_json::Value ↔ bytes; JsonEncoder / JsonDecoder for json
ToonEncoder / ToonDecoder / encode_toon_string TOON v4.1 for LLM / agent context (Cargo feature toon)
Normalizer / Denormalizer Object ↔ Value for types that need a custom graph walk
NormalizerRegistry First matching normalizer/denormalizer wins
Serializer Normalize then encode; decode then denormalize (SerializerInterface analogue)
Serde bridge serialize_value / deserialize_value for typed Serialize / DeserializeOwned JSON

Custom types register normalizers on the registry. Redis/XML formats remain out of scope. HTTP and MCP stay JSON; TOON is opt-in export for prompts.

Validator

Constraint checks live in serenade-validator (#13).

Piece Role
Constraint Rule that may append a Violation (NotBlank, Length, Range)
Validator / RecursiveValidator Runs constraints into a ConstraintViolationList
Validatable Object validates itself through a Validator
MessengerValidateHook Implements messenger ValidateHook; rejects with MessengerError::Rejected

Register message types on the hook, then wrap the bus with ValidationMiddleware.

Security

AuthN/Z and CSRF live in serenade-security (#10, CSRF in #110). See SECURITY.md.

Piece Role
UserInterface / TokenInterface Principal and credentials carrier
Voter / AccessDecisionManager Affirmative access checks
FirewallMiddleware HTTP middleware: header → Authenticator → _security_token attribute
CsrfTokenManager / HmacCsrfTokenManager Stateless HMAC CSRF tokens (_token)
PasswordHasher / Argon2idPasswordHasher Argon2id hash/verify (PHC string)
login / SessionTokenMiddleware Session identity bridge for HTML logins

OAuth/OIDC is out of scope. Apps plug bearer or API-key authenticators. HTML forms use CSRF via serenade-form.

Admin / back office

Serenade does not ship a Symfony 1-style admin generator (YAML → full CRUD UI) in the kernel or FrameworkBundle. That matches modern Symfony: core has no EasyAdmin; ecosystem and apps own the BO.

Concern Owner
Hand-written or SPA admin UIs App / product (e.g. MyFeed /admin, product admin hosts)
Form + CSRF + escape on admin pages Serenade (FORMS.md, SECURITY.md)
Firewall / session login for admin routes Serenade primitives; apps wire authenticators
Optional EasyAdmin-shaped Admin CRUD bundle serenade-admin (opt-in; not FrameworkBundle) - ADMIN.md, #177

Stance (not in kernel): #122. Implementation: #177.

Forms

HTML form helpers live in serenade-form (#110). See FORMS.md.

Piece Role
Form / FormBuilder Fields, bind POST, validate, render
CSRF default on _token + CsrfTokenManager
escape_html / escape_attr XSS-safe output

Apps stay thin: declare fields and constraints; framework owns CSRF and escape.

View helpers

Optional HTML helpers live in serenade-view (#111). See VIEW.md.

Piece Role
path / RouteCollection::generate Named reverse routing
asset / AssetConfig Asset URL under a base prefix
partial Include-style fragment nesting (no engine)
Re-exported escape Same XSS helpers as Forms

Translation / i18n

UI strings and locale live in serenade-translation (#132). See I18N.md.

Piece Role
Translator / catalogues TOML-first domains, fallbacks, {name} + ICU plural subset
LocaleNegotiator Query / cookie / Accept-Language → _locale
ICU format helpers Number, currency code, date (feature icu)
TranslationExtension DI service translator

Product multilang entity rows stay in the application database.

Cache

PSR-like pools live in serenade-cache (#9, Redis follow-up #99). See CACHE.md.

Piece Role
CacheItem / ArrayCacheItem Key, hit flag, Arc value, optional TTL, optional tags
CacheItemPool / ArrayAdapter In-memory get/save/delete/clear; invalidate_tags
FilesystemAdapter Disk-backed pool; TTL; marshaller; persisted tags
RedisAdapter (feature redis) redis-rs + r2d2; prefix keys; SET/PX; SCAN clear; persisted tags
cache.pool tag DI tag; RegisterDefaultCachePoolPass seeds cache.app
HTTP headers HttpCacheHeaders / maybe_not_modified in serenade-http (see CACHE.md)

Default DI pool is in-memory. Apps register FilesystemAdapter or RedisAdapter when they need persistence beyond process memory.

Session

Session stickiness lives in serenade-session. See SESSION.md.

Piece Role
Session Attribute bag for one request
FlashBag One-shot messages on the session
SessionStore / MemorySessionStore Persist attributes by opaque id
CookieSession Open/commit with session-id cookie
SessionMiddleware / AsyncSessionMiddleware Load/save per request on HttpKernel / AsyncHttpKernel

Push SessionMiddleware::new(CookieSession::new(store)) (first registered is outermost). Controllers use request_session / request_session_mut. CSRF stays in serenade-security (HMAC, no session required).

Search / indexation

Basic document search lives in serenade-search (#129, #255). See SEARCH.md.

Piece Role
SearchDocument / SearchQuery / SearchHit Ids, text fields, ranked hits
DocumentIndex Upsert / delete / query / clear
MemorySearchAdapter Zero-deps in-memory index
HttpSearchAdapter (feature http) REST stub; apps own the HTTP client

No mandatory SaaS engine in core. Apps wire adapters when they need one.

Mailer / Mime

Outgoing message types live in serenade-mailer (#153, #228, #253). See MAILER.md.

Piece Role
Address Mailbox (email + optional display name; parse / parse_list)
Body Text and/or HTML parts
Attachment / ContentDisposition Downloadable or inline (CID) parts
MimeTree / MimePart multipart/alternative, related, mixed
Email Builder for From/To/Cc/Bcc/Reply-To, subject, body, attach/embed
Transport Sync send contract
NullTransport / FileTransport / SmtpTransport Discard / file dump / SMTP (lettre)
EspHttpTransport (feature esp) HTTP ESP mail API; apps own the HTTP client
RegisterDefaultMailerPass DI: service mailer defaults to null

FrameworkExtension installs the default mailer pass. See MAILER.md.

Notifier (SMS / push)

Non-email channels live in serenade-notifier (#230). See NOTIFIER.md.

Piece Role
Channel Sms / Push
Notification SMS or push payload
Transport Sync supports + send
NullTransport / MemoryTransport / SmsOnlyTransport Discard / record / SMS-only
RegisterDefaultNotifierPass DI: service notifier defaults to null

FrameworkExtension installs the default notifier pass. Email remains in the mailer crate.

String / Inflector

Helpers live in serenade-string (#229). See STRING.md.

Piece Role
slug URL-safe lowercase slug
snake_case / kebab_case / camel_case / pascal_case / title_case Case transforms
pluralize / singularize English Inflector (limited irregulars)

Pure functions; no DI pass. ICU / catalogue plurals stay in I18N.md.

Expression

Safe rule formulas live in serenade-expression (#251). See EXPRESSION.md.

Piece Role
evaluate / evaluate_bool Parse and run against ExpressionContext
ExpressionGuard In serenade-workflow: block transitions from an expression string

Boolean / arithmetic / property paths only. No function calls or scripting VM.

Workflow

State machines live in serenade-workflow (#239). See WORKFLOW.md.

Piece Role
Definition / DefinitionBuilder Places, transitions, initial marking
Workflow can / apply / enabled_transitions
Marking / MarkingStore / MemoryMarkingStore Subject places + persist trait
Guard / TransitionListener Block before apply; hook after apply

Construct in app code; no FrameworkExtension pass yet.

Filesystem

Path helpers live in serenade-filesystem (#241). See FILESYSTEM.md.

Piece Role
exists / is_file / is_dir Presence checks
mkdir / remove / remove_tree / rename Directory and path ops
dump_file / append_to_file / read / touch File contents
copy / mirror File and tree copy
temp_dir / temp_file Temporary paths

Process

Child processes live in serenade-process (#243). See PROCESS.md.

Piece Role
Process Builder: args, cwd, env, timeout
run / must_run Capture stdout/stderr; optional fail-on-nonzero
CompletedProcess Exit status + output helpers

Finder

Directory walking lives in serenade-finder (#245). See FINDER.md.

Piece Role
Finder Builder: roots, files/directories, name glob, depth
collect Sorted matching paths
name_matches Basename * / ? helper

HttpClient (outbound)

Outbound HTTP lives in serenade-http-client (#184). See HTTP_CLIENT.md.

Piece Role
HttpClient / DynHttpClient Async send contract (+ object-safe DI form)
ClientRequest / ClientResponse Outbound request and response
MockHttpClient / ReqwestHttpClient Tests / production (reqwest + rustls)
RegisterDefaultHttpClientPass DI: service http_client defaults to reqwest

FrameworkExtension installs the default HTTP client pass. See HTTP_CLIENT.md.

Lock

Named locks live in serenade-lock (#185). See LOCK.md.

Piece Role
LockStore / LockFactory / Lock Persist + acquire / release / refresh
InMemoryLockStore Process-local (DI default)
FilesystemLockStore / RedisLockStore Disk / Redis (feature redis)
RegisterDefaultLockPass DI: service lock.store defaults to in-memory

FrameworkExtension installs the default lock pass. See LOCK.md.

RateLimiter

Rate limiting lives in serenade-rate-limiter (#186). See RATE_LIMITER.md.

Piece Role
Policy / RateLimiterFactory / RateLimiter Token bucket or fixed window; consume / reset
InMemoryRateLimiterStorage Process-local (DI default)
RedisRateLimiterStorage (feature redis) Multi-node Redis windows
consume_or_exceed / too_many_requests App helper + HTTP 429 bridge
RegisterDefaultRateLimiterPass DI: service rate_limiter.storage defaults to in-memory

FrameworkExtension installs the default rate-limiter pass. See RATE_LIMITER.md.

Scheduler

Scheduling lives in serenade-scheduler (#187). See SCHEDULER.md.

Piece Role
Trigger / Schedule / Scheduler Interval or cron; tick without sleeping
ScheduleHandler / HandlerMap Sync dispatch for due jobs
CommandOnDue Optional Messenger bridge (feature messenger)
RegisterDefaultSchedulerPass DI: service scheduler defaults to empty
serenade:scheduler:run Console tick loop (--once for a single tick)

FrameworkExtension installs the default scheduler pass and console command. See SCHEDULER.md.

Observability (structured logging)

serenade-observability documents Monolog-like conventions on tracing: named channels (serenade::app, serenade::request, …), an app-owned var/log/{env}.log layout, and LoggingConfig / init for stderr + daily rolling files. Optional feature otel adds an OpenTelemetry tracing bridge and OTLP/HTTP export (OtelConfig, init_with_otel). Apps call init (or init_with_otel) from main; crates do not require a global logger. See OBSERVABILITY.md.

OpenAPI (explorers)

serenade-openapi provides Info/servers helpers and, behind feature actix, mounts Swagger UI, Redoc, RapiDoc, and Scalar beside Actix listen. Applications still own #[derive(OpenApi)] path and schema lists and the /openapi.json route. See OPENAPI.md.

The browser Web Debug Toolbar / Profiler lives in serenade-profiler (PROFILER.md): per-request collectors, HTML toolbar injection, and /_profiler/{token}. It is not the console. AsyncHttpKernel supports an async middleware pipeline (AsyncMiddleware) so Actix/listen apps can push the profiler the same way sync HttpKernel pushes Middleware.

Configuration layers

serenade-config loads TOML and YAML mappings, deep-merges them, interpolates ${VAR} / ${VAR:-default}, and flattens dotted keys into the DI ParameterBag.

Preference: new apps and Serenade examples use config/packages/*.toml. YAML remains supported for Symfony familiarity. There is no PHP/XML config track.

load_dotenv(project_root, APP_ENV)
    .env → .env.local (not in prod) → .env.{env} → .env.{env}.local
    (later files override earlier; process env never overwritten)
defaults (TOML or YAML)
    + config/packages/*.{toml,yaml,yml}  (sorted by file name; files only)
    + config/packages/{env}/*.{toml,yaml,yml}  (env overlay when the directory exists)
    + ${VAR} interpolation from the process environment

load_packages loads the base directory only. load_packages_for_env applies the overlay. build_container(packages_dir, environment, extensions) uses the env-aware loader. Apps should call load_dotenv on the project root before building the container when they ship .env files.

Mappings merge; scalars and sequences in an overlay replace the base value. Unset variables without a default fail load.

Console

The console component is Serenade’s bin/console analogue: discoverable commands, --env / --no-debug, kernel boot for ops (migrate, workers, debug). Plain commands use clap; interactive debug surfaces may use ratatui. See CONSOLE.md. Flex-like app scaffolding is separate (#30); Cargo remains the package manager.

Extension surface (kernel-level)

  • Tagged services (event subscriber, command handler, …)
  • Compiler passes / container extensions (bundle build() phase)
  • Middleware ordering
  • Route loaders from bundles

Domain-specific hooks (pricing, shipping) belong in product bundles, not the Serenade core.