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