Architecture
When to Use
Read this when you need to understand how the module's components fit together before writing code or configuring submodules.
Decision
| Question | Answer |
|---|---|
| How are providers discovered? | Symfony service collector tag: orchestration_services_provider — no plugin manager, no annotation scanning |
| What is the service UUID format? | {provider_id}::{service_id} — double colon, from Service::uuid() |
| Where are webhooks stored? | Drupal's KeyValue store, orchestration collection |
| Is the poll API stable? | No — PollEventBase and subclasses are marked @internal |
Pattern
External Platform (Activepieces, etc.)
| HTTP (Basic Auth)
v
Orchestration Core
├── Connect controller ← REST endpoints (/orchestration/*)
├── ServicesProviderManager ← collects all registered providers
└── Webhooks service ← KeyValue store + outbound HTTP dispatch
|
v
Services Providers (one per submodule)
├── orchestration_eca → ECA models (Tool event subscribers)
├── orchestration_ai_agents → AI Agent entities
├── orchestration_ai_function → AI FunctionCall plugins
└── orchestration_tool → Tool API plugins
Provider registration (Symfony service collector pattern):
# orchestration.services.yml
orchestration.services_manager:
class: Drupal\orchestration\ServicesProviderManager
tags:
- { name: 'service_collector', tag: 'orchestration_services_provider', call: 'addServicesProvider' }
Key classes:
| Class | Role |
|---|---|
Connect |
Handles all five REST routes; parses JSON bodies |
ServicesProviderManager |
Aggregates getAll() across providers; delegates executeService() to matching provider |
Webhooks |
Persists webhook config in KeyValue; dispatches outbound HTTP via Guzzle |
Service |
Value object: provider ref + id + label + description + ServiceConfig list; JsonSerializable |
ServiceConfig |
Value object: key, label, description, required, type, isEditable, defaultValue, weight, constraints |
ServicesProviderInterface |
Contract: getId(), getAll(), execute() |
Bidirectional communication:
- Inbound (external → Drupal): External platform calls
/orchestration/service/execute; manager finds the matching provider and callsexecute() - Outbound (Drupal → external): ECA model fires
orchestration_dispatch_webhookaction →Webhooks::dispatch()POSTs to registered URL - Poll (external pulls):
/orchestration/polldispatches aPollEventTimestamporPollEventIdSymfony event; ECA poll action plugins collect matching items
Common Mistakes
- Wrong: Looking for a plugin annotation system → Right: Providers register as tagged Symfony services; no
@Pluginannotation - Wrong: Adding providers after the first
getAllServices()call and expecting them to appear → Right: The manager caches$serviceson first call; re-registration requires a cache rebuild - Wrong: Treating
PollEventBasesubclasses as stable API → Right: They are@internaland may change on minor versions
See Also
- Custom Services Provider
- Orchestration API Reference
- Reference:
orchestration.services.yml,orchestration.routing.yml