a9cd7750f4
CI / unit-test (push) Has been cancelled
CI / detect-changes (push) Has been cancelled
CI / build (push) Has been cancelled
Publish docs via GitHub Pages / Deploy docs (push) Has been cancelled
CI / test-harness (push) Has been cancelled
CI / generate-e2e-matrix (push) Has been cancelled
CI / e2e (push) Has been cancelled
CI / build-ui (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
UI v2 Integration CI / E2E (Integration) (push) Has been cancelled
UI v2 CI / Lint, Format & Test (push) Has been cancelled
UI v2 CI / E2E (Mocked) (push) Has been cancelled
257 lines
7.2 KiB
Markdown
257 lines
7.2 KiB
Markdown
---
|
|
description: "Conductor cookbook — microservice orchestration recipes with HTTP service chains, conditional branching, and parallel HTTP calls using Fork/Join."
|
|
---
|
|
|
|
# Microservice orchestration
|
|
|
|
### HTTP service chain
|
|
|
|
A common pattern: call a series of HTTP endpoints where each step uses output from the previous one. No custom workers needed — Conductor handles it with built-in HTTP tasks.
|
|
|
|
```json
|
|
{
|
|
"name": "order_processing",
|
|
"description": "Validate order, charge payment, reserve inventory, send confirmation",
|
|
"version": 1,
|
|
"schemaVersion": 2,
|
|
"inputParameters": ["orderId", "customerId", "amount", "items"],
|
|
"tasks": [
|
|
{
|
|
"name": "validate_order",
|
|
"taskReferenceName": "validate",
|
|
"type": "HTTP",
|
|
"inputParameters": {
|
|
"http_request": {
|
|
"uri": "https://api.example.com/orders/${workflow.input.orderId}/validate",
|
|
"method": "POST",
|
|
"body": {
|
|
"customerId": "${workflow.input.customerId}",
|
|
"items": "${workflow.input.items}"
|
|
},
|
|
"connectionTimeOut": 5000,
|
|
"readTimeOut": 5000
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "charge_payment",
|
|
"taskReferenceName": "payment",
|
|
"type": "HTTP",
|
|
"inputParameters": {
|
|
"http_request": {
|
|
"uri": "https://api.example.com/payments/charge",
|
|
"method": "POST",
|
|
"body": {
|
|
"orderId": "${workflow.input.orderId}",
|
|
"amount": "${workflow.input.amount}",
|
|
"customerId": "${workflow.input.customerId}"
|
|
},
|
|
"connectionTimeOut": 10000,
|
|
"readTimeOut": 10000
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "reserve_inventory",
|
|
"taskReferenceName": "inventory",
|
|
"type": "HTTP",
|
|
"inputParameters": {
|
|
"http_request": {
|
|
"uri": "https://api.example.com/inventory/reserve",
|
|
"method": "POST",
|
|
"body": {
|
|
"orderId": "${workflow.input.orderId}",
|
|
"items": "${workflow.input.items}",
|
|
"paymentId": "${payment.output.response.body.paymentId}"
|
|
},
|
|
"connectionTimeOut": 5000,
|
|
"readTimeOut": 5000
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "send_confirmation",
|
|
"taskReferenceName": "notify",
|
|
"type": "HTTP",
|
|
"inputParameters": {
|
|
"http_request": {
|
|
"uri": "https://api.example.com/notifications/send",
|
|
"method": "POST",
|
|
"body": {
|
|
"customerId": "${workflow.input.customerId}",
|
|
"orderId": "${workflow.input.orderId}",
|
|
"paymentId": "${payment.output.response.body.paymentId}",
|
|
"reservationId": "${inventory.output.response.body.reservationId}"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"outputParameters": {
|
|
"paymentId": "${payment.output.response.body.paymentId}",
|
|
"reservationId": "${inventory.output.response.body.reservationId}"
|
|
},
|
|
"failureWorkflow": "order_compensation",
|
|
"timeoutPolicy": "TIME_OUT_WF",
|
|
"timeoutSeconds": 120
|
|
}
|
|
```
|
|
|
|
Each task passes data forward using `${taskReferenceName.output.response.body.field}` expressions. If any step fails, Conductor retries it (configurable) and can trigger the `failureWorkflow` for compensation.
|
|
|
|
**Register and run:**
|
|
|
|
```shell
|
|
curl -X POST 'http://localhost:8080/api/metadata/workflow' \
|
|
-H 'Content-Type: application/json' \
|
|
-d @order_processing.json
|
|
|
|
curl -X POST 'http://localhost:8080/api/workflow/order_processing' \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"orderId": "ORD-123", "customerId": "CUST-456", "amount": 99.99, "items": ["SKU-A", "SKU-B"]}'
|
|
```
|
|
|
|
---
|
|
|
|
### HTTP with conditional branching
|
|
|
|
Use a SWITCH operator to route workflow execution based on a previous task's output.
|
|
|
|
```json
|
|
{
|
|
"name": "user_onboarding",
|
|
"version": 1,
|
|
"schemaVersion": 2,
|
|
"inputParameters": ["userId"],
|
|
"tasks": [
|
|
{
|
|
"name": "get_user_profile",
|
|
"taskReferenceName": "profile",
|
|
"type": "HTTP",
|
|
"inputParameters": {
|
|
"http_request": {
|
|
"uri": "https://api.example.com/users/${workflow.input.userId}",
|
|
"method": "GET"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "route_by_tier",
|
|
"taskReferenceName": "tier_switch",
|
|
"type": "SWITCH",
|
|
"evaluatorType": "javascript",
|
|
"expression": "$.tier == 'enterprise' ? 'enterprise' : 'standard'",
|
|
"inputParameters": {
|
|
"tier": "${profile.output.response.body.tier}"
|
|
},
|
|
"decisionCases": {
|
|
"enterprise": [
|
|
{
|
|
"name": "assign_account_manager",
|
|
"taskReferenceName": "assign_am",
|
|
"type": "HTTP",
|
|
"inputParameters": {
|
|
"http_request": {
|
|
"uri": "https://api.example.com/account-managers/assign",
|
|
"method": "POST",
|
|
"body": {"userId": "${workflow.input.userId}"}
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"standard": [
|
|
{
|
|
"name": "send_welcome_email",
|
|
"taskReferenceName": "welcome",
|
|
"type": "HTTP",
|
|
"inputParameters": {
|
|
"http_request": {
|
|
"uri": "https://api.example.com/emails/welcome",
|
|
"method": "POST",
|
|
"body": {"userId": "${workflow.input.userId}"}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Parallel HTTP calls with Fork/Join
|
|
|
|
When tasks are independent, run them in parallel with a static fork.
|
|
|
|
```json
|
|
{
|
|
"name": "enrich_customer_data",
|
|
"version": 1,
|
|
"schemaVersion": 2,
|
|
"inputParameters": ["customerId"],
|
|
"tasks": [
|
|
{
|
|
"name": "parallel_enrichment",
|
|
"taskReferenceName": "fork",
|
|
"type": "FORK_JOIN",
|
|
"forkTasks": [
|
|
[
|
|
{
|
|
"name": "get_credit_score",
|
|
"taskReferenceName": "credit",
|
|
"type": "HTTP",
|
|
"inputParameters": {
|
|
"http_request": {
|
|
"uri": "https://api.example.com/credit/${workflow.input.customerId}",
|
|
"method": "GET"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"name": "get_purchase_history",
|
|
"taskReferenceName": "purchases",
|
|
"type": "HTTP",
|
|
"inputParameters": {
|
|
"http_request": {
|
|
"uri": "https://api.example.com/purchases/${workflow.input.customerId}",
|
|
"method": "GET"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"name": "get_support_tickets",
|
|
"taskReferenceName": "tickets",
|
|
"type": "HTTP",
|
|
"inputParameters": {
|
|
"http_request": {
|
|
"uri": "https://api.example.com/support/${workflow.input.customerId}",
|
|
"method": "GET"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
]
|
|
},
|
|
{
|
|
"name": "join_results",
|
|
"taskReferenceName": "join",
|
|
"type": "JOIN",
|
|
"joinOn": ["credit", "purchases", "tickets"]
|
|
}
|
|
],
|
|
"outputParameters": {
|
|
"creditScore": "${credit.output.response.body}",
|
|
"purchases": "${purchases.output.response.body}",
|
|
"tickets": "${tickets.output.response.body}"
|
|
}
|
|
}
|
|
```
|
|
|
|
All three HTTP calls execute simultaneously. The JOIN waits for all to complete before the workflow continues.
|