ASP.NET Core Microservices Architecture Guide 2026

A practical guide to ASP.NET Core microservices architecture - service decomposition, communication patterns, data isolation, and deployment on Azure.

ASP.NET Core

ASP.NET Core Microservices Architecture Guide 2026

  • Monday, July 20, 2026

A practical guide to ASP.NET Core microservices architecture - service decomposition, communication patterns, data isolation, and deployment on Azure.

The short version: microservices architecture in ASP.NET Core is the right choice when your application has genuinely independent business domains that need to scale, deploy, and fail separately and it's the wrong choice when it's adopted prematurely on a system that would be better served by a well-structured monolith for another year or two. This guide covers how to architect ASP.NET Core microservices when the decision is right, what the common patterns look like in practice, and where teams consistently get into trouble.

When Microservices Actually Makes Sense for an ASP.NET Core Application

Before getting into how to build microservices with ASP.NET Core, it's worth being direct about when not to because the majority of systems that adopt microservices prematurely end up with distributed monolith problems rather than microservices benefits.

Microservices are appropriate when:

  • Independent scalability matters at the service level. If your order processing service needs to scale to handle peak load independently of your user management service, splitting them makes sense. If they both scale together under the same load pattern, you're adding distribution overhead for no benefit.
  • Teams are genuinely independent. The organizational case for microservices Conway's Law in reverse is that separate services allow separate teams to deploy independently without coordinating release cycles. If one team owns all the services, you've distributed the code without distributing the organization, and you've made your own life harder.
  • Business domains are clearly bounded. If you can't draw a clean line between "what this service owns" and "what that service owns" without constantly negotiating shared data, your domain isn't ready to be split. The architecture will reflect the ambiguity in the form of chatty inter-service calls and shared database tables.
  • The system is already working and understood. Microservices are almost never the right starting architecture for a new product. The domain boundaries you think you understand at the start of a project are almost always wrong by the time the product matures. Starting with a modular monolith one codebase with clear internal boundaries and extracting services when genuine scaling or team independence pressure emerges is a pattern that consistently produces better results.

If you are reading this because you're evaluating the architecture for a new project, "build a modular monolith with clean domain boundaries and extract services when you feel the pressure to" is legitimate advice, not a cop-out.

Core Architectural Concepts

Bounded Contexts and Service Decomposition

The fundamental unit of microservices design is the bounded context a domain concept borrowed from Domain-Driven Design. A bounded context is a boundary within which a specific domain model applies and is internally consistent. The goal of service decomposition is to align service boundaries with bounded context boundaries, so each service owns its domain completely and other services interact with it only through a defined interface.

In practice for an ASP.NET Core application, a typical bounded context decomposition for a SaaS platform might look like:

  • Identity service: user registration, authentication, authorization, roles
  • Billing service: subscriptions, invoicing, payment processing
  • Notification service: email, SMS, push notifications, delivery tracking
  • Core domain service(s): whatever the application actually does for users

These four domains interact but are internally independent. The billing service doesn't need to know how user authentication works internally, and the notification service doesn't need to know what triggered a notification. Each service can be built, deployed, and scaled independently.

The common mistake at this stage is decomposing by technical layer rather than business domain a "data service," a "logic service," a "presentation service" which produces services that are tightly coupled by nature and share no independence benefits.

Data Isolation

Each microservice should own its own data store. This is the rule that makes microservices genuinely independent, and it's the rule teams most commonly want to negotiate around when they first encounter it, because sharing a database feels simpler.

Sharing a database between services creates coupling at the data layer that defeats the independence microservices are supposed to provide. If Service A and Service B share a table, neither can change that table's schema without coordinating with the other which is exactly the deployment coupling microservices are supposed to eliminate.

In ASP.NET Core implementations, data isolation means:

  • Separate SQL Server databases per service (or separate schemas with strict access controls as a migration step, not a permanent solution)
  • Each service manages its own migrations via Entity Framework Core independently
  • Cross-service data needs are satisfied through API calls or events, not direct database queries
  • Read models for cross-service reporting are built separately (often using event sourcing patterns or a dedicated read database populated by events)

The practical consequence is that some queries you could write in one SQL JOIN across a monolith now require either an API call to retrieve the related data, or maintaining a denormalized read model. This is a real cost it's worth accepting when independence benefits outweigh it, and worth reconsidering when they don't.

Communication Patterns

ASP.NET Core microservices communicate in two fundamental modes: synchronous (request-response) and asynchronous (event-driven). Choosing correctly between them is one of the most consequential architectural decisions in a microservices system.

Synchronous Communication REST and gRPC

REST APIs built with ASP.NET Core Minimal APIs or MVC controllers are the default choice for synchronous inter-service communication. They're straightforward to build, easy to test, and work well when:

  • The calling service genuinely needs a response before continuing
  • Latency requirements are tight
  • The called service is expected to be reliably available

