microservices-patterns
.NET microservices patterns including service decomposition, communication, CQRS, saga, and resilience
What this skill does
# .NET Microservices Patterns
## Service Decomposition
### By Business Capability
```
E-Commerce Platform:
├── Catalog Service (products, categories, search)
├── Order Service (orders, order processing)
├── Payment Service (payment processing, refunds)
├── Inventory Service (stock management)
├── Notification Service (email, SMS, push)
├── Identity Service (authentication, users)
└── Gateway (API gateway, BFF)
```
### Database Per Service
Each service owns its data store. No shared databases.
```csharp
// Catalog uses PostgreSQL
builder.AddNpgsqlDbContext<CatalogDbContext>("catalogdb");
// Order uses SQL Server
builder.AddSqlServerDbContext<OrderDbContext>("orderdb");
// Notification uses Cosmos DB
builder.AddCosmosDbContext<NotificationDbContext>("cosmosdb");
```
## Communication Patterns
### Synchronous (HTTP/gRPC)
- Use for queries that need immediate response
- gRPC for internal service-to-service (performance)
- REST for external APIs and BFF
### Asynchronous (Messages/Events)
- Use for commands that can be processed later
- Event-driven for loose coupling between services
- RabbitMQ or Azure Service Bus for message broker
### CQRS Pattern
```csharp
// Command side
public sealed record CreateOrderCommand(int CustomerId, List<OrderItemDto> Items);
public sealed class CreateOrderHandler(OrderDbContext db, IPublishEndpoint bus)
{
public async Task<int> HandleAsync(CreateOrderCommand command, CancellationToken ct)
{
var order = Order.Create(command.CustomerId, command.Items);
db.Orders.Add(order);
await db.SaveChangesAsync(ct);
await bus.Publish(new OrderCreatedEvent(order.Id, order.CustomerId), ct);
return order.Id;
}
}
// Query side (separate read model, possibly different DB)
public sealed class OrderQueryService(IReadOnlyDbContext readDb)
{
public async Task<OrderDetailDto?> GetByIdAsync(int id, CancellationToken ct) =>
await readDb.Orders
.AsNoTracking()
.Where(o => o.Id == id)
.Select(o => o.ToDetailDto())
.FirstOrDefaultAsync(ct);
}
```
### Saga Pattern (Orchestration)
```csharp
// Using MassTransit state machine
public sealed class OrderSaga : MassTransitStateMachine<OrderSagaState>
{
public OrderSaga()
{
InstanceState(x => x.CurrentState);
Event(() => OrderCreated, x => x.CorrelateById(m => m.Message.OrderId));
Event(() => PaymentProcessed, x => x.CorrelateById(m => m.Message.OrderId));
Event(() => InventoryReserved, x => x.CorrelateById(m => m.Message.OrderId));
Initially(
When(OrderCreated)
.Then(ctx => ctx.Saga.OrderId = ctx.Message.OrderId)
.Publish(ctx => new ProcessPaymentCommand(ctx.Saga.OrderId))
.TransitionTo(AwaitingPayment));
During(AwaitingPayment,
When(PaymentProcessed)
.Publish(ctx => new ReserveInventoryCommand(ctx.Saga.OrderId))
.TransitionTo(AwaitingInventory),
When(PaymentFailed)
.Publish(ctx => new CancelOrderCommand(ctx.Saga.OrderId))
.TransitionTo(Failed));
During(AwaitingInventory,
When(InventoryReserved)
.Publish(ctx => new FulfillOrderCommand(ctx.Saga.OrderId))
.TransitionTo(Completed));
}
}
```
## Resilience Patterns
```csharp
// Microsoft.Extensions.Resilience + Polly v8
builder.Services.AddHttpClient<ICatalogClient>(client =>
client.BaseAddress = new("https+http://catalog-api"))
.AddStandardResilienceHandler(); // Retry + Circuit Breaker + Timeout
// Custom pipeline
builder.Services.AddResiliencePipeline("custom", pipeline =>
{
pipeline
.AddRetry(new() { MaxRetryAttempts = 3, BackoffType = DelayBackoffType.Exponential })
.AddCircuitBreaker(new() { FailureRatio = 0.5, MinimumThroughput = 10 })
.AddTimeout(TimeSpan.FromSeconds(5));
});
```
## Health Checks
```csharp
builder.Services.AddHealthChecks()
.AddNpgSql(connectionString, name: "database")
.AddRedis(redisConnection, name: "cache")
.AddRabbitMQ(rabbitConnection, name: "messaging")
.AddCheck<CustomHealthCheck>("custom");
app.MapHealthChecks("/health/ready", new() { Predicate = check => check.Tags.Contains("ready") });
app.MapHealthChecks("/health/live", new() { Predicate = _ => false }); // Just checks app is running
```
## API Gateway vs Direct Communication
| Pattern | Use when | Trade-offs |
|---------|----------|------------|
| **Direct client-to-service** | Few services, internal apps | Simple but couples clients to services |
| **API Gateway (YARP/Ocelot)** | Many services, external clients | Single entry point, adds latency |
| **BFF (Backend for Frontend)** | Multiple client types (web, mobile) | Client-optimized APIs, more gateways |
```csharp
// YARP reverse proxy (Microsoft's recommended API gateway)
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
app.MapReverseProxy();
```
## Data Sovereignty Per Microservice
Each service owns its data. Cross-service data needs are resolved via:
- **API calls** for real-time queries
- **Integration events** for eventual consistency
- **Materialized views** for read-heavy cross-service queries
```
Order Service ──(event)──> Catalog Service
│ OrderDB │ CatalogDB
│ (orders, items) │ (products, stock)
│ │
└──(HTTP)──> Payment Service
│ PaymentDB
│ (payments, refunds)
```
## Asynchronous Message-Based Communication
```csharp
// Integration events cross service boundaries
public abstract record IntegrationEvent
{
public Guid Id { get; } = Guid.NewGuid();
public DateTime CreatedAt { get; } = DateTime.UtcNow;
}
public sealed record OrderSubmittedIntegrationEvent(
int OrderId, int BuyerId, decimal Total) : IntegrationEvent;
// Publish via outbox pattern for reliability
public sealed class OutboxPublisher(AppDbContext db, IEventBus bus)
{
public async Task PublishPendingEventsAsync(CancellationToken ct)
{
var pending = await db.OutboxMessages
.Where(m => !m.Published)
.OrderBy(m => m.CreatedAt)
.Take(50)
.ToListAsync(ct);
foreach (var message in pending)
{
await bus.PublishAsync(message.Event, ct);
message.Published = true;
message.PublishedAt = DateTime.UtcNow;
}
await db.SaveChangesAsync(ct);
}
}
```
## Composite UI (Micro-Frontends with Blazor)
Each service can own a UI fragment:
```razor
@* Main Blazor app composes service-specific components *@
<CatalogProductList /> @* Owned by Catalog team *@
<OrderStatusWidget /> @* Owned by Order team *@
<CartSummary /> @* Owned by Cart team *@
```
Pattern options:
- **Server-side composition**: Aggregate HTML from multiple services
- **Client-side composition**: Load Blazor components from different assemblies
- **API composition**: BFF aggregates data, single Blazor app renders
## Microservice Security
```csharp
// JWT validation at API gateway
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://identity-service";
options.Audience = "catalog-api";
});
// Azure Key Vault for secrets
builder.Configuration.AddAzureKeyVault(
new Uri("https://myvault.vault.azure.net/"),
new DefaultAzureCredential());
```
## Reference Documentation
- Architecture patterns: https://learn.microsoft.com/en-us/dotnet/architecture/microservices/architect-microservice-container-applications/
- API Gateway pattern: https://learn.microsoft.com/en-us/dotnet/architecture/microservices/architect-microservice-container-applications/direct-client-to-microservice-communication-versus-the-api-gateway-pattern
- Data sovereignRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.