Service Collector Pattern
When to Use
Use Service Collector for REST API-first external integrations with stateless services. Use Provider Plugin for internal Drupal service abstraction. Use Foundation+Extension when mature plugin ecosystem exists.
Decision
| Situation | Choose | Why |
|---|---|---|
| Stateless service aggregation | Service Collector | Minimal coordination, no shared state |
| REST API-first architecture | Service Collector | Built-in REST endpoints, JSON serialization |
| Webhook-driven workflows | Service Collector | Persistent callback storage via KeyValue |
| Polling-based event systems | Service Collector | Timestamp or ID-based polling events |
| Minimal interface (3 methods) | Service Collector | Simple contract, no annotation overhead |
| Self-describing configuration | Service Collector | Constraint-driven UI metadata auto-generation |
| Need plugin alterations | Provider Plugin | Service Collector doesn't support hook_plugin_info_alter |
| Multiple configured instances | Provider Plugin | Service Collector services are singletons |
Pattern
ServicesProviderInterface - Minimal 3-method contract:
interface ServicesProviderInterface {
public function getId(): string;
public function getAll(): array; // Returns Service[]
public function execute(Service $service, array $config): array|string;
}
Service Registration:
# Main module service collector
services:
orchestration.services_manager:
class: Drupal\orchestration\ServicesProviderManager
tags:
- { name: 'service_collector', tag: 'orchestration_services_provider', call: 'addServicesProvider' }
# Provider module tagged service
orchestration_eca.services_provider:
class: Drupal\orchestration_eca\ServicesProvider
tags:
- { name: 'orchestration_services_provider' }
Lightweight DTOs with JSON Serialization:
// Service with configuration metadata
$service = new Service($provider, 'webhook_dispatcher', 'Dispatch Webhook', 'Send data to webhook');
$service->addConfig(new ServiceConfig(
key: 'webhook_id',
label: 'Webhook',
description: 'Select registered webhook',
required: true,
type: 'string',
constraints: [
'Choice' => [
'choices' => ['webhook1' => 'Webhook 1', 'webhook2' => 'Webhook 2']
]
]
));
// Auto-generates: {options: [{key: 'webhook1', name: 'Webhook 1'}, ...]}
REST API Endpoints:
- GET /orchestration/services - List all services with config metadata
- POST /orchestration/service/execute - Execute service with configuration
- POST /orchestration/webhook/register - Register webhook callback
- POST /orchestration/poll - Poll for events (timestamp or ID-based)
Common Mistakes
- Wrong: Using Service Collector when plugin alterations needed → Right: Use Provider Plugin for flexibility
- Wrong: Using Service Collector for internal Drupal services → Right: Use Provider Plugin for internal abstraction
- Wrong: Complex plugin discovery when simple tagging suffices → Right: Tagged services auto-discovered during container compilation
- Wrong: Building custom REST API when Service Collector provides it → Right: Leverage built-in REST endpoints
See Also
- Provider Plugin Pattern
- Plugin Manager Implementation
- Service Integration Patterns
- Reference:
/web/modules/contrib/orchestration/ - Reference: Service Tags
- Reference: Service Collector Deep Dive
- Reference: RESTful Web Services API