Operations: HTTPS, isolates, profiles¶
HTTPS¶
Configure certificate and key paths; the server switches to TLS:
Both paths are required together (startup error otherwise). The startup log
switches to https://. Terminating TLS at a reverse proxy and serving
plain HTTP behind it is equally supported — just omit server.ssl.*.
Multi-isolate serving (experimental)¶
runDiana() (through DianaRuntime.run) spawns n - 1 worker isolates.
Each worker runs the full bootstrap — its own DianaContext, its own
initialize() — and binds the same port with shared sockets; the OS
distributes connections. The main isolate owns the signal handlers and
coordinates worker shutdown (workers close and dispose before the main
isolate does). Worker failure is contained: a worker whose dispose throws
is reported on stderr (shutdown still completes), a worker that hangs is
killed after DianaRuntime.workerShutdownTimeout (default 15s), and a
worker isolate that dies from an uncaught error is logged and skipped by
the event relay and by shutdown.
Startup is coordinated and fail-closed: workers bootstrap and run
initialize() when spawned, but no isolate accepts connections until every
worker exists and the main isolate has initialized — early traffic can
neither reach an isolate whose @PostConstruct hasn't run nor trigger a
broadcast that misses a not-yet-spawned sibling. If any isolate fails to
start (or the main initialize() throws), the already-spawned workers are
shut down before the error propagates — a failed startup never leaves
orphan isolates serving.
Constraints:
- Everything captured by the bootstrap must be sendable across isolates.
The generated bootstrap captures only
DianaConfig(immutable primitives) and theoverridesvalue —const DianaOverrides()is fine, live mock instances generally are not. - State is per isolate: in-memory repositories and caches are independent copies. Share state through external systems.
@Scheduledtasks run once per process: only the main isolate starts them, so schedules never multiply withserver.isolates. A task that genuinely needs to run in every isolate (e.g. refreshing an isolate-local cache) opts in with@Scheduled(perIsolate: true). See Observability.- Application events bridge isolates:
publisher.broadcast(event)delivers the event to every isolate exactly once (main relays worker broadcasts to the siblings). See Application events. - Multi-process coordination (several replicas behind a load balancer)
is out of scope today — schedules still run once per replica; a
distributed-lock option (ShedLock-style, via
diana_data) is on the roadmap. - Labeled experimental;
server.isolates: 1(the default) uses none of the isolate machinery.
Scheduled tasks¶
Annotate a no-argument void/Future<void> method on a singleton provider
with @Scheduled. Pick exactly one trigger:
@Component()
class Jobs {
@Scheduled(fixedDelay: 30000) // 30s after each run completes
Future<void> poll() async { ... }
@Scheduled(cron: '0 0 3 * * MON-FRI') // 03:00 on weekdays
Future<void> nightlyReport() async { ... }
@Scheduled(cron: '@hourly') // top of every hour
Future<void> sweep() async { ... }
}
The cron expression has six fields — second minute hour day-of-month
month day-of-week (Spring style) — and supports ranges (9-17), lists
(1,15), steps (*/15), three-letter month/day names, and the macros
@yearly/@monthly/@weekly/@daily/@hourly. The next run is computed
after the previous completes, so a slow task never overlaps itself.
Time zone. Cron expressions are evaluated in the process's local
time. Diana has no per-expression zone; pin the schedule by running the
process with a fixed zone, e.g. TZ=UTC dart run bin/server.dart (or set
TZ in the container). A malformed field's shape is a build error; an
out-of-range value surfaces as a FormatException at startup.
Profiles¶
Spring-style configuration profiles:
application.yaml # base
application-dev.yaml # overlay when 'dev' is active
application-prod.yaml # overlay when 'prod' is active
Activate via the DIANA_PROFILE environment variable (comma-separated,
later profiles win) or the diana.profile key in the base file —
DIANA_PROFILE replaces the key entirely when set:
Precedence (lowest → highest): base file → profile overlays in listed
order → environment variables. A profile without a file is skipped.
config.activeProfiles exposes the active list at runtime.
Deployment checklist¶
- Configuration is env-overridable by design:
server.port←SERVER_PORT, etc. — see the config keys reference. - Missing required keys (
@Valuewithout default, non-nullable@ConfigurationProperties) fail at startup. - SIGTERM triggers graceful shutdown (drain →
@PreDestroy) — Kubernetes default termination works out of the box. /actuator/health(whenmanagement.enabled) returns 503 while anyHealthIndicatoris down — wire it to readiness probes.