System Architecture · How it works
3 min readHow it works
Choose a system topology that matches scale, team size, and operational maturity.
Detailed Explanation
Modular Monolith
What it is: A single deployment with strict module boundaries (e.g., Orders, Pricing, Risk).
Pros:
- Simple deployment and debugging
- Strong consistency and transactions
- Clear boundaries without distributed complexity
Cons:
- Scaling is all-or-nothing
- Boundary violations creep in without discipline
Use it when: Team is small to medium and you want fast delivery with strong consistency.
Microservices Architecture
What it is: Independent services owning their data and deploy lifecycle.
[Keycloak IDP]
|
JWT Tokens
|
[API Gateway] -----> Authentication
|
+----|----+---------+---------+
| | | |
[Identity] [Orders] [Content] [Menu]
| | | |
[DB] [DB] [S3] [DB]
Pros:
- Independent scaling and deployments
- Clear ownership boundaries
- Technology flexibility per service
- Team autonomy
Cons:
- Distributed systems complexity
- Data consistency challenges
- Higher operational overhead (observability, tracing, CI/CD)
Key Components:
| Component | Purpose |
|---|---|
| API Gateway | Single entry point, routing, rate limiting, auth |
| Service Mesh | Service-to-service communication, mTLS |
| Message Broker | Async communication (RabbitMQ, Kafka) |
| Distributed Cache | Redis for cross-service caching |
| Identity Provider | Centralized auth (Keycloak, Auth0) |
Use it when: You have multiple teams, different scaling needs, and strong DevOps maturity.
Event-Driven Architecture
What it is: Services publish events; consumers react asynchronously.
[Order Service] ---> [Message Broker] ---> [Notification Service]
|
+-----------> [Analytics Service]
|
+-----------> [Billing Service]
Event Patterns:
| Pattern | Description | Use Case |
|---|---|---|
| Pub/Sub | One-to-many broadcasting | Notifications, cache invalidation |
| Event Sourcing | Store events as source of truth | Audit, replay capability |
| CQRS | Separate read/write models | High-read workloads |
| Saga | Distributed transactions via events | Multi-service workflows |
| Outbox | Reliable event publishing | Consistency guarantees |
Pros:
- Loose coupling between services
- Natural fit for streaming (price ticks, trade events)
- Resilient under bursts
- Easy to add new consumers
Cons:
- Eventual consistency
- Harder debugging without tracing
- Need idempotency and deduplication
- Event schema evolution challenges
Use it when: You need high throughput, asynchronous workflows, or streaming pipelines.
API Gateway Pattern
What it is: Single entry point that routes requests to appropriate microservices.
[Mobile App] [Web App] [Third Party]
\ | /
\ | /
+--------+---------+
|
[API Gateway]
- Authentication
- Rate Limiting
- Request Routing
- Response Aggregation
|
+------------+------------+
| | |
[Service A] [Service B] [Service C]
Responsibilities:
- Authentication/Authorization: Validate JWT tokens, enforce policies
- Rate Limiting: Protect services from abuse, per-tenant limits
- Request Routing: Route to appropriate backend service
- Response Aggregation: Combine multiple service responses (BFF pattern)
- Protocol Translation: REST to gRPC, WebSocket proxying
- Caching: Edge caching for common requests
Technologies:
- YARP (C# reverse proxy)
- Kong, AWS API Gateway
- Azure API Management
- Nginx, Envoy
Use it when: You have multiple backend services and need unified edge policies.
Multi-Tenant SaaS Architecture
What it is: Single application instance serving multiple tenants with data isolation.
Isolation Strategies:
| Strategy | Description | Pros | Cons |
|---|---|---|---|
| Row-Level | Tenant ID column, query filtering | Cost efficient, simple | Filter discipline required |
| Schema-Level | Separate schema per tenant | Better isolation | More complex migrations |
| Database-Level | Separate DB per tenant | Complete isolation | Higher cost, more ops |
Row-Level Implementation (EF Core):
// Automatic tenant filtering in DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>().HasQueryFilter(e =>
_currentTenantService.TenantId == null || // SuperUser bypass
e.TenantId == _currentTenantService.TenantId);
}
Tenant Resolution:
- JWT claims (
tenant_idclaim) - Subdomain (
tenant1.app.com) - Header (
X-Tenant-Id) - Path segment (
/api/tenants/{tenantId}/orders)
Key Considerations:
- Data Isolation: Prevent cross-tenant data access
- Resource Limits: Per-tenant quotas, rate limiting
- Configuration: Tenant-specific settings, feature flags
- Billing/Metering: Track usage per tenant
- Onboarding: Self-service vs manual provisioning
Micro-kernel (Plugin Architecture)
What it is: A small core with extensible plugins that follow a strict contract.
Pros:
- Extend behavior without changing the core
- Clear contract for integrations and client-specific behavior
Cons:
- Plugin lifecycle and versioning complexity
- Requires strict API contracts and discipline
Use it when: You need a stable core with customizable extensions (e.g., per-client risk engines, pluggable pricing strategies, tenant-specific tax rules).
Layered / N-Tier
What it is: Classic tier separation (UI, business, data) often deployed together as a single unit.
Pros:
- Easy to understand for newcomers
- Works well for small internal apps
Cons:
- Can become tightly coupled across tiers
- Hard to scale individual tiers independently
- Layers tend to leak responsibilities over time
Use it when: Small internal apps where the cost of any other architecture isn't justified.