Modular Monolith / Microservices
Overview
FlexBase provides a unique approach to application architecture that starts with a modular monolith by default and can seamlessly evolve into true microservices when needed. This evolutionary architecture pattern allows you to start simple and scale to enterprise levels without the complexity of microservices from day one.
The Evolution Path
Phase 1: Modular Monolith β Phase 2: Distributed Monolith β Phase 3: True Microservices
(Single Application) (Shared Database) (Independent Services)
FlexBase Philosophy: Start with a modular monolith, evolve to microservices when business requirements demand it.
Modular Monolith: The FlexBase Default
What is a Modular Monolith?
A modular monolith is a single application that is internally organized into loosely coupled modules. Each module has clear boundaries and can be developed, tested, and deployed independently, but they all run within the same process.
FlexBase Modular Monolith Structure
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FlexBase Modular Monolith β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββ β
β β ECommerce β β Payment β β Shipping β β User β β
β β Module β β Module β β Module β β Module β β
β βββββββββββββββ€ βββββββββββββββ€ βββββββββββββββ€ βββββββββββ€ β
β β β’ Orders β β β’ Payments β β β’ Shipments β β β’ Users β β
β β β’ Products β β β’ Refunds β β β’ Tracking β β β’ Auth β β
β β β’ Inventory β β β’ Billing β β β’ Logistics β β β’ Roles β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Shared Database Layer β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββ β
β β Orders β β Payments β β Shipments β β Users β β
β β Tables β β Tables β β Tables β β Tables β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
FlexBase Modular Monolith Benefits
1. Clear Module Boundaries
// ECommerce Module
namespace EBusiness.Modules.ECommerce
{
public class OrdersController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> CreateOrder(OrderDto order)
{
return await RunService(201, order, _processOrdersService.CreateOrder);
}
}
}
// Payment Module
namespace EBusiness.Modules.Payment
{
public class PaymentsController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> ProcessPayment(PaymentDto payment)
{
return await RunService(200, payment, _processPaymentsService.ProcessPayment);
}
}
}
2. Independent Module Development
Separate Teams: Each module can be developed by different teams
Independent Testing: Test each module in isolation
Module-Specific Dependencies: Each module manages its own dependencies
Clear Interfaces: Well-defined contracts between modules
3. Shared Infrastructure
Single Database: All modules share the same database
Shared Message Bus: Common message queue for inter-module communication
Unified Monitoring: Single monitoring and logging system
Common Configuration: Shared configuration management
FlexBase Module Communication Patterns
1. Synchronous Communication (Within Monolith)
// Direct service calls within the same application
public class OrderService
{
private readonly IPaymentService _paymentService;
private readonly IInventoryService _inventoryService;
public async Task<Order> CreateOrder(OrderDto order)
{
// Synchronous calls within the same process
await _inventoryService.ReserveInventory(order.Items);
var payment = await _paymentService.ProcessPayment(order.Payment);
return await SaveOrder(order, payment);
}
}
2. Asynchronous Communication (Event-Driven)
// Event-driven communication between modules
public class OrderCreatedHandler : IOrderCreatedHandler
{
public async Task Execute(OrderCreatedEvent @event, IFlexServiceBusContext context)
{
// Fire events to other modules
await this.Fire<InventoryReservationCommand>(EventCondition, context);
await this.Fire<PaymentProcessingCommand>(EventCondition, context);
await this.Fire<ShippingNotificationCommand>(EventCondition, context);
}
}
Modular Monolith Advantages
1. Development Simplicity
Single Deployment: Deploy one application
Shared Dependencies: Common libraries and frameworks
Unified Testing: Test the entire application together
Simple Debugging: Single process to debug
2. Performance Benefits
No Network Overhead: Direct method calls
Shared Memory: Efficient data sharing
Single Database Connection: Reduced connection overhead
Fast Communication: In-process communication
3. Operational Simplicity
Single Monitoring: One application to monitor
Unified Logging: All logs in one place
Simple Deployment: One deployment pipeline
Easy Rollback: Single application rollback
True Microservices: The Next Evolution
When to Evolve to Microservices
Business Triggers
Team Growth: Multiple teams need independent deployment
Technology Diversity: Different modules need different technologies
Scaling Requirements: Different modules have different scaling needs
Geographic Distribution: Modules need to be deployed in different regions
Technical Triggers
Database Bottlenecks: Single database becomes a bottleneck
Deployment Conflicts: Teams block each other during deployments
Technology Constraints: Different modules need different tech stacks
Performance Isolation: Need to isolate performance issues
FlexBase Microservices Architecture
True Microservices Structure
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β ECommerce β β Payment β β Shipping β β User β
β Microservice β β Microservice β β Microservice β β Microservice β
βββββββββββββββββββ€ βββββββββββββββββββ€ βββββββββββββββββββ€ βββββββββββββββββββ€
β β’ Orders API β β β’ Payments API β β β’ Shipping API β β β’ Users API β
β β’ Products API β β β’ Billing API β β β’ Tracking API β β β’ Auth API β
β β’ Inventory API β β β’ Refunds API β β β’ Logistics API β β β’ Roles API β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β β β β
βΌ βΌ βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β ECommerce DB β β Payment DB β β Shipping DB β β User DB β
β (Independent) β β (Independent) β β (Independent) β β (Independent) β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β β β β
βββββββββββββββββββββββββΌββββββββββββββββββββββββΌββββββββββββββββββββββββ
β β
βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ
β Message Bus β β API Gateway β
β (Events) β β (Routing) β
βββββββββββββββββββ βββββββββββββββββββ
FlexBase Microservices Implementation
Each microservice is a completely independent FlexBase application with its own:
Database: Independent database schema and data
Message Bus: Own message queue configuration
Endpoints: Separate WebAPI, Handlers, and Subscribers
Configuration: Independent appsettings and environment configs
Deployment: Independent deployment pipeline
Scaling: Independent scaling configuration
1. ECommerce Microservice - Independent FlexBase Application
Project Structure:
ECommerce.Service/
βββ ECommerce.EndPoint.WebAPI/ # WebAPI Endpoint
βββ ECommerce.EndPoint.Handlers/ # Command Handlers
βββ ECommerce.EndPoint.Subscribers/ # Event Subscribers
βββ ECommerce.Domain/ # Domain Models
βββ ECommerce.Application/ # Application Services
βββ ECommerce.Infrastructure/ # Database & Message Bus
WebAPI Endpoint:
// ECommerce Microservice - Independent FlexBase Application
namespace ECommerce.Service.EndPoint.WebAPI
{
public class OrdersController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> CreateOrder(OrderDto order)
{
// Process order using FlexBase patterns
return await RunService(201, order, _processOrdersService.CreateOrder);
}
[HttpGet]
public async Task<IActionResult> GetOrder(string orderId)
{
return await RunService(200, new GetOrderDto { Id = orderId }, _queryOrdersService.GetOrderById);
}
}
}
Command Handler:
// ECommerce Command Handler - Independent Processing
public class CreateOrderHandler : ICreateOrderHandler
{
public async Task Execute(CreateOrderCommand cmd, IFlexServiceBusContext context)
{
// Process order in ECommerce service
_model = _flexHost.GetDomainModel<Order>().CreateOrder(cmd);
_repoFactory.GetRepo().InsertOrUpdate(_model);
await _repoFactory.GetRepo().SaveAsync();
// Publish event to other microservices
await this.Fire<OrderCreatedEvent>(EventCondition, context);
}
}
Configuration:
{
"FlexBase": {
"AppDbConnection": "Data Source=localhost;Initial Catalog=ECommerceDb;...",
"AppReadDbConnection": "Data Source=localhost;Initial Catalog=ECommerceReadDb;...",
"RabbitMqConnectionString": "amqp://localhost:5672/ecommerce"
}
}
2. Payment Microservice - Independent FlexBase Application
Project Structure:
Payment.Service/
βββ Payment.EndPoint.WebAPI/ # WebAPI Endpoint
βββ Payment.EndPoint.Handlers/ # Command Handlers
βββ Payment.EndPoint.Subscribers/ # Event Subscribers
βββ Payment.Domain/ # Domain Models
βββ Payment.Application/ # Application Services
βββ Payment.Infrastructure/ # Database & Message Bus
WebAPI Endpoint:
// Payment Microservice - Independent FlexBase Application
namespace Payment.Service.EndPoint.WebAPI
{
public class PaymentsController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> ProcessPayment(PaymentDto payment)
{
// Process payment using FlexBase patterns
return await RunService(200, payment, _processPaymentsService.ProcessPayment);
}
[HttpGet]
public async Task<IActionResult> GetPayment(string paymentId)
{
return await RunService(200, new GetPaymentDto { Id = paymentId }, _queryPaymentsService.GetPaymentById);
}
}
}
Event Subscriber:
// Payment Event Subscriber - Reacts to ECommerce Events
public class OrderCreatedHandler : IOrderCreatedHandler
{
public async Task Execute(OrderCreatedEvent @event, IFlexServiceBusContext context)
{
// Process payment for the order
var paymentCommand = new ProcessPaymentCommand
{
OrderId = @event.OrderId,
Amount = @event.TotalAmount,
CustomerId = @event.CustomerId
};
await _processPaymentsService.ProcessPayment(paymentCommand);
}
}
Configuration:
{
"FlexBase": {
"AppDbConnection": "Data Source=localhost;Initial Catalog=PaymentDb;...",
"AppReadDbConnection": "Data Source=localhost;Initial Catalog=PaymentReadDb;...",
"RabbitMqConnectionString": "amqp://localhost:5672/payment"
}
}
3. Shipping Microservice - Independent FlexBase Application
Project Structure:
Shipping.Service/
βββ Shipping.EndPoint.WebAPI/ # WebAPI Endpoint
βββ Shipping.EndPoint.Handlers/ # Command Handlers
βββ Shipping.EndPoint.Subscribers/ # Event Subscribers
βββ Shipping.Domain/ # Domain Models
βββ Shipping.Application/ # Application Services
βββ Shipping.Infrastructure/ # Database & Message Bus
WebAPI Endpoint:
// Shipping Microservice - Independent FlexBase Application
namespace Shipping.Service.EndPoint.WebAPI
{
public class ShipmentsController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> CreateShipment(ShipmentDto shipment)
{
// Process shipment using FlexBase patterns
return await RunService(201, shipment, _processShipmentsService.CreateShipment);
}
[HttpGet]
public async Task<IActionResult> TrackShipment(string trackingNumber)
{
return await RunService(200, new TrackShipmentDto { TrackingNumber = trackingNumber }, _queryShipmentsService.TrackShipment);
}
}
}
Event Subscriber:
// Shipping Event Subscriber - Reacts to Payment Events
public class PaymentProcessedHandler : IPaymentProcessedHandler
{
public async Task Execute(PaymentProcessedEvent @event, IFlexServiceBusContext context)
{
if (@event.Status == PaymentStatus.Success)
{
// Create shipment for paid order
var shipmentCommand = new CreateShipmentCommand
{
OrderId = @event.OrderId,
CustomerId = @event.CustomerId,
Items = @event.Items
};
await _processShipmentsService.CreateShipment(shipmentCommand);
}
}
}
Configuration:
{
"FlexBase": {
"AppDbConnection": "Data Source=localhost;Initial Catalog=ShippingDb;...",
"AppReadDbConnection": "Data Source=localhost;Initial Catalog=ShippingReadDb;...",
"RabbitMqConnectionString": "amqp://localhost:5672/shipping"
}
}
3. Event-Driven Communication
// ECommerce Microservice - Event Handler
public class PaymentProcessedHandler : IPaymentProcessedHandler
{
public async Task Execute(PaymentProcessedEvent @event, IFlexServiceBusContext context)
{
// Update order status based on payment
if (@event.Status == PaymentStatus.Success)
{
await _orderService.UpdateOrderStatus(@event.OrderId, OrderStatus.Paid);
await this.Fire<OrderPaidEvent>(EventCondition, context);
}
else
{
await _orderService.UpdateOrderStatus(@event.OrderId, OrderStatus.PaymentFailed);
await this.Fire<OrderPaymentFailedEvent>(EventCondition, context);
}
}
}
Microservices Aggregator: Consolidating Services for Clients
The Aggregator Pattern
When you have multiple independent FlexBase microservices, you need an Aggregator Service to consolidate data and provide a unified API for client applications. The aggregator acts as a composite service that orchestrates calls to multiple microservices and combines their responses.
Aggregator Architecture
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β Client App β β Mobile App β β Web App β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β β β
βββββββββββββββββββββββββΌββββββββββββββββββββββββ
β
βββββββββββββββββββ
β Aggregator β
β Service β
β (FlexBase) β
βββββββββββββββββββ
β
βββββββββββββββββββββββββΌββββββββββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β ECommerce β β Payment β β Shipping β
β Microservice β β Microservice β β Microservice β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
FlexBase Aggregator Service Implementation
Aggregator Project Structure
Aggregator.Service/
βββ Aggregator.EndPoint.WebAPI/ # WebAPI Endpoint
βββ Aggregator.EndPoint.Handlers/ # Command Handlers
βββ Aggregator.EndPoint.Subscribers/ # Event Subscribers
βββ Aggregator.Application/ # Aggregation Services
βββ Aggregator.Infrastructure/ # HTTP Clients & Configuration
βββ Aggregator.Shared/ # Shared DTOs & Models
Aggregator Controller - Unified API
// Aggregator Service - Consolidates Multiple Microservices
namespace Aggregator.Service.EndPoint.WebAPI
{
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly IOrderAggregationService _orderAggregationService;
[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
{
// Orchestrate order creation across multiple services
var result = await _orderAggregationService.CreateOrderAsync(request);
return Ok(result);
}
[HttpGet("{orderId}")]
public async Task<IActionResult> GetOrderDetails(string orderId)
{
// Aggregate data from multiple services
var orderDetails = await _orderAggregationService.GetOrderDetailsAsync(orderId);
return Ok(orderDetails);
}
[HttpGet("{orderId}/status")]
public async Task<IActionResult> GetOrderStatus(string orderId)
{
// Get comprehensive order status
var status = await _orderAggregationService.GetOrderStatusAsync(orderId);
return Ok(status);
}
}
}
Order Aggregation Service
// Order Aggregation Service - Orchestrates Multiple Microservices
public class OrderAggregationService : IOrderAggregationService
{
private readonly IECommerceServiceClient _ecommerceClient;
private readonly IPaymentServiceClient _paymentClient;
private readonly IShippingServiceClient _shippingClient;
public async Task<OrderDetailsResponse> GetOrderDetailsAsync(string orderId)
{
// Parallel calls to multiple microservices
var orderTask = _ecommerceClient.GetOrderAsync(orderId);
var paymentTask = _paymentClient.GetPaymentByOrderIdAsync(orderId);
var shipmentTask = _shippingClient.GetShipmentByOrderIdAsync(orderId);
await Task.WhenAll(orderTask, paymentTask, shipmentTask);
// Aggregate responses
return new OrderDetailsResponse
{
Order = await orderTask,
Payment = await paymentTask,
Shipment = await shipmentTask,
Status = DetermineOverallStatus(await orderTask, await paymentTask, await shipmentTask)
};
}
public async Task<CreateOrderResponse> CreateOrderAsync(CreateOrderRequest request)
{
// Step 1: Create order in ECommerce service
var orderResult = await _ecommerceClient.CreateOrderAsync(new CreateOrderDto
{
CustomerId = request.CustomerId,
Items = request.Items,
TotalAmount = request.TotalAmount
});
// Step 2: Process payment
var paymentResult = await _paymentClient.ProcessPaymentAsync(new ProcessPaymentDto
{
OrderId = orderResult.OrderId,
Amount = orderResult.TotalAmount,
PaymentMethod = request.PaymentMethod
});
// Step 3: Create shipment if payment successful
ShipmentDto shipmentResult = null;
if (paymentResult.Status == PaymentStatus.Success)
{
shipmentResult = await _shippingClient.CreateShipmentAsync(new CreateShipmentDto
{
OrderId = orderResult.OrderId,
CustomerId = request.CustomerId,
Items = request.Items
});
}
return new CreateOrderResponse
{
OrderId = orderResult.OrderId,
PaymentId = paymentResult.PaymentId,
ShipmentId = shipmentResult?.ShipmentId,
Status = DetermineOrderStatus(orderResult, paymentResult, shipmentResult)
};
}
}
HTTP Client Services
// ECommerce Service Client
public class ECommerceServiceClient : IECommerceServiceClient
{
private readonly HttpClient _httpClient;
private readonly ILogger<ECommerceServiceClient> _logger;
public async Task<OrderDto> GetOrderAsync(string orderId)
{
var response = await _httpClient.GetAsync($"/api/orders/{orderId}");
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<OrderDto>(content);
}
public async Task<CreateOrderResponse> CreateOrderAsync(CreateOrderDto order)
{
var json = JsonSerializer.Serialize(order);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync("/api/orders", content);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<CreateOrderResponse>(responseContent);
}
}
// Payment Service Client
public class PaymentServiceClient : IPaymentServiceClient
{
private readonly HttpClient _httpClient;
private readonly ILogger<PaymentServiceClient> _logger;
public async Task<PaymentDto> GetPaymentByOrderIdAsync(string orderId)
{
var response = await _httpClient.GetAsync($"/api/payments/order/{orderId}");
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<PaymentDto>(content);
}
public async Task<ProcessPaymentResponse> ProcessPaymentAsync(ProcessPaymentDto payment)
{
var json = JsonSerializer.Serialize(payment);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync("/api/payments", content);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<ProcessPaymentResponse>(responseContent);
}
}
Aggregator Configuration
{
"FlexBase": {
"AppDbConnection": "Data Source=localhost;Initial Catalog=AggregatorDb;...",
"AppReadDbConnection": "Data Source=localhost;Initial Catalog=AggregatorReadDb;...",
"RabbitMqConnectionString": "amqp://localhost:5672/aggregator"
},
"Microservices": {
"ECommerce": {
"BaseUrl": "https://ecommerce-service.yourcompany.com",
"ApiKey": "your-ecommerce-api-key",
"Timeout": "00:00:30"
},
"Payment": {
"BaseUrl": "https://payment-service.yourcompany.com",
"ApiKey": "your-payment-api-key",
"Timeout": "00:00:30"
},
"Shipping": {
"BaseUrl": "https://shipping-service.yourcompany.com",
"ApiKey": "your-shipping-api-key",
"Timeout": "00:00:30"
}
}
}
HTTP Client Registration
// Program.cs - Register HTTP Clients
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Register HTTP clients for microservices
builder.Services.AddHttpClient<IECommerceServiceClient, ECommerceServiceClient>(client =>
{
var config = builder.Configuration.GetSection("Microservices:ECommerce");
client.BaseAddress = new Uri(config["BaseUrl"]);
client.DefaultRequestHeaders.Add("X-API-Key", config["ApiKey"]);
client.Timeout = TimeSpan.Parse(config["Timeout"]);
});
builder.Services.AddHttpClient<IPaymentServiceClient, PaymentServiceClient>(client =>
{
var config = builder.Configuration.GetSection("Microservices:Payment");
client.BaseAddress = new Uri(config["BaseUrl"]);
client.DefaultRequestHeaders.Add("X-API-Key", config["ApiKey"]);
client.Timeout = TimeSpan.Parse(config["Timeout"]);
});
builder.Services.AddHttpClient<IShippingServiceClient, ShippingServiceClient>(client =>
{
var config = builder.Configuration.GetSection("Microservices:Shipping");
client.BaseAddress = new Uri(config["BaseUrl"]);
client.DefaultRequestHeaders.Add("X-API-Key", config["ApiKey"]);
client.Timeout = TimeSpan.Parse(config["Timeout"]);
});
// Register aggregation services
builder.Services.AddScoped<IOrderAggregationService, OrderAggregationService>();
var app = builder.Build();
app.Run();
}
}
Aggregator Benefits
1. Unified API for Clients
Single Endpoint: Clients call one API instead of multiple microservices
Consistent Interface: Standardized request/response format
Simplified Integration: Easier for client applications to integrate
2. Data Aggregation
Combined Responses: Merge data from multiple services
Business Logic: Implement cross-service business rules
Data Transformation: Convert service-specific data to client-friendly format
3. Error Handling & Resilience
Circuit Breaker: Handle service failures gracefully
Retry Logic: Retry failed calls with exponential backoff
Fallback Data: Provide partial data when some services are unavailable
4. Performance Optimization
Parallel Calls: Make concurrent calls to multiple services
Caching: Cache frequently accessed data
Response Compression: Compress responses for better performance
Microservices Communication Patterns
1. API Gateway Pattern
# API Gateway Configuration
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-gateway
spec:
rules:
- host: api.yourcompany.com
http:
paths:
- path: /api/orders
pathType: Prefix
backend:
service:
name: aggregator-service
port:
number: 80
- path: /ecommerce
pathType: Prefix
backend:
service:
name: ecommerce-service
port:
number: 80
- path: /payment
pathType: Prefix
backend:
service:
name: payment-service
port:
number: 80
- path: /shipping
pathType: Prefix
backend:
service:
name: shipping-service
port:
number: 80
2. Event-Driven Communication
// Event Bus Configuration
public class EventBusConfiguration
{
public void ConfigureServices(IServiceCollection services)
{
services.AddNServiceBus(cfg =>
{
cfg.Transport(transport =>
{
transport.UseRabbitMQ(connectionString: "amqp://localhost:5672");
});
cfg.Routing(routing =>
{
routing.RouteToEndpoint(typeof(OrderCreatedEvent), "ECommerce.Service");
routing.RouteToEndpoint(typeof(PaymentProcessedEvent), "Payment.Service");
routing.RouteToEndpoint(typeof(ShippingCreatedEvent), "Shipping.Service");
});
});
}
}
3. Database per Service
// Each microservice has its own database
public class ECommerceDbContext : DbContext
{
public DbSet<Order> Orders { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Inventory> Inventory { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(Configuration.GetConnectionString("ECommerceDb"));
}
}
public class PaymentDbContext : DbContext
{
public DbSet<Payment> Payments { get; set; }
public DbSet<Refund> Refunds { get; set; }
public DbSet<Billing> Billing { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(Configuration.GetConnectionString("PaymentDb"));
}
}
Microservices Advantages
1. Independent Deployment
Team Autonomy: Each team can deploy independently
Technology Freedom: Use different technologies per service
Scaling Independence: Scale each service based on its needs
Fault Isolation: Failure in one service doesn't affect others
2. Technology Diversity
// ECommerce Service - .NET Core
public class OrdersController : ControllerBase { }
// Payment Service - Node.js
app.post('/api/payments', (req, res) => { });
// Shipping Service - Python
@app.route('/api/shipping', methods=['POST'])
def create_shipment(): pass
3. Data Independence
Database per Service: Each service owns its data
Data Consistency: Eventual consistency through events
Technology Choice: Different databases per service
Independent Schema: Evolve schemas independently
Migration Strategy: From Modular Monolith to Microservices
Phase 1: Identify Service Boundaries
Domain Analysis
// Analyze existing modules for service boundaries
public class ServiceBoundaryAnalysis
{
public List<ServiceCandidate> AnalyzeModules()
{
return new List<ServiceCandidate>
{
new ServiceCandidate
{
Name = "ECommerce",
Modules = new[] { "Orders", "Products", "Inventory" },
Dependencies = new[] { "Payment", "Shipping" },
DataVolume = "High",
ChangeFrequency = "High"
},
new ServiceCandidate
{
Name = "Payment",
Modules = new[] { "Payments", "Refunds", "Billing" },
Dependencies = new[] { "ECommerce" },
DataVolume = "Medium",
ChangeFrequency = "Low"
}
};
}
}
Phase 2: Extract Services Gradually
Strangler Fig Pattern
// Gradually replace monolith functionality with microservices
public class OrderService
{
private readonly IOrderService _legacyOrderService;
private readonly IOrderService _newOrderService;
private readonly bool _useNewService;
public async Task<Order> CreateOrder(OrderDto order)
{
if (_useNewService)
{
return await _newOrderService.CreateOrder(order);
}
else
{
return await _legacyOrderService.CreateOrder(order);
}
}
}
Phase 3: Implement Event-Driven Communication
Event Sourcing Pattern
// Implement event sourcing for data consistency
public class OrderAggregate
{
private readonly List<IDomainEvent> _events = new();
public void CreateOrder(OrderDto order)
{
// Apply business logic
var orderCreated = new OrderCreatedEvent
{
OrderId = Guid.NewGuid(),
CustomerId = order.CustomerId,
Items = order.Items
};
// Store event
_events.Add(orderCreated);
}
public IEnumerable<IDomainEvent> GetUncommittedEvents()
{
return _events;
}
}
FlexBase: The Best of Both Worlds
Why FlexBase is Perfect for This Evolution
1. Built-in Modularity
Endpoint Architecture: Natural service boundaries
Event-Driven: Ready for microservices communication
CQRS Pattern: Independent read/write operations
Message Queues: Built-in asynchronous communication
2. Seamless Migration
Same Code: Business logic remains unchanged
Configuration-Driven: Switch between monolith and microservices
Gradual Evolution: Move modules to microservices one by one
Risk Mitigation: Test each migration step independently
3. Operational Excellence
Unified Monitoring: Same monitoring for both architectures
Consistent Patterns: Same development patterns
Shared Infrastructure: Leverage existing infrastructure
Team Training: Minimal learning curve for teams
FlexBase Configuration for Architecture Evolution
Modular Monolith Configuration
{
"FlexBase": {
"Architecture": "ModularMonolith",
"Database": {
"Type": "Single",
"ConnectionString": "Data Source=localhost;Initial Catalog=YourApp;..."
},
"MessageBus": {
"Type": "InProcess",
"Configuration": "Local"
}
}
}
Microservices Configuration
{
"FlexBase": {
"Architecture": "Microservices",
"Services": {
"ECommerce": {
"Database": "Data Source=localhost;Initial Catalog=ECommerceDb;...",
"MessageBus": "amqp://localhost:5672"
},
"Payment": {
"Database": "Data Source=localhost;Initial Catalog=PaymentDb;...",
"MessageBus": "amqp://localhost:5672"
}
}
}
}
Real-World Examples
E-Commerce Platform Evolution
Phase 1: Modular Monolith (Startup)
Team Size: 5 developers
Deployment: Single application
Database: Single SQL Server
Communication: Direct method calls
Cost: $500/month
Phase 2: Distributed Monolith (Growth)
Team Size: 15 developers
Deployment: Single application with load balancing
Database: Single database with read replicas
Communication: Events + direct calls
Cost: $2,000/month
Phase 3: Microservices (Scale)
Team Size: 50+ developers
Deployment: Independent services
Database: Database per service
Communication: Event-driven only
Cost: $10,000/month
Financial Services Evolution
Modular Monolith Benefits
Compliance: Single audit trail
Security: Unified security model
Development: Fast feature development
Testing: Comprehensive integration testing
Microservices Benefits
Regulatory: Independent compliance per service
Security: Isolated security per service
Scalability: Scale payment processing independently
Technology: Use specialized financial technologies
Best Practices
1. Start with Modular Monolith
Clear Boundaries: Define module boundaries early
Event-Driven: Use events from the beginning
Database Design: Design for future service extraction
API Design: Design APIs as if they were separate services
2. Plan for Microservices Evolution
Service Identification: Identify potential services early
Data Ownership: Plan data ownership per service
Communication Patterns: Use event-driven communication
Technology Choices: Choose technologies that support both architectures
3. Migration Strategy
Gradual Migration: Move one module at a time
Feature Flags: Use feature flags for gradual rollout
Testing: Test each migration step thoroughly
Rollback Plan: Always have a rollback strategy
4. Operational Considerations
Monitoring: Implement comprehensive monitoring
Logging: Centralized logging across all services
Security: Consistent security model
Deployment: Automated deployment pipelines
Conclusion
FlexBase provides the perfect foundation for both modular monoliths and true microservices. By starting with a modular monolith, you get the benefits of simplicity and performance while maintaining the flexibility to evolve to microservices when business requirements demand it.
The FlexBase Advantage:
Start Simple: Begin with modular monolith
Evolve Naturally: Move to microservices when needed
Same Code: Business logic remains unchanged
Risk Mitigation: Gradual, tested evolution
Best of Both Worlds: Simplicity when you need it, scalability when you want it
Ready to build applications that can evolve with your business? Start with FlexBase and let your architecture grow with your needs! π
Last updated