Microservices Architecture in Azure

Microservices architecture structures an application as a collection of small, independently deployable services, each owning a specific business domain and its own data. Azure provides a rich set of building blocks for running microservices at production scale: Azure Kubernetes Service for container orchestration, Azure Service Bus and Event Hubs for async messaging, Azure API Management for gateway concerns, and Dapr for portable distributed systems primitives. This page covers how to compose these building blocks into a coherent, operable microservices platform.

When microservices make sense

Microservices solve specific problems. They are worth the operational overhead when:

  • Different parts of the system need to be deployed independently. The payments team should not have to coordinate a release with the search team.
  • Different parts of the system have dramatically different scaling requirements. The image processing service needs GPU instances; the user profile service needs nothing exotic.
  • Different parts of the system need different technology stacks. The recommendation engine is Python/TensorFlow; the transaction service is Java/Spring Boot.
  • The engineering team has grown large enough that Conway’s Law means a monolith will create coordination overhead.

The primary cost is operational complexity. Every additional service requires its own CI/CD pipeline, its own monitoring, its own capacity planning, and its own on-call ownership. A team of five engineers running fifteen microservices will spend most of their time on infrastructure rather than features. Right-size the number of services to your team.

Decomposing by domain

Domain-Driven Design (DDD) provides the clearest guidance for decomposing a system into services. Each service should own a bounded context — a coherent domain with a clear, consistent model. The bounded context defines the service’s data schema, its API, and its internal language. Where two bounded contexts meet, there is an explicit interface contract, not shared database tables.

An e-commerce application might decompose into: Order Management (order lifecycle), Inventory (stock levels and reservations), Customer (profiles, addresses, preferences), Payment (payment methods, transaction history), Fulfilment (shipping, tracking), and Notification (email, SMS, push). Each service owns its own database — Inventory does not read directly from the Orders table.

The “database per service” pattern is non-negotiable for true independent deployability. Shared databases create implicit coupling — a schema change required by one service breaks another. It forces database-level coordination between teams and prevents independent deployment.

Running microservices on AKS

Azure Kubernetes Service is the most common platform for running microservices in Azure. It provides a managed control plane, node auto-provisioning, automatic OS upgrades, integration with Azure Active Directory for RBAC, and native integration with Azure Container Registry.

Each microservice is packaged as a Docker image, deployed as a Kubernetes Deployment, and exposed via a Service and optionally an Ingress. Namespace boundaries map naturally to service ownership — the orders team deploys to the orders namespace; the inventory team deploys to the inventory namespace. Network policies restrict cross-namespace traffic to only what is explicitly allowed.

# Example: Inventory service Deployment and Service
apiVersion: apps/v1
kind: Deployment
metadata:
  name: inventory-service
  namespace: inventory
spec:
  replicas: 3
  selector:
    matchLabels:
      app: inventory-service
  template:
    metadata:
      labels:
        app: inventory-service
    spec:
      containers:
        - name: inventory
          image: myregistry.azurecr.io/inventory-service:1.4.2
          ports:
            - containerPort: 8080
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: inventory-db-secret
                  key: connection-string
          resources:
            requests:
              cpu: "250m"
              memory: "256Mi"
            limits:
              cpu: "1000m"
              memory: "512Mi"
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: inventory-service
  namespace: inventory
spec:
  selector:
    app: inventory-service
  ports:
    - port: 80
      targetPort: 8080

Service communication patterns

Microservices communicate either synchronously (request-response) or asynchronously (event-driven). Both patterns have a place in a well-designed system.

Synchronous communication uses HTTP REST or gRPC. REST is the default for its ubiquity and tooling. gRPC is preferable for high-frequency internal calls between services because it uses binary encoding (Protocol Buffers), multiplexes over a single HTTP/2 connection, and supports streaming. Within AKS, gRPC calls are significantly more efficient than REST for chatty service-to-service traffic.

Asynchronous communication uses Azure Service Bus for guaranteed, ordered, at-least-once delivery of business events. Azure Event Hubs is preferable for high-throughput telemetry or event streaming scenarios. Asynchronous messaging decouples services in time — the sender does not wait for the receiver to process the message, and the receiver does not need to be running when the message is sent.

# Create a Service Bus namespace and a topic for order events
az servicebus namespace create \
  --resource-group myRG \
  --name myServiceBusNS \
  --sku Standard

az servicebus topic create \
  --resource-group myRG \
  --namespace-name myServiceBusNS \
  --name order-events

# Create subscriptions for each consuming service
az servicebus topic subscription create \
  --resource-group myRG \
  --namespace-name myServiceBusNS \
  --topic-name order-events \
  --name inventory-subscription

