Service Management ๐Ÿงฑยถ

This page describes how shared services are declared, discovered, ordered, loaded, and injected.

What Is a Service? ๐Ÿงฉยถ

A service is a reusable dependency that an application enables via services.<service_key>.

Examples:

  • web-svc-cdn provides the primary service key cdn

  • web-app-keycloak provides oidc

  • svc-db-mariadb provides mariadb

Each service entry lives in the provider roleโ€™s own meta/services.yml, where the file root IS the services map keyed by <entity_name> (no compose: and no services: wrapper). See layout.md for the full per-role meta layout.

Role-Local Service Metadata ๐Ÿท๏ธยถ

Service providers are self-describing. The provider role owns:

  • enabled

  • shared

  • optional provides

  • optional canonical

Example (file root of meta/services.yml):

keycloak:
  enabled: false
  shared: true
  provides: oidc

Canonical aliases are also role-local:

cdn:
  enabled: false
  shared: true
css:
  enabled: true
  shared: true
  canonical: cdn
javascript:
  enabled: true
  shared: true
  canonical: cdn

Rules:

  • The primary service entry is the role entity name returned by get_entity_name.

  • provides: is only used when the public service name differs from the entity name.

  • canonical: is only used on alias entries that resolve back to the primary service key.

  • frontend vs. backend is derived from the role name prefix, not stored in config.

Service Discovery ๐Ÿ”ยถ

Service discovery is built from role configs, not from a central registry file.

Primary implementation files:

The discovery layer:

  • scans role configs from roles/

  • discovers provider entries from services

  • derives deploy type and loader bucket from the role name

  • resolves provides: and canonical:

  • validates and applies run_after: from meta/services.yml.<primary_entity>

Load Order ๐Ÿงฎยถ

main.yml is the single loader entry point for all shared services.

It runs from 01_constructor.yml before the normal application stage.

Global bucket order:

  1. universal

  2. workstation

  3. server

  4. web-svc

  5. web-app

Within the same bucket, ordering is refined by run_after: declared on the provider roleโ€™s primary entity in meta/services.yml (i.e. services.<primary_entity>.run_after).

Rules:

  • run_after: entries are role names, not service keys.

  • Cross-type run_after: is invalid and fails hard.

  • Later-bucket dependencies are invalid and fail hard.

  • Cross-type service dependencies do not need run_after: because constructor-stage loading already brings backend services up before normal app deployment.

Loading vs Injection ๐Ÿ”€ยถ

Service loading and frontend injection are separate mechanisms.

Loading ๐Ÿ“ฅยถ

Loading decides whether the provider role is deployed at all.

main.yml:

  • queries the ordered discovered service list

  • checks lookup('service', service_key).required

  • skips roles already protected by run_once_*

  • loads services through load/app.yml

Frontend service probe/load helper:

Injection ๐Ÿ”Œยถ

Injection decides whether a deployed app gets extra nginx integration such as dashboard, logout, CSS, or JavaScript hooks.

This stays in:

Injection still reads the current appโ€™s services.<feature>.enabled flags. It does not load provider roles.

Lookup Plugins ๐Ÿ”Žยถ

service ๐Ÿงทยถ

File: service.py

Examples:

lookup('service', 'matomo')
lookup('service', 'sso')
lookup('service', 'web-svc-cdn')

Returns:

  • id

  • role

  • enabled

  • shared

  • required is True when some deployed app has the service with both enabled: true AND shared: true, directly or transitively through its own enabled service dependencies. This is the flag consumers should gate on when deciding whether to load, integrate with, or configure the service. A plain enabled flag is not enough on its own, because a service that is enabled but never shared with another app is not actually contractually required by anyone.

Behavior:

  • accepts either a service key or a provider role name

  • resolves aliases through canonical

  • resolves provider roles through discovered primary service keys

  • computes required transitively from enabled shared services

service_registry ๐Ÿ“šยถ

File: service_registry.py

Examples:

query('service_registry') | first
query('service_registry', 'ordered') | first

Modes:

  • default: full discovered registry mapping

  • ordered: ordered primary service entries for the service loader

applications_current_play ๐Ÿงญยถ

File: applications_current_play.py

Builds the current-play application set including:

  • group-selected roles

  • transitive shared service dependencies

  • meta dependencies

Database Services ๐Ÿ—„๏ธยถ

Relational databases are regular services:

  • svc-db-mariadb provides mariadb

  • svc-db-postgres provides postgres

Applications express database choice directly via the file root of meta/services.yml:

mariadb:
  enabled: true
  shared: true

or:

postgres:
  enabled: true
  shared: false

The lookup('database', ...) API is the convenience accessor for database connection values; it resolves the active direct database service from the role-local keys above.