Architecture¶
This page is the high-level map of the FedMS framework. It tells you what the moving parts are, what role each one plays, and where the boundaries lie.
The 30-second version¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | |
Layering principle¶
FedMS is layered:
- Structural layer —
fedms_federation,fedms_content,fedms_attachment,fedms_task. These modules define the data model — entities, fields, base fields, relationships. They contain little business logic; they describe what exists. - Business-logic layer —
fedms_eca. This module contains the rules that turn the data model into a workflow. It listens to events on the structural layer and reacts. - I18n layer —
fedms_locale. Provides translations. - Operations layer — scripts and recipes. Used at install / deploy time, not at request time.
A change at the structural layer often needs corresponding work at the business-logic layer. A change at the business-logic layer should rarely require a structural change.
The two entity worlds¶
A key architectural decision is the use of two parallel entity types for orgs, events, and sessions:
| World | Entity type | Owner module | Used for |
|---|---|---|---|
| Group | group (bundles org, event, session) |
fedms_federation |
Structure, hierarchy, membership, federation, permissions |
| Content | fedms_event, fedms_session |
fedms_content |
Editorial body, paragraphs, view modes, translations |
The two worlds are linked but distinct. Code that walks both worlds should be explicit about which one it is querying.
See Entities for the deep dive.
Sub-grouping¶
The hierarchy:
1 | |
is implemented with the Drupal subgroup module, which gives
each level a subgroup_handler that knows its parent. FedMS uses
this in:
- The federation query alter (
queryEntityReferenceAlter) for session-member restrictions. - Pathauto patterns for hierarchical URLs.
- Cron-driven views that walk the hierarchy.
ECA-first business logic¶
A central architectural commitment: business logic lives in ECA models, not in PHP code, whenever possible. Reasons:
- ECA models are inspectable in the modeler UI.
- ECA models are config — they version, sync, and deploy like any other config.
- ECA models are extensible by customers without code changes.
- ECA models keep the code base focused on plumbing rather than on business rules.
The exceptions to ECA-first are:
- Query alters — sometimes the right place is a database query alter, not an ECA action. The federation hook is the canonical example.
- Performance-critical paths — where ECA's overhead is unacceptable.
- Custom ECA plugins — when an action or condition doesn't yet exist, you implement it in PHP and expose it to ECA.
Federation as a separable layer¶
The federation behavior — how instances exchange data with each
other — is conceptually a separable layer. Currently it is part
of fedms_federation, but it is architected so it could be split
into its own module if needed.
The structural foundations (group hierarchy, field_gitlab_id,
field_sync, the status base field on group_relationship) are in
place. The transport layer is still being designed.
Themes and the front-end¶
FedMS ships:
- Gin — the admin theme. Modern, accessible, sub-themable.
- Olivero — the default front-end theme. Customers typically sub-theme from this.
The fedms_federation module declares the
gin_content_form_routes hook to register group forms with the Gin
theme — this is how group add/edit forms get the Gin admin layout.
Backend services and DI¶
FedMS uses constructor dependency injection throughout. New services should:
- Be defined in the module's
<module>.services.yml. - Use type-hinted constructor parameter injection (Drupal 11 OOP hook style).
- Be tagged appropriately (
@event_subscriber,@logger.channel.<id>, etc.).
See the drupal-module-development skill (for AI agents) and the
existing services in fedms_task/fedms_task.services.yml and
fedms_federation/fedms_federation.services.yml for examples.
Caching¶
Standard Drupal caching applies. Specifically:
- Page cache for anonymous users.
- Dynamic page cache and BigPipe for authenticated users.
- Render cache invalidation through cache tags.
- Entity cache tags are emitted by all FedMS entities; ECA
models can invalidate them via the
eca_cacheactions.
The biggest caching pitfall in FedMS is forgetting to add the
right cache contexts to a render array — e.g. rendering a list
based on the current user's group memberships without user.groups
context.
Concurrency and locking¶
FedMS doesn't have many long-running operations. The places where locking matters:
- Task spawning —
task_apiwalks patterns and creates tasks. Concurrent spawning can produce duplicates. The pattern uses an open-task lookup before creation to guard against this. - Cron jobs — the standard Drupal cron lock applies.
- Membership soft-delete / reactivate — relies on atomic
updates to
group_relationship.status.
For new long-running operations, use the Lock: acquire ECA action
or the standard lock.persistent Drupal service.
Where to extend¶
If you need to add a feature:
| Need | Where |
|---|---|
| New entity type | New module, or extension of fedms_content if it's an event/session bundle. |
| New field on existing entity | The module that owns the entity, via config/install/ or config/optional/. |
| New business rule | An ECA model (new or existing). |
| New ECA action / condition / event plugin | The module that owns the affected domain. |
| New recipe (optional feature) | New repo under fedms/components/recipes/<name>. |
| Custom site-specific code | Site-specific module, not a fedms_* module. |
Next steps¶
- Repository layout — where the code lives.
- Modules — the seven components in detail.
- ECA models — the business logic.