Service Orchestration in Aletyx Enterprise Build of Kogito and Drools 10.1.0-aletyx¶
Service orchestration coordinates interactions between multiple services to accomplish complex business objectives. Aletyx Enterprise Build of Kogito and Drools provides powerful orchestration capabilities through BPMN processes, enabling you to design, execute, and monitor multi-service workflows with visual process models, automated service invocations, and sophisticated error handling.
Understanding Service Orchestration¶
Service orchestration differs from service choreography by providing centralized coordination. An orchestrator process:
- Defines the sequence of service calls explicitly
- Manages state throughout the workflow
- Handles errors and compensation centrally
- Provides visibility into workflow progress
- Enforces business rules and conditions
Orchestration vs Choreography:
graph TB
subgraph Orchestration
O[Orchestrator Process] --> S1[Service A]
O --> S2[Service B]
O --> S3[Service C]
end
subgraph Choreography
E1[Service A] -->|Event| E2[Service B]
E2 -->|Event| E3[Service C]
E3 -->|Event| E1
end
When to use orchestration:
- Complex workflows with many conditional paths
- Transactions requiring compensation patterns
- Workflows needing human intervention
- Processes requiring audit trails and monitoring
- Long-running business processes
When to use choreography:
- Loosely coupled event-driven systems
- High scalability requirements
- Services owned by different teams
- Simple event notification patterns
Service Tasks in BPMN¶
The Service Task is the primary mechanism for invoking external services within BPMN processes.
Service Task Overview¶
Service Tasks enable:
- REST API invocation: Call HTTP endpoints with custom headers and payloads
- Java service calls: Invoke local Java methods directly
- Custom work item handlers: Extend with specialized integration logic
- Asynchronous execution: Execute long-running operations without blocking
- Data transformation: Map process variables to service inputs and outputs
See BPMN Activities for detailed Service Task configuration.
Configuring REST Service Tasks¶
Example: Credit Bureau Integration
Configure a Service Task to call an external credit bureau API:
Process diagram:
graph LR
A[Start] --> B[Validate Application]
B --> C[Service Task:<br/>Get Credit Score]
C --> D{Score >= 700?}
D -->|Yes| E[Approve]
D -->|No| F[Reject]
E --> G[End]
F --> G
Service Task configuration:
- Task Type: Service Task
- Implementation: REST
- URL:
https://credit-bureau.example.com/api/v1/scores - Method: POST
- Content Type: application/json
Input mapping:
{
"applicantId": "#{application.applicantId}",
"ssn": "#{application.ssn}",
"requestDate": "#{now()}"
}
Output mapping:
Map the response to process variables:
- Response field
score→ Process variablecreditScore - Response field
reportId→ Process variablecreditReportId
Example REST call:
curl -X POST https://credit-bureau.example.com/api/v1/scores \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${API_TOKEN}" \
-d '{
"applicantId": "A001",
"ssn": "123-45-6789",
"requestDate": "2025-01-31"
}'
Response:
{
"score": 745,
"reportId": "CR-98765",
"factors": ["on-time-payments", "low-utilization"],
"timestamp": "2025-01-31T10:30:00Z"
}
Java Service Task Implementation¶
Custom Java service class:
package org.example.services;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class CreditService {
public CreditScore getCreditScore(String applicantId, String ssn) {
// Call external API or perform calculation
CreditScore score = externalCreditBureau.requestScore(ssn);
// Log for audit
auditLog.record(applicantId, "Credit score requested", score);
return score;
}
}
Service Task configuration in BPMN:
Configure the task to call the Java method:
- Implementation: Java
- Interface:
org.example.services.CreditService - Operation:
getCreditScore - Parameters: Map process variables to method parameters
Input mapping:
- Process variable
applicantId→ ParameterapplicantId - Process variable
ssn→ Parameterssn
Output mapping:
- Method return value → Process variable
creditScore
Asynchronous Service Tasks¶
For long-running operations, configure Service Tasks to execute asynchronously.
Benefits:
- Non-blocking: Process engine remains responsive
- Scalability: Distribute work across job executors
- Resilience: Automatic retry on transient failures
- Resource management: Control concurrent task execution
Configuration:
- Select the Service Task
- Enable Is Async property
- Configure retry parameters (optional)
Process behavior with async:
sequenceDiagram
participant Process as Process Engine
participant JobService as Job Service
participant ExtAPI as External Service
Process->>JobService: Queue: CreditCheck Task
Process->>Process: Continue to next safe point
JobService->>ExtAPI: Invoke: Credit Bureau API
ExtAPI->>JobService: Return: Credit Score
JobService->>Process: Signal: Task Complete
Process->>Process: Resume process execution
Job Service configuration (application.properties):
# Enable job service
quarkus.kogito.jobs-service.enabled=true
# Job service URL
kogito.jobs-service.url=http://localhost:8085
# Retry configuration
kogito.service-task.retry.maxAttempts=3
kogito.service-task.retry.delay=5s
Orchestration Patterns¶
Sequential Service Orchestration¶
Call services in a defined sequence where each service depends on previous results.
Use case: Loan Approval Workflow
graph LR
A[Start] --> B[Validate Data]
B --> C[Check Credit Score]
C --> D[Verify Employment]
D --> E[Calculate DTI Ratio]
E --> F[Determine Eligibility]
F --> G[End]
Implementation:
Each Service Task executes sequentially, passing data through process variables.
Example process variables flow:
application(input) → Validate Data →validationResultvalidationResult→ Check Credit Score →creditScorecreditScore→ Verify Employment →employmentVerifiedcreditScore,employmentVerified→ Calculate DTI →dtiRatiodtiRatio,creditScore→ Determine Eligibility →approved
Parallel Service Orchestration¶
Invoke multiple independent services concurrently for performance.
Use case: Multi-Bureau Credit Check
graph TB
A[Start] --> B{Parallel Gateway}
B --> C[Experian API]
B --> D[Equifax API]
B --> E[TransUnion API]
C --> F{Join Gateway}
D --> F
E --> F
F --> G[Aggregate Scores]
G --> H[End]
Implementation:
- Parallel Gateway (fork): Splits process into parallel branches
- Service Tasks: Execute concurrently on separate threads
- Parallel Gateway (join): Waits for all branches to complete
- Aggregation Task: Combines results from parallel services
Performance benefit:
- Sequential: 3 services × 2 seconds = 6 seconds
- Parallel: Max(2, 2, 2) = 2 seconds
Conditional Service Routing¶
Route to different services based on runtime conditions.
Use case: Product-Specific Pricing
graph TD
A[Start] --> B{Product Type?}
B -->|Mortgage| C[Mortgage Pricing Service]
B -->|Auto Loan| D[Auto Loan Pricing Service]
B -->|Personal Loan| E[Personal Loan Pricing Service]
C --> F[Generate Quote]
D --> F
E --> F
F --> G[End]
Implementation:
- Exclusive Gateway: Evaluates condition based on process variable
- Conditional flows: Each path has a condition expression
- Service Tasks: Different services on each path
- Converging Gateway: Paths rejoin after service execution
Gateway conditions:
- Path to Mortgage:
#{application.productType == 'MORTGAGE'} - Path to Auto Loan:
#{application.productType == 'AUTO_LOAN'} - Path to Personal Loan:
#{application.productType == 'PERSONAL_LOAN'}
Error Handling and Compensation¶
Handle service failures gracefully with error boundary events.
Pattern: Service with Fallback
graph TB
A[Start] --> B[Primary Service]
B --> C[End]
B -.->|Error| D[Error Boundary Event]
D --> E[Fallback Service]
E --> C
Implementation:
- Attach Error Boundary Event to Service Task
- Configure error code to catch (e.g., HTTP 500, timeout)
- Define error handling path
- Optional: retry logic before fallback
Example boundary event configuration:
- Error Code:
500 - Error Name:
ServiceUnavailable - Handler Path: Route to fallback service or compensation
Compensation pattern:
graph LR
A[Reserve Inventory] --> B[Charge Payment]
B -.->|Error| C[Compensation: Release Inventory]
C --> D[Cancel Order]
Saga Pattern with Orchestration¶
Implement distributed transactions using compensation.
Use case: Order Fulfillment Saga
graph TD
A[Start Order] --> B[Reserve Inventory]
B --> C[Charge Payment]
C --> D[Ship Order]
D --> E[End: Success]
B -.->|Fail| F[Cancel Order]
C -.->|Fail| G[Release Inventory]
D -.->|Fail| H[Refund Payment]
F --> I[End: Cancelled]
G --> I
H --> I
Implementation steps:
- Forward path: Define normal execution sequence
- Compensation handlers: Attach to each Service Task
- Error boundaries: Catch failures and trigger compensation
- Compensation logic: Undo completed steps in reverse order
Compensation example:
If payment fails after inventory reserved:
- Error boundary event catches payment failure
- Process triggers compensation for "Reserve Inventory" task
- Compensation calls "Release Inventory" service
- Process transitions to cancellation path
Data Transformation and Mapping¶
Input Mapping¶
Transform process variables into service request format.
Expression examples:
// Direct variable mapping
#{applicantId}
// Object property access
#{application.income}
// Collection operations
#{application.addresses[0].zipCode}
// Arithmetic operations
#{loanAmount * 0.1}
// String concatenation
#{'Applicant: ' + applicant.firstName + ' ' + applicant.lastName}
// Conditional expressions
#{creditScore >= 700 ? 'PRIME' : 'SUBPRIME'}
// Date formatting
#{now().format('yyyy-MM-dd')}
Complex JSON construction:
{
"requestId": "#{processInstanceId}",
"timestamp": "#{now()}",
"applicant": {
"id": "#{application.applicantId}",
"name": "#{application.firstName} #{application.lastName}",
"income": #{application.income},
"employmentYears": #{application.employmentYears}
},
"loan": {
"amount": #{application.requestedAmount},
"term": #{application.termMonths},
"purpose": "#{application.purpose}"
}
}
Output Mapping¶
Extract data from service responses and store in process variables.
JSON Path extraction:
$.score→ Extractscorefield from root$.applicant.creditHistory.accounts→ Extract nested array$..transactionId→ Extract alltransactionIdfields (recursive)
Mapping strategies:
- Direct mapping: Response field → Process variable
- Transformation: Apply expression to response data
- Conditional mapping: Map different fields based on conditions
- Collection mapping: Iterate over response arrays
Example output mapping:
Service returns:
{
"scoreResult": {
"score": 745,
"tier": "PRIME",
"factors": ["on-time-payments", "low-utilization"]
},
"reportId": "CR-98765"
}
Mappings:
$.scoreResult.score→creditScore(Integer)$.scoreResult.tier→creditTier(String)$.reportId→creditReportId(String)
Data Validation¶
Validate service responses before continuing process execution.
Validation patterns:
- Gateway after service: Check response validity
- Script task: Perform complex validation logic
- Business rule task: Validate using decision rules
- Boundary error event: Handle invalid responses
Example validation gateway:
If validation fails, route to error handling path.
Service Registry and Discovery¶
Static Service Configuration¶
Configure service endpoints in application properties.
application.properties:
# Service endpoints
services.credit-bureau.url=https://credit-bureau.example.com/api/v1
services.fraud-detection.url=https://fraud-detection.example.com/api/v2
services.notification.url=https://notification.example.com/api/v1
# Service credentials
services.credit-bureau.apiKey=${CREDIT_BUREAU_API_KEY}
services.fraud-detection.apiKey=${FRAUD_DETECTION_API_KEY}
# Timeout configuration
services.default.timeout=30s
services.credit-bureau.timeout=60s
Reference in Service Task:
Dynamic Service Discovery¶
Integrate with service registries for dynamic endpoint resolution.
Kubernetes service discovery:
apiVersion: v1
kind: Service
metadata:
name: credit-bureau-service
spec:
selector:
app: credit-bureau
ports:
- port: 8080
targetPort: 8080
Service Task URL:
Kubernetes DNS resolves service name to active pod IPs automatically.
Monitoring and Observability¶
Process Instance Tracking¶
Monitor orchestration execution in real-time.
Key metrics:
- Process duration: Time from start to completion
- Service task latency: Time per service invocation
- Active instances: Currently executing processes
- Completion rate: Successful vs failed instances
- Bottleneck identification: Tasks with longest wait times
Process dashboard queries:
# Average process duration
avg(process_instance_duration_seconds{process_id="loan-approval"})
# Service task success rate
sum(rate(service_task_completed_total{status="success"}[5m]))
/ sum(rate(service_task_completed_total[5m]))
# Current active instances
process_instance_active{process_id="loan-approval"}
Distributed Tracing¶
Trace requests across service boundaries.
OpenTelemetry integration:
@ApplicationScoped
public class TracedCreditService {
@Inject
Tracer tracer;
public CreditScore getCreditScore(String applicantId) {
Span span = tracer.spanBuilder("getCreditScore")
.setAttribute("applicant.id", applicantId)
.startSpan();
try {
CreditScore score = callExternalService(applicantId);
span.setAttribute("credit.score", score.getValue());
return score;
} catch (Exception e) {
span.recordException(e);
throw e;
} finally {
span.end();
}
}
}
Trace visualization:
ProcessStart → ValidateData → GetCreditScore → VerifyEmployment → CalculateDTI → Approve
↓
ExternalCreditBureau
(2.3s)
Error Logging and Alerting¶
Capture and alert on orchestration failures.
Structured logging:
@ApplicationScoped
public class ServiceTaskLogger {
Logger log = Logger.getLogger(ServiceTaskLogger.class);
public void logServiceInvocation(String taskName, String processInstanceId) {
log.info("Service task executed",
StructuredArguments.kv("task", taskName),
StructuredArguments.kv("processInstanceId", processInstanceId),
StructuredArguments.kv("timestamp", Instant.now())
);
}
public void logServiceError(String taskName, Exception error) {
log.error("Service task failed",
StructuredArguments.kv("task", taskName),
StructuredArguments.kv("errorType", error.getClass().getSimpleName()),
StructuredArguments.kv("errorMessage", error.getMessage())
);
}
}
Alerting rules (Prometheus):
groups:
- name: service_orchestration
rules:
- alert: HighServiceTaskFailureRate
expr: |
rate(service_task_failed_total[5m]) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "High service task failure rate detected"
Best Practices¶
Process Design¶
- Keep processes focused: One process per business capability
- Limit process complexity: Break large processes into sub-processes
- Handle errors explicitly: Define error paths for all service tasks
- Use meaningful names: Clear task and variable names
- Document process logic: Add annotations explaining business rules
Service Integration¶
- Design for failure: Assume services will fail
- Implement timeouts: Prevent indefinite waiting
- Use circuit breakers: Protect against cascading failures
- Validate responses: Check service outputs before continuing
- Version service contracts: Handle backward compatibility
Performance Optimization¶
- Parallelize independent tasks: Use parallel gateways
- Minimize data transfer: Only pass necessary variables
- Use async for long operations: Prevent process engine blocking
- Cache frequent lookups: Store static reference data
- Monitor execution times: Identify slow services
Security Considerations¶
- Secure credentials: Use secrets management for API keys
- Encrypt sensitive data: Protect PII in process variables
- Validate inputs: Sanitize data before service calls
- Audit service access: Log all external service invocations
- Use mutual TLS: Authenticate both client and server
Testing Strategy¶
- Unit test service adapters: Test service integration code
- Mock external services: Use WireMock or similar for testing
- Integration test processes: Validate full orchestration flows
- Performance test under load: Verify scalability
- Chaos test error handling: Simulate service failures
Deployment Architectures¶
Embedded Process Engine¶
Deploy process engine within the application.
Characteristics:
- Single deployable unit: Application and engine together
- Low latency: No network calls to separate engine
- Simple deployment: One container/application
- Resource sharing: Process engine shares application resources
Use cases:
- Microservices with embedded workflows
- Lightweight orchestration needs
- Fast startup requirements
Centralized Process Engine¶
Deploy process engine as separate service.
Characteristics:
- Multiple applications: Many apps interact with one engine
- Centralized monitoring: Single view of all processes
- Shared infrastructure: Engine managed independently
- Horizontal scaling: Scale engine separately
Use cases:
- Enterprise-wide orchestration
- Complex governance requirements
- Multiple applications sharing workflows
Hybrid Architecture¶
Combine embedded and centralized approaches.
Strategy:
- Embedded engines for domain-specific workflows
- Centralized engine for cross-domain orchestration
- Event-based integration between engines
Benefits:
- Autonomy: Teams control their workflows
- Coordination: Central orchestration when needed
- Scalability: Scale components independently
Next Steps¶
- Learn BPMN Basics: BPMN Activities
- Explore Advanced BPMN: Advanced BPMN Patterns
- Event Integration: Event-Driven Integration
- Decision Orchestration: Decision Process Integration
- Get Support: Contact Aletyx for orchestration design assistance