The main risk with synchronous communication in microservices is cascading failure if Service A calls Service B synchronously and Service B is slow or unavailable, Service A is blocked. Addressing this requires resilience patterns: retry with exponential backoff, circuit breakers, timeouts, and fallback responses.

Polly is the standard library for resilience patterns in .NET it integrates cleanly with ASP.NET Core's HttpClientFactory and handles retry, circuit breaker, timeout, and bulkhead isolation policies with minimal configuration.

gRPC is increasingly common for internal service-to-service communication in ASP.NET Core microservices where performance is critical binary serialization via Protocol Buffers is significantly more efficient than JSON over REST. The tradeoff is that gRPC is harder to debug (binary messages aren't human-readable), and it requires HTTP/2, which has some hosting infrastructure implications. For services communicating at high frequency internally, gRPC is worth the additional setup; for most inter-service calls, REST is sufficient.

Asynchronous Communication Message Queues and Event Buses

When a service needs to notify others about something that happened rather than request something asynchronous messaging is almost always the better pattern. It decouples services in time: the publishing service doesn't wait for consumers to process the event, and consumers can process at their own pace.

Common patterns in ASP.NET Core microservices:

Domain events when something significant happens in one service's domain (an order is placed, a subscription is renewed, a user is created), it publishes an event to a message broker. Other services subscribe and react without the publishing service knowing or caring who's listening.

Message brokers used in production .NET stacks:

  • Azure Service Bus - the default choice for Azure-hosted ASP.NET Core microservices. Supports both queues (point-to-point) and topics (publish-subscribe). Native integration with Azure identity and monitoring.
  • RabbitMQ - popular for non-Azure deployments, open source, excellent .NET client library (RabbitMQ.Client)
  • MassTransit - a .NET library that abstracts over Azure Service Bus, RabbitMQ, and other transports, providing a consistent programming model regardless of the underlying broker. Widely used in production .NET microservices

In practice, most ASP.NET Core microservices systems use both patterns: synchronous REST or gRPC for queries (read operations that need a response), and asynchronous events for commands and notifications (write operations that trigger downstream reactions).

API Gateway

In a microservices system, clients whether a web frontend, a mobile app, or a third-party API consumer shouldn't call individual services directly. An API gateway sits in front of all services and handles:

  • Routing - directing requests to the appropriate service
  • Authentication - validating tokens centrally rather than in every service
  • Rate limiting - protecting services from traffic spikes
  • Response aggregation - combining responses from multiple services into a single client-facing response
  • SSL termination - handling HTTPS at the gateway rather than in every service

For ASP.NET Core microservices, the common API gateway choices are:

YARP (Yet Another Reverse Proxy) Microsoft's open-source reverse proxy library, built for .NET. Configured in code or via JSON, integrates natively with ASP.NET Core middleware pipeline. The recommended starting point for most .NET microservices teams.

Azure API Management a fully managed gateway service for Azure-hosted systems. Better suited to systems that need enterprise-grade gateway features (developer portals, monetization, analytics) than pure internal-service routing.

Ocelot a popular open-source API gateway built specifically for .NET microservices. Mature, widely documented, though YARP has largely superseded it for new projects due to Microsoft's direct investment.

Service Discovery and Configuration

Service Discovery

In a containerized deployment, service instances come and go you can't hardcode IP addresses or URLs. Service discovery solves this by allowing services to find each other dynamically.

In Kubernetes-hosted ASP.NET Core microservices, Kubernetes' built-in DNS-based service discovery is sufficient for most cases services reference each other by Kubernetes service name rather than IP, and Kubernetes handles routing. For non-Kubernetes deployments, tools like Consul or Azure Service Fabric provide similar capability.

Centralized Configuration

Distributing configuration across 8 microservices each with its own appsettings.json is a maintenance problem. Centralizing configuration reduces this overhead and allows configuration changes without redeployment.

Azure App Configuration is the natural choice for Azure-hosted ASP.NET Core microservices it integrates with ASP.NET Core's IConfiguration API, supports feature flags, and connects with Azure Key Vault for secrets. HashiCorp Vault is the alternative for non-Azure or hybrid environments.

The critical rule: secrets (connection strings, API keys, certificates) should never be in appsettings.json, even in development. Azure Key Vault (production) and the .NET Secret Manager (local development) are the right patterns, and they require minimal setup with ASP.NET Core's built-in configuration system.

Observability - Logging, Tracing, and Metrics

A monolith fails in one place. A microservices system can fail in any of a dozen services or in the communication between them. Without deliberate observability investment, diagnosing a production issue across a distributed system is significantly harder than in a monolith.

Distributed tracing is the most important observability investment in a microservices system. A trace follows a request across all the services it touches, showing where time was spent and where failures occurred. In .NET 8, OpenTelemetry is the standard Microsoft.Extensions.Diagnostics provides built-in activity tracing that integrates with OpenTelemetry exporters for Azure Application Insights, Jaeger, or Zipkin.

Structured logging with correlation IDs is the practical complement to distributed tracing every log entry across every service includes a correlation ID that ties it to the originating request, so you can filter logs across services by a single request ID. Serilog with the Serilog.Enrichers.CorrelationId package handles this with minimal configuration in ASP.NET Core.

Health checks are built into ASP.NET Core via IHealthCheck and the AddHealthChecks() middleware. Every service should expose a /health endpoint that checks its own dependencies (database connectivity, downstream service availability) and returns a structured status. Kubernetes liveness and readiness probes use these endpoints to manage container lifecycle.

Deployment - Docker and Kubernetes

ASP.NET Core microservices are almost always deployed as Docker containers, orchestrated by Kubernetes or Azure Kubernetes Service (AKS).

A minimal production-ready Dockerfile for an ASP.NET Core 8 service:

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 8080

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["ServiceName/ServiceName.csproj", "ServiceName/"]
RUN dotnet restore "ServiceName/ServiceName.csproj"
COPY . .
WORKDIR "/src/ServiceName"
RUN dotnet build -c Release -o /app/build

FROM build AS publish
RUN dotnet publish -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ServiceName.dll"]

In CI/CD, Azure DevOps or GitHub Actions builds the Docker image, pushes it to Azure Container Registry, and deploys to AKS via Helm charts or kubectl. The critical practice: each service has its own CI/CD pipeline and deploys independently. A shared deployment pipeline across all services reintroduces the deployment coupling microservices are supposed to eliminate.

Common Mistakes and How to Avoid Them

Chatty services. If Service A makes 12 synchronous calls to Service B to complete a single user-facing operation, the services are too granular they're communicating like they're modules in a monolith but with the latency overhead of network calls. The fix is usually either service consolidation or response aggregation at the API gateway level.

Distributed transactions. Maintaining ACID transactions across service boundaries requires patterns like the Saga pattern, which adds significant complexity. Before implementing a saga, ask whether the business requirement genuinely needs strict consistency across services or whether eventual consistency is acceptable. Often it is, and asynchronous events handle the coordination without explicit saga orchestration.

Skipping the modular monolith phase. Teams that jump directly to microservices on a new product consistently report getting the service boundaries wrong the first time the domain isn't understood well enough to decompose correctly. A modular monolith for 6–12 months lets you learn the domain before making the decomposition permanent.

Neglecting observability until something breaks in production. Setting up distributed tracing and structured logging after a microservices system is in production is significantly harder than doing it from the start. Build observability in at day one, even if the system only has two services.

Frequently Asked Questions

How many microservices is too many?

There's no universal number, but a useful heuristic is that a team of 5–8 engineers can comfortably own and operate 3–5 services. If the number of services significantly exceeds the number of teams, the system is likely over-decomposed nobody has clear ownership, and cross-service changes become coordination problems rather than independence benefits.

Should each microservice have its own database server, or just its own database?

In most production systems, services share database server infrastructure (a single SQL Server instance or Azure SQL elastic pool) but own separate databases or strictly isolated schemas. Separate physical servers per service are unnecessary for most systems and add significant infrastructure cost. The critical isolation is at the database/schema level, not the server level.

Can ASP.NET Core microservices use different databases for different services?

Yes, and this is common in practice one service might use SQL Server for relational data while another uses Redis for caching-heavy data or Cosmos DB for document-oriented data. The polyglot persistence pattern is one of the genuine benefits of microservices data isolation, though it also means your team needs operational familiarity with multiple data stores.

How do we handle authentication across multiple services?

The standard pattern is centralized authentication via a dedicated identity service or a third-party identity provider (Azure AD / Microsoft Entra ID, Auth0, IdentityServer). The API gateway validates tokens centrally and passes identity claims downstream to services via HTTP headers. Services trust the gateway's validation rather than re-validating tokens independently. ASP.NET Core's JWT middleware makes consuming these claims straightforward on each service.

When should we use gRPC instead of REST for inter-service communication?

When the performance difference matters high-frequency internal calls (hundreds per second or more), or scenarios where payload size makes JSON serialization overhead significant. For most inter-service communication, REST is sufficient and easier to work with. Start with REST and move specific high-traffic communication paths to gRPC if performance measurements justify it.

Closing

Microservices architecture in ASP.NET Core is well-supported the ecosystem, the tooling, and the .NET runtime itself are all well-suited to this kind of deployment. The technical pieces (service communication, API gateways, containerization, observability) are the solvable part. The harder questions are organizational: are your domain boundaries clear enough to decompose? Are your teams structured to own services independently? Do the scaling benefits genuinely justify the distribution overhead for your specific system?

If you're working through these questions for a real project, our team has designed and built microservices systems in ASP.NET Core across healthcare, fintech, and SaaS platforms the architectural decisions look different depending on the domain, and we're happy to think through what applies to your specific situation.