az servicebus topic subscription create \
  --resource-group myRG \
  --namespace-name myServiceBusNS \
  --topic-name order-events \
  --name fulfilment-subscription

API Management as the service gateway

Azure API Management (APIM) acts as the single entry point for all external traffic to your microservices. It handles cross-cutting concerns that would otherwise be duplicated in every service: authentication and authorisation, rate limiting, request routing, response caching, API versioning, and developer portal documentation.

Internally, APIM routes to backend services using products and APIs. Each backend microservice registers its OpenAPI specification with APIM. APIM policies (XML-based transformation rules) handle JWT validation, header injection, response transformation, and circuit breaking before the request ever reaches the service.

This pattern separates external concerns (public API stability, rate limiting, auth) from internal concerns (service implementation, internal routing). Internal services communicate directly with each other via Kubernetes service DNS or via Service Bus — they do not route internal traffic through APIM.

Simplifying distributed systems with Dapr

Dapr runs as a sidecar container alongside each service. Instead of importing an Azure SDK and writing Service Bus-specific connection and retry code, your service makes a simple HTTP call to http://localhost:3500/v1.0/publish/order-events and Dapr handles the rest — connecting to Azure Service Bus, handling retries, tracking delivery, and emitting telemetry. The same call works unchanged when running locally against a Redis pub/sub component for development.

# Dapr component: Azure Service Bus pub/sub
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: order-events
  namespace: default
spec:
  type: pubsub.azure.servicebus.topics
  version: v1
  metadata:
    - name: connectionString
      secretKeyRef:
        name: servicebus-secret
        key: connectionString
    - name: maxRetriableErrorStatusCodes
      value: "408,429,500,502,503,504"

Dapr’s state management building block abstracts session and application state. The same API call stores state in Redis locally and in Azure Cosmos DB in production. Dapr’s secrets building block fetches secrets from Azure Key Vault without the service knowing it is talking to Key Vault specifically.

Observability across services

The single hardest operational challenge with microservices is understanding what is happening when something goes wrong. A request that touches six services before returning an error produces a stack trace that starts in the wrong service. Distributed tracing solves this by propagating a trace ID through every service call and correlating all spans into a single trace.

Azure Monitor Application Insights supports distributed tracing natively. Instrument each service with the Application Insights SDK or OpenTelemetry exporter. The trace ID is propagated via the traceparent HTTP header. In the Application Insights portal, the End-to-End transaction view shows the complete call tree, latency at each hop, and any errors, even across services implemented in different languages.

Set up alerts on inter-service error rates, not just overall error rates. An upstream service that returns 200 OK while a downstream service it called returned 503 looks healthy at the surface but has a hidden failure mode. Track dependency failure rates in each service separately.

Common mistakes

  1. Sharing a database between services. When two services read and write the same database tables, they are not independently deployable. A schema migration required by one service can break the other. Each service must own its own data store, with the only cross-service data access happening through explicit API calls.
  2. Building a distributed monolith. If every deployment requires coordinating releases across multiple services (because they have tight runtime dependencies), you have a distributed monolith — with all the operational complexity of microservices and none of the independence benefits. Services must truly be able to deploy and operate independently.
  3. Neglecting idempotency in message consumers. Azure Service Bus delivers messages at least once. If a consumer processes a message and then crashes before acknowledging it, Service Bus will redeliver. Your consumer must handle duplicate delivery gracefully. Use idempotency keys (the message ID) and check whether the message has already been processed before acting on it.

Frequently asked questions

Should I use microservices for every Azure application?

No. Microservices add real operational complexity — more services to deploy, monitor, and secure, more network calls, and more failure modes to handle. They are justified when teams need independent release cadences, when different parts of the system have very different scaling requirements, or when the codebase has grown so large that a monolith is slowing development. Start with a modular monolith and extract services when you have a concrete reason to do so.

What is Dapr and when should I use it in Azure?

Dapr (Distributed Application Runtime) is a runtime that provides portable building blocks for microservices: service invocation, pub/sub messaging, state management, secret management, and observability. Running as a sidecar, it abstracts the underlying infrastructure — your service calls the Dapr HTTP/gRPC API and Dapr handles the routing, retries, and integration with Azure Service Bus, Cosmos DB, Key Vault, and others. Use Dapr when you want to avoid tight coupling to Azure-specific SDKs and when you want the portability benefits across environments.

How should microservices communicate — synchronously or asynchronously?

Use synchronous communication (HTTP/gRPC) when the calling service needs an immediate response before it can continue — for example, checking inventory before confirming an order. Use asynchronous communication (Azure Service Bus, Event Hubs) when the calling service does not need to wait — for example, publishing an event after an order is confirmed and letting downstream services react. Preferring async communication reduces temporal coupling and improves resilience.

Last verified: 19 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.