Lab: Build an End-to-End Order Processing Pipeline
Difficulty: Intermediate to Advanced
Duration: 90 minutes
Prerequisites: Azure subscription, basic knowledge of APIM, Service Bus, Logic Apps, and Blob Storage
What You'll Build
A complete order processing pipeline that:
- Receives orders via a REST API (APIM)
- Queues them for reliable processing (Service Bus)
- Processes orders and generates invoices (Logic App)
- Stores invoices in Blob Storage
- Notifies the customer via Event Grid
- Handles failures with Dead Letter Queues and alerts
┌──────────┐ ┌──────────┐ ┌───────────────┐ ┌─────────────┐
│ Client │────▶│ APIM │────▶│ Service Bus │────▶│ Logic App │
│ (POST) │ │ Gateway │ │ Queue │ │ Processor │
└──────────┘ └──────────┘ └───────────────┘ └──────┬──────┘
│
┌─────────────────────┼──────────────┐
▼ ▼ ▼
┌─────────────┐ ┌──────────────┐ ┌──────────┐
│ Blob │ │ Event Grid │ │ DLQ │
│ Storage │ │ (Notify) │ │ Alert │
│ (Invoice) │ └──────────────┘ └──────────┘
└─────────────┘
Step 1: Create the Azure Resources
1.1 Resource Group
az group create --name rg-order-lab --location eastus
1.2 Service Bus Namespace and Queue
# Create namespace (Standard tier for DLQ support)
az servicebus namespace create \
--name sb-order-lab \
--resource-group rg-order-lab \
--sku Standard
# Create queue with dead-lettering enabled
az servicebus queue create \
--name orders \
--namespace-name sb-order-lab \
--resource-group rg-order-lab \
--max-delivery-count 3 \
--dead-lettering-on-message-expiration true
1.3 Storage Account
az storage account create \
--name storderlab \
--resource-group rg-order-lab \
--sku Standard_LRS
# Create container for invoices
az storage container create \
--name invoices \
--account-name storderlab
1.4 Logic App (Standard)
az logicapp create \
--name la-order-processor \
--resource-group rg-order-lab \
--storage-account storderlab
1.5 API Management (Consumption tier for lab)
az apim create \
--name apim-order-lab \
--resource-group rg-order-lab \
--publisher-name "Lab" \
--publisher-email "lab@example.com" \
--sku-name Consumption
Step 2: Configure Managed Identity
Enable system-assigned managed identity on the Logic App and grant permissions:
# Enable MI on Logic App
az logicapp identity assign \
--name la-order-processor \
--resource-group rg-order-lab
# Get the principal ID
PRINCIPAL_ID=$(az logicapp show \
--name la-order-processor \
--resource-group rg-order-lab \
--query identity.principalId -o tsv)
# Grant Service Bus Receiver role
az role assignment create \
--assignee $PRINCIPAL_ID \
--role "Azure Service Bus Data Receiver" \
--scope $(az servicebus namespace show --name sb-order-lab --resource-group rg-order-lab --query id -o tsv)
# Grant Storage Blob Contributor role
az role assignment create \
--assignee $PRINCIPAL_ID \
--role "Storage Blob Data Contributor" \
--scope $(az storage account show --name storderlab --resource-group rg-order-lab --query id -o tsv)
Step 3: Build the Logic App Workflow
Create a workflow that:
- Triggers on Service Bus queue message
- Parses the order JSON
- Generates an invoice (Compose action)
- Uploads invoice to Blob Storage
- Publishes a notification event
Workflow Definition (simplified)
{
"definition": {
"triggers": {
"When_message_received": {
"type": "ServiceBus",
"inputs": {
"parameters": {
"queueName": "orders",
"isSessionsEnabled": false
},
"serviceProviderConfiguration": {
"connectionName": "serviceBus",
"operationId": "receiveQueueMessages"
}
}
}
},
"actions": {
"Parse_Order": {
"type": "ParseJson",
"inputs": {
"content": "@triggerBody()?['contentData']",
"schema": {
"type": "object",
"properties": {
"orderId": { "type": "string" },
"customer": { "type": "string" },
"items": { "type": "array" },
"total": { "type": "number" }
}
}
}
},
"Generate_Invoice": {
"type": "Compose",
"inputs": {
"invoiceId": "@{guid()}",
"orderId": "@body('Parse_Order')?['orderId']",
"customer": "@body('Parse_Order')?['customer']",
"total": "@body('Parse_Order')?['total']",
"generatedAt": "@utcNow()"
},
"runAfter": { "Parse_Order": ["Succeeded"] }
},
"Upload_to_Blob": {
"type": "ApiConnection",
"inputs": {
"host": { "connection": { "name": "azureblob" } },
"method": "post",
"path": "/v2/datasets/default/files",
"body": "@outputs('Generate_Invoice')",
"queries": {
"folderPath": "/invoices",
"name": "@{body('Parse_Order')?['orderId']}.json"
}
},
"runAfter": { "Generate_Invoice": ["Succeeded"] }
}
}
}
}
Step 4: Configure APIM to Accept Orders
4.1 Import a blank API
Create an API in APIM with a POST /orders operation.
4.2 Apply the send-to-Service-Bus policy
<policies>
<inbound>
<base />
<set-variable name="orderId" value="@(Guid.NewGuid().ToString())" />
<set-body>@{
var body = context.Request.Body.As<JObject>(preserveContent: true);
body["orderId"] = (string)context.Variables["orderId"];
return body.ToString();
}</set-body>
</inbound>
<backend>
<send-request mode="new" timeout="30" response-variable-name="sb-response">
<set-url>https://sb-order-lab.servicebus.windows.net/orders/messages</set-url>
<set-method>POST</set-method>
<authentication-managed-identity resource="https://servicebus.azure.net/" />
<set-header name="Content-Type" exists-action="override">application/json</set-header>
<set-body>@(context.Request.Body.As<string>(preserveContent: true))</set-body>
</send-request>
</backend>
<outbound>
<return-response>
<set-status code="202" reason="Accepted" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>@{
return JsonConvert.SerializeObject(new {
status = "accepted",
orderId = (string)context.Variables["orderId"],
message = "Order queued for processing"
});
}</set-body>
</return-response>
</outbound>
<on-error>
<base />
</on-error>
</policies>
Step 5: Test the Pipeline
Send a test order:
curl -X POST "https://apim-order-lab.azure-api.net/orders" \
-H "Content-Type: application/json" \
-H "Ocp-Apim-Subscription-Key: <your-key>" \
-d '{
"customer": "John Doe",
"items": [
{ "name": "Azure Certification Voucher", "qty": 1, "price": 165 }
],
"total": 165.00
}'
Expected response:
{
"status": "accepted",
"orderId": "a1b2c3d4-...",
"message": "Order queued for processing"
}
Validate:
- Check Service Bus queue — message received
- Check Logic App run history — workflow triggered and succeeded
- Check Blob Storage — invoice JSON file created in
/invoices
Step 6: Add Failure Handling
6.1 Monitor Dead Letter Queue
# Check DLQ depth
az servicebus queue show \
--name orders \
--namespace-name sb-order-lab \
--resource-group rg-order-lab \
--query "countDetails.deadLetterMessageCount"
6.2 Create an alert for DLQ depth
az monitor metrics alert create \
--name "dlq-depth-alert" \
--resource-group rg-order-lab \
--scopes $(az servicebus namespace show --name sb-order-lab --resource-group rg-order-lab --query id -o tsv) \
--condition "total DeadletteredMessages > 0" \
--description "Orders are failing — DLQ has messages"
Step 7: Cleanup
az group delete --name rg-order-lab --yes --no-wait
What You Learned
| Concept | Where Applied |
|---|---|
| API Gateway pattern | APIM accepts and validates orders |
| Async messaging | Service Bus decouples API from processing |
| Managed Identity | Zero credentials stored anywhere |
| Workflow orchestration | Logic App processes and routes |
| Blob Storage | Invoice persistence |
| Dead Letter Queue | Failure safety net |
| Monitoring alerts | Proactive failure detection |
Next Labs (Coming Soon)
- Lab 2: Event-Driven File Processing — Blob upload → Event Grid → Function → Transform → Store
- Lab 3: Multi-Region Failover — APIM + Front Door + Logic Apps across regions
- Lab 4: Real-Time Dashboard — Event Hubs → Stream Analytics → Power BI
Azure Integration Hub — Hands-On Labs