Tenant ID :👈 👉:Service Per Business Capability Pattern

Service-Per-Subdomain Pattern

Service Per Subdomain Pattern in Microservices

Service Per Subdomain is a microservice design pattern where each microservice is built around a specific business subdomain from Domain-Driven Design (DDD).

Instead of creating services based on technical functions (such as UserService, DatabaseService, LoggingService), you split services according to business capabilities like:

  • Customer Management
  • Order Management
  • Inventory Management
  • Payment Processing
  • Shipping

Each subdomain owns:

  • Its business logic
  • Its database
  • Its APIs
  • Its deployment

Why Service Per Subdomain?

In large applications, different business areas evolve independently.

For example, in an e-commerce system:

E-Commerce System
│
├── Customer Subdomain
├── Product Catalog Subdomain
├── Order Subdomain
├── Payment Subdomain
├── Shipping Subdomain
└── Notification Subdomain

Instead of one large monolith handling everything, each subdomain becomes a separate microservice.

Monolithic Approach

ECommerceApp
│
├── Customers
├── Products
├── Orders
├── Payments
├── Shipping
└── Notifications

Problems:

  • Huge codebase
  • Difficult deployments
  • Tight coupling
  • Slow development
  • Scaling entire application for one heavy module

Service Per Subdomain Architecture

                API Gateway
                               │
        ┌──────────────────────┼─────────────────────┐
        │                      │                     │
        ▼                      ▼                     ▼
 Product Service     Order Service       Customer Service
      │                   │                     │
      ▼                   ▼                     ▼
 ProductDB           OrderDB            CustomerDB
        │
        │
        ▼
 Payment Service
        │
        ▼
   PaymentDB

Every service owns its data.

Example: Online Shopping Application

1. Customer Subdomain

Responsibilities:

  • Registration
  • Login
  • Profile Management
  • Address Management

Microservice:

CustomerService

Database:

CustomerDB

API:

GET /api/customers/1
POST /api/customers

2. Product Subdomain

Responsibilities:

  • Product Catalog
  • Categories
  • Product Search

Microservice:

ProductService

Database:

ProductDB

Endpoints:

GET /api/products
GET /api/products/101

3. Order Subdomain

Responsibilities:

  • Create Orders
  • Track Orders
  • Cancel Orders

Microservice:

OrderService

Database:

OrderDB

Endpoints:

POST /api/orders
GET /api/orders/5001

4. Payment Subdomain

Responsibilities:

  • Payment Processing
  • Refunds
  • Payment Status

Microservice:

PaymentService

Database:

PaymentDB

Endpoints:

POST /api/payments
GET /api/payments/1001

.NET Example

Suppose we create an Order Service.

Structure

OrderService
│
├── Controllers
│   └── OrdersController.cs
│
├── Models
│   └── Order.cs
│
├── Data
│   └── OrderDbContext.cs
│
├── Repository
│   └── OrderRepository.cs
│
└── Program.cs

Order Model

public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public decimal Amount { get; set; }
    public DateTime OrderDate { get; set; }
}

DbContext

using Microsoft.EntityFrameworkCore;
public class OrderDbContext : DbContext
{
    public OrderDbContext(DbContextOptions<OrderDbContext> options)
        : base(options)
    {
    }
    public DbSet<Order> Orders { get; set; }
}

Orders Controller

[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly OrderDbContext _context;
    public OrdersController(OrderDbContext context)
    {
        _context = context;
    }
    [HttpGet("{id}")]
    public async Task<IActionResult> GetOrder(int id)
    {
        var order = await _context.Orders.FindAsync(id);
        if (order == null)
            return NotFound();
        return Ok(order);
    }
    [HttpPost]
    public async Task<IActionResult> CreateOrder(Order order)
    {
        _context.Orders.Add(order);
        await _context.SaveChangesAsync();
        return Ok(order);
    }
}

Program.cs

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<OrderDbContext>(options =>
    options.UseSqlServer(
        builder.Configuration.GetConnectionString("Default")));
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();

Service Communication Example

When creating an order:

Client
  │
  ▼
Order Service
  │
  ├── Validate Customer
  │         │
  │         ▼
  │   Customer Service
  │
  ├── Check Product
  │         │
  │         ▼
  │   Product Service
  │
  └── Process Payment
            │
            ▼
      Payment Service

Synchronous Communication

Using HttpClient:

public class CustomerClient
{
    private readonly HttpClient _httpClient;
    public CustomerClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }
    public async Task<CustomerDto> GetCustomerAsync(int id)
    {
        return await _httpClient
            .GetFromJsonAsync<CustomerDto>(
                $"api/customers/{id}");
    }
}

Asynchronous Communication

Using RabbitMQ/Azure Service Bus.

Order Service publishes:

await _publishEndpoint.Publish(
    new OrderCreated
    {
         OrderId = order.Id,
         Amount = order.Amount
    });

Payment Service listens:

public class OrderCreatedConsumer :
    IConsumer<OrderCreated>
{
    public async Task Consume(
        ConsumeContext<OrderCreated> context)
    {
        var message = context.Message;
        // Process payment
    }
}

Database Per Service

A key rule in Service Per Subdomain:

✅ Good

Customer Service -> CustomerDB
Order Service    -> OrderDB
Payment Service  -> PaymentDB

❌ Bad

Customer Service -> SharedDB
Order Service    -> SharedDB
Payment Service  -> SharedDB

Why? Because shared databases create tight coupling and defeat the purpose of microservices.

Real-World Example: Amazon-like System

Subdomain Microservice
Customer Management Customer Service
Product Catalog Catalog Service
Shopping Cart Cart Service
Order Processing Order Service
Payments Payment Service
Delivery Shipping Service
Reviews Review Service
Notifications Notification Service

Each service:

  • Has its own database
  • Can be deployed independently
  • Can scale independently
  • Can use different technologies if needed

Advantages

1. Independent Deployment

Deploy Order Service without touching Payment Service.

2. Independent Scaling

If order traffic increases:

Order Service = 10 instances
Payment Service = 2 instances

3. Better Ownership

Teams can own services:

Team A → Customer Service
Team B → Order Service
Team C → Payment Service

4. Fault Isolation

Payment failure does not crash Product Service.

Disadvantages

1. Distributed System Complexity

  • Service discovery
  • Network latency
  • Retry handling
  • Circuit breakers

2. Data Consistency

Transactions across services become difficult.

Example:

Order Created
Payment Failed
Inventory Reduced

Need Saga Pattern.

3. Monitoring

Need tools like:

  • OpenTelemetry
  • Seq
  • ELK
  • Application Insights

When to Use Service Per Subdomain

✅ Use when:

  • Large enterprise applications
  • Clear business domains
  • Multiple development teams
  • Independent deployments required

❌ Avoid when:

  • Small applications
  • Small teams
  • Simple CRUD systems
  • Startup MVPs

Summary

Service Per Subdomain means one microservice per business subdomain/bounded context. In a .NET microservices architecture, each service is typically an ASP.NET Core Web API with its own database, business logic, and deployment pipeline. This pattern aligns closely with Domain-Driven Design (DDD) and is one of the most recommended approaches for designing scalable enterprise microservices.

Back to Index
Tenant ID :👈 👉:Service Per Business Capability Pattern