Devizur
All insightsEngineering

From Monolith to Modular Architecture: Building Software That Can Grow

Why modular monoliths often give startups better boundaries with lower operational cost than premature microservices, and how to preserve an extraction path.

Soud Al Raihan7 May 20263 min read
MonolithDomain ModulesEventsOptional Services

History and context

Early web apps
Single-deployment applications were the default and often mixed presentation, data access and business rules in one codebase.
Service-oriented era
Enterprise systems introduced service boundaries, integration buses and remote contracts.
Microservices era
Independent deployment and team ownership became major design goals, but operational complexity increased.
Current pragmatic view
A well-designed monolith can remain a valid deployment model; boundaries and coupling matter more than the number of processes.

Why monoliths gained a bad reputation

The failure mode is not one deployment. It is unrestricted dependency.

Typical failure pattern
Controller
  -> Shared Service
     -> Another Shared Service
        -> Repository
           -> Any Table

What microservices buy and what they cost

They buy

  • Independent deployment
  • Independent scaling
  • Harder data boundaries
  • Team autonomy

They cost

  • Network failure modes
  • Eventual consistency
  • Distributed tracing
  • Message delivery concerns
  • More infrastructure

The modular monolith middle path

ASP.NET Core HostCatalogPricingOrdersInventoryPostgreSQL
A modular monolith preserves one deployable unit while separating business capabilities.

Business-first solution structure

.NET solution
src/
  Platform.Api/

  Modules/
    Catalog/
      Catalog.Domain/
      Catalog.Application/
      Catalog.Infrastructure/

    Orders/
      Orders.Domain/
      Orders.Application/
      Orders.Infrastructure/

    Pricing/
      Pricing.Domain/
      Pricing.Application/
      Pricing.Infrastructure/

Keep important rules close to the domain

C#
public sealed class Order
{
    public OrderStatus Status { get; private set; }

    public void Confirm()
    {
        if (Status != OrderStatus.Pending)
            throw new InvalidOperationException(
                $"Cannot confirm order in {Status} state.");

        Status = OrderStatus.Confirmed;
    }
}

Use internal events before network services

C#
public sealed record OrderConfirmed(
    long OrderId,
    int ShopId,
    IReadOnlyCollection<OrderConfirmedLine> Lines);

An in-process event can decouple modules conceptually. It does not need a broker until asynchronous delivery or deployment independence becomes a real requirement.

Database ownership still matters

Schemas
catalog.product
catalog.category

sales.order
sales.order_line

pricing.sales_offer
pricing.sales_offer_target

Sharing one PostgreSQL server is not permission to ignore ownership. Cross-module updates should go through contracts, because database coupling is often harder to remove than code coupling.

When should a module become a service?

Strong reasons include independent scaling, reliability isolation, separate team ownership, a different technology requirement, or regulatory separation. "We may become large" is not a sufficient engineering requirement.

Core Modular PlatformDomain EventsReporting ServiceAnalytics Store
Extract one capability when evidence supports independence; leave the rest simple.

Business value and practical considerations

Pros

  • Simple deployment
  • Easier transactions
  • Low infrastructure overhead
  • Clear domain boundaries
  • Future extraction path

Considerations

  • Boundary discipline is social + technical
  • One deployment remains shared
  • Independent scaling is limited
  • Database access must be governed
SA
Soud Al RaihanSoftware architecture · Devizur

References