← Back to ArticlesHands-On Labs

Lab: Build an End-to-End Order Processing Pipeline

Hands-on lab — build a complete order processing system using APIM, Service Bus, Logic Apps, Blob Storage, and Event Grid with failure handling at every stage.

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:

  1. Receives orders via a REST API (APIM)
  2. Queues them for reliable processing (Service Bus)
  3. Processes orders and generates invoices (Logic App)
  4. Stores invoices in Blob Storage
  5. Notifies the customer via Event Grid
  6. 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:

  1. Triggers on Service Bus queue message
  2. Parses the order JSON
  3. Generates an invoice (Compose action)
  4. Uploads invoice to Blob Storage
  5. 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:


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

ConceptWhere Applied
API Gateway patternAPIM accepts and validates orders
Async messagingService Bus decouples API from processing
Managed IdentityZero credentials stored anywhere
Workflow orchestrationLogic App processes and routes
Blob StorageInvoice persistence
Dead Letter QueueFailure safety net
Monitoring alertsProactive failure detection

Next Labs (Coming Soon)


Azure Integration Hub — Hands-On Labs