IConfiguration vs IOptions NET
Synchronous and Asynchronous in .NET Core
Model Binding and Validation in ASP.NET Core
ControllerBase vs Controller in ASP.NET Core
ConfigureServices and Configure methods
IHostedService interface in .NET Core
ASP.NET Core request processing
| Tenant ID :👈 | 👉:Service Per Business Capability Pattern |
Service-Per-Subdomain Pattern |
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:
Each subdomain owns:
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.
ECommerceApp │ ├── Customers ├── Products ├── Orders ├── Payments ├── Shipping └── Notifications
Problems:
API Gateway
│
┌──────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
Product Service Order Service Customer Service
│ │ │
▼ ▼ ▼
ProductDB OrderDB CustomerDB
│
│
▼
Payment Service
│
▼
PaymentDB
Every service owns its data.
Responsibilities:
Microservice:
CustomerService
Database:
CustomerDB
API:
GET /api/customers/1 POST /api/customers
Responsibilities:
Microservice:
ProductService
Database:
ProductDB
Endpoints:
GET /api/products GET /api/products/101
Responsibilities:
Microservice:
OrderService
Database:
OrderDB
Endpoints:
POST /api/orders GET /api/orders/5001
Responsibilities:
Microservice:
PaymentService
Database:
PaymentDB
Endpoints:
POST /api/payments GET /api/payments/1001
Suppose we create an Order Service.
OrderService │ ├── Controllers │ └── OrdersController.cs │ ├── Models │ └── Order.cs │ ├── Data │ └── OrderDbContext.cs │ ├── Repository │ └── OrderRepository.cs │ └── Program.cs
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public decimal Amount { get; set; }
public DateTime OrderDate { get; set; }
}
using Microsoft.EntityFrameworkCore;
public class OrderDbContext : DbContext
{
public OrderDbContext(DbContextOptions<OrderDbContext> options)
: base(options)
{
}
public DbSet<Order> Orders { get; set; }
}
[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);
}
}
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();
When creating an order:
Client
│
▼
Order Service
│
├── Validate Customer
│ │
│ ▼
│ Customer Service
│
├── Check Product
│ │
│ ▼
│ Product Service
│
└── Process Payment
│
▼
Payment Service
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}");
}
}
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
}
}
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.
| 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:
Deploy Order Service without touching Payment Service.
If order traffic increases:
Order Service = 10 instances Payment Service = 2 instances
Teams can own services:
Team A → Customer Service Team B → Order Service Team C → Payment Service
Payment failure does not crash Product Service.
Transactions across services become difficult.
Example:
Order Created Payment Failed Inventory Reduced
Need Saga Pattern.
Need tools like:
✅ Use when:
❌ Avoid when:
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.
| Tenant ID :👈 | 👉:Service Per Business Capability Pattern |