Skip to content

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
┌──────────────────────────────────────────────────────────────────┐
│                                                                  │
│  Drupal 11.x + PHP 8.4 + MariaDB                                 │
│  Install profile: `fedms`                                        │
│                                                                  │
│  ┌─ Structural / data ─────────────────────────────────┐         │
│  │ fedms_federation  groups (org/event/session)        │         │
│  │ fedms_content     fedms_event / fedms_session       │         │
│  │ fedms_attachment  fedms_attachment                  │         │
│  │ fedms_task        fedms_task + fedms_task_pattern   │         │
│  └─────────────────────────────────────────────────────┘         │
│                                                                  │
│  ┌─ Business logic ────────────────────────────────────┐         │
│  │ fedms_eca         10 ECA models                     │         │
│  └─────────────────────────────────────────────────────┘         │
│                                                                  │
│  ┌─ I18n ──────────────────────────────────────────────┐         │
│  │ fedms_locale      de + extract/import tooling       │         │
│  └─────────────────────────────────────────────────────┘         │
│                                                                  │
│  ┌─ Operations ────────────────────────────────────────┐         │
│  │ vendor/fedms/scripts   install.sh / config.sh / ahoy│         │
│  │ recipes/                site-level optional features│         │
│  └─────────────────────────────────────────────────────┘         │
│                                                                  │
│  ┌─ Tests ─────────────────────────────────────────────┐         │
│  │ tests/playwright    e2e (active)                    │         │
│  │ tests/cypress       e2e (deprecated)                │         │
│  │ tests/phpunit.xml.dist  unit/kernel/functional      │         │
│  │ tests/phpcs.files       coding standards            │         │
│  │ tests/phpstan.neon      static analysis             │         │
│  └─────────────────────────────────────────────────────┘         │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

Layering principle

FedMS is layered:

  1. Structural layerfedms_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.
  2. Business-logic layerfedms_eca. This module contains the rules that turn the data model into a workflow. It listens to events on the structural layer and reacts.
  3. I18n layerfedms_locale. Provides translations.
  4. 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
org ─→ event ─→ session

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_cache actions.

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 spawningtask_api walks 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