Around the evolution of IT systems many myths have grown, the greatest of which is the belief in the inevitability and linearity of the transition from monolith to microservices. In reality, system architecture is not a simple, linear path toward distribution — it is a multidimensional set of independent design decisions about system boundaries, deployment strategies, communication models, scaling, and failure-management techniques.

warning

The belief that every mature system must follow the transformation schema Monolith → Microservices → Event-Driven often leads to catastrophically wrong design decisions, generating enormous technical debt and unjustified operational costs.

A more precise model classifies architectural decisions across three independent axes: logical boundaries (Monolith / Modular / Microservices), deployment strategy (single artifact / multiple artifacts), and communication (synchronous / asynchronous). This demonstrates that the decision about logical decomposition is independent of physical packaging and communication protocols. It is possible to build a highly decoupled modular monolith communicating asynchronously, as well as a tightly coupled distributed monolith based on synchronous REST calls.

1. Introduction: Architecture as a Set of Trade-offs

1.1. What Is System Architecture?

Software architecture is not just about choosing technologies or drawing box diagrams. It defines the fundamental operational framework of a system. Its key tasks include defining component responsibility boundaries, designing data-flow structures, and choosing inter-service communication models. Architecture also sets scaling parameters, fault-tolerance strategies (tolerating infrastructure failures), and software deployment approaches. From an organizational perspective, following Conway's Law, architecture directly determines the structure and ownership boundaries of engineering teams.

1.2. There Is No Single Best Architecture

Every design choice in software engineering is a direct trade-off of one benefit for another. Every decision to introduce a new technology or paradigm generates a specific operational cost. The goal of an architect should not be to maximize the technological sophistication of the system. The key to success is identifying the simplest possible architecture that optimally solves real, documented business and infrastructure problems while maintaining an acceptable level of operational complexity.

2. Monolithic Architecture and Its Variants

A monolith is an architectural style in which an application relies on a coherent execution unit sharing CPU resources, memory, and (usually) a database. By eliminating network delays, data serialization requirements, and distributed transactions, it allows focus on business logic. Depending on how code is organized, three main monolith variants and one evolution methodology can be identified.

2.1. Monolith Variants

VariantDescriptionBest forKey advantageKey risk
Classic MonolithAll functional modules compiled and packaged as a single deployment unit. All components share the same database engine.Simple systems, new products (MVP), small teams (<15 devs), high market uncertainty.No overhead for managing code boundaries; fastest operational start.Without discipline, easily degrades into the Big Ball of Mud anti-pattern.
Modular MonolithIntroduces strict, logically protected Bounded Contexts between modules at source-code level. Maintains a shared runtime process and single deployment artifact.Default pattern for most new business systems; teams of 15–50 devs. Proven at scale by Shopify.High code cleanliness, easy evolution, and painless future extraction of modules to microservices.Requires high team discipline and architectural tests (e.g., ArchUnit).
MicromonolithSmall, autonomous, specialized monolith focused on one clear domain area (e.g., authentication). Groups related subdomains within its own process and dedicated database.Stable modules with specific performance or security requirements worth separating from the main application.Small codebase, fast build times, operational isolation from the rest of the platform.Increases number of operational units to monitor; risk of technology silos.
Monolith First (strategy)Every new application should start as a monolith (preferably modular). Distribution happens only when telemetry data proves real performance or organizational problems.Mandatory strategy for startups, greenfield projects, and systems with unstable domains.Protects against the most common mistake: premature microservice decomposition based on false assumptions.Delaying division in poorly managed code can complicate later refactoring and database extraction.

2.2. General Advantages and Disadvantages of Monolithic Architecture

AdvantageDisadvantage
Low operational cost: No "distributed tax" (network delays, timeouts, complex infrastructure).Monotypic scaling: No selective scaling of a single loaded module — must replicate the entire application.
Development and testing simplicity: Easy deployment (one artifact), local execution, and coherent sequential logs.Large blast radius: A critical error (e.g., memory leak) in one module can bring down the entire system.
ACID transactions: Data consistency guaranteed at the database level without distributed Saga patterns.Organizational growth challenges: Many engineers on one repository generate conflicts and deployment bottlenecks.
Fast feedback loops: Easy in-process refactoring without network API versioning.Shared runtime: A failure in one area destabilizes the entire virtual machine.

3. When Does a Monolith Stop Being Enough?

lightbulb

The transition from monolith to distributed architecture should be treated as a last resort. Sam Newman describes microservices as "the last resort." The decision must stem from specific, measurable problems that cannot be solved through code optimization or vertical database scaling.

An architect should make the decomposition decision based on rigorous analysis of these operational questions:

  • Asymmetric scaling: Is there an isolated part of the system (search engine, video processing, report generation) that requires drastically more hardware resources than the rest, forcing costly scaling of the entire monolith?
  • Deployment bottlenecks: Has building, testing, and deploying one large artifact become an organizational bottleneck, paralyzing independent teams and forcing coordinated release windows?
  • Organizational conflicts: Has the developer team structure grown to the point where engineers constantly get in each other's way in a single code repository, generating merge conflicts and paralyzing code review?
  • Different lifecycle requirements: Do functional modules require completely different deployment frequencies (e.g., daily UI changes vs. rare financial engine updates)?
  • Failure isolation requirement: Does a critical failure in a secondary module (e.g., notification queue overflow) regularly cause unavailability of critical business processes (e.g., the shopping cart)?
  • Technology diversification: Does the system require drastically different technologies (e.g., ML models in Python integrated with a transactional system in C#)?
warning

If an honest answer to these questions is "no," introducing microservices will not solve existing problems — it will merely impose the burden of complex distributed infrastructure on the organization.

4. Distributed Architecture — When the Network Appears

4.1. Definition

Distributed architecture is a construction model in which system components run in separate system processes, on different machines or physically separated locations, communicating with each other over a computer network.

4.2. The New Operational Reality

In a monolithic architecture, calling another method occurs directly in device RAM and is fully deterministic. In distributed architecture, every service call becomes a network operation with a high failure risk. A typical distributed request flow involves numerous physical infrastructure components: Request → DNS → Network → Load Balancer → Service → Database → Response. Each link is a potential Single Point of Failure, which fundamentally changes the rules of reliability design.

4.3. Problems Distributed Architecture Solves

  • Eliminates physical performance barriers of a single server (horizontal scaling).
  • Enables full failure isolation — damage to one process does not need to paralyze the rest of the platform.
  • Provides technological and deployment independence of individual components.
  • Enables geographic data distribution to minimize latency for end users.

4.4. Problems Distributed Architecture Introduces

  • Network latency, partition risks, and timeouts — the "distributed tax."
  • Distributed consistency problem: excludes classic ACID transactions, requiring eventual consistency and consensus algorithms (Raft, Paxos).
  • Drastically complicated debugging: requires distributed tracing (Jaeger, OpenTelemetry) and centralized log aggregation.
lightbulb

A distributed system is not difficult to implement because of the large number of components. Its difficulty stems from the fact that these components are forced to communicate through an unreliable network.

5. Microservices

5.1. Definition

A microservice is a small, autonomous, independently deployable and developed service that has full ownership of its data (own database schema) and performs one coherent business task (Bounded Context). Microservices cannot share a database at the physical schema level, and their integration happens exclusively through defined network contracts. This principle excludes the "every class as a separate service" approach, which leads to catastrophic infrastructure fragmentation.

5.2. Problems Microservices Solve

  • Eliminate the lack of team autonomy and deployment bottlenecks in large IT organizations.
  • Enable asymmetric hardware resource scaling for the most loaded business processes without costly scaling of the entire platform.

5.3. Microservices Characteristics

AdvantageDisadvantage
Deployment autonomy: Each service can be deployed independently, multiple times per day, without regression risk in other areas.Operational complexity: Requires container orchestration (Kubernetes), Service Discovery, API Gateways.
Failure isolation: A database failure or code error in one service does not directly affect other services' availability.Difficult data consistency: Requires Saga patterns, CQRS, and handling eventual consistency.
Technological freedom: Choose the optimal tech stack (graph, relational, NoSQL databases) for each problem domain.Difficult testing: Requires contract verification and coordination of integration tests in a distributed environment.
Conway's Law alignment: Small, agile teams have full ownership and responsibility for their services.Versioning problem: Managing backward compatibility and API contract evolution between services.

5.4. When to Use Microservices?

warning

Only when the benefits from full team autonomy and independent scaling outweigh the enormous costs of deploying and maintaining distributed infrastructure. This is a pattern for large organizations (50–100+ developers) with very high DevOps maturity, operating on a stable and correctly divided business domain.

6. Synchronous Communication

The basic integration model in distributed systems relies on synchronous communication. The sender initiates a connection (REST, gRPC, GraphQL), sends a request, and blocks its resources (CPU thread) while waiting for an immediate response from the receiver.

6.1. Characteristics of Synchronous Communication

AdvantageDisadvantage
Implementation simplicity: Most popular, intuitive communication model, widely supported by all frameworks.Temporal coupling: For an operation to succeed, both services must be available at exactly the same moment.
Immediate response: The sender immediately receives operation status, simplifying UI control logic.Cascading failures: A failure in one service at the end of a call chain blocks resources of all upstream services.
Easy debugging: Request flow is deterministic and easy to trace in system logs.Cumulative latency: The sender's response time is the sum of response times of all systems in the chain.

6.2. The Cascading Failure Threat

When service A synchronously calls B, which calls C, which calls D and E, the total system response time grows additively. A failure or sudden delay in service E causes thread pool starvation in all upstream services (A, B, C, D), leading to complete platform paralysis. The availability of the entire chain equals the product of individual component availabilities. With five services at 99% availability, total chain availability drops to roughly 95% (0.99⁵ ≈ 0.951). Mitigation patterns — Circuit Breaker, bulkhead, and explicit timeouts — are mandatory in these topologies.

7. Event Bus — Asynchronous Transport

To eliminate temporal coupling, distributed systems deploy asynchronous communication channels. The Event Bus — a dedicated message broker (e.g., RabbitMQ, Kafka, AWS EventBridge) — mediates data exchange. The sender publishes a message and immediately ends its execution process, while the consumer processes the message at its own convenience (backpressure). This also enables easy one-to-many fanout distribution to multiple receivers simultaneously.

AdvantageDisadvantage
Loose coupling: No need for simultaneous network availability of sender and receivers.Eventual consistency: Data in the system is not immediately consistent; transient inconsistency states occur.
Load buffering: The broker protects slower consumers from being overwhelmed by sudden request spikes.Technology complexity: Challenges with event ordering, deduplication, and delivery guarantees.
Fault tolerance: When a consumer fails, messages safely wait in the queue for its return.Schema evolution: Risk of consumer failures when the producer changes the event payload structure.

8. Event-Driven Architecture

8.1. Event Bus vs. Event-Driven Architecture

A common mistake is equating the Event Bus with Event-Driven Architecture. An Event Bus is purely a technical transport mechanism — an infrastructure intermediary for message delivery (Message Broker). Event-Driven Architecture (EDA) is a comprehensive system design paradigm in which control flow, business logic, and component states are defined through reactions to events that are past business facts.

8.2. Request-Driven vs. Event-Driven

The difference lies in the intent of the message and the direction of dependency. In the request-driven model, service A has an intent and issues service B an explicit command to perform an action — A depends on B's interface. In the event-driven model, service A merely announces to the world that a business fact has occurred. Service A does not know who will react to the event or what they will do with it, completely reversing the direction of dependency. B, C, and D subscribe and decide their own reaction independently.

8.3. Problems EDA Solves

  • Completely eliminates strong domain dependencies between the producer and consumers.
  • Prevents rigid, synchronous call chains from forming in the first place.
  • Allows non-invasive system extension with new features by simply adding new event consumers.

8.4. EDA Characteristics

AdvantageDisadvantage
Extremely low coupling: Maximum component autonomy and complete logical decoupling.Difficult mental model: Business flow is distributed; no easy top-down code comprehension.
High extensibility: Adding a new module (e.g., analytics) requires no modification of the event sender's code.Testing challenges: Very difficult to implement and automate end-to-end integration tests.
Real-time support: Natural suitability for stream processing and immediate reactions to business events.Idempotency requirement: Every consumer must be prepared for re-receiving the same event (replay safety).

9. Combining Architectural Styles

Architectural styles can and should be combined within a single business platform. The four most common hybrid patterns found in production systems:

  • Monolith + Synchronous API: All business logic in one process; communication with external systems (payment gateways, SMS providers) via synchronous REST or gRPC calls.
  • Modular Monolith + Event Bus (highly recommended): Logical modules share a common process and database, but internal communication uses a local event bus (e.g., Spring Modulith Event Publisher) rather than direct method calls. Code cleanliness at minimal operational cost.
  • Microservices + REST: A mesh of distributed services connected synchronously. Common for internal CRUD systems requiring immediate state return to UI. Susceptible to cascading failures.
  • Microservices + Event-Driven: Most advanced and flexible distributed model. Microservices are completely isolated; coordination happens exclusively through asynchronous publication of facts to a distributed event bus (e.g., Apache Kafka). Highest fault tolerance at the cost of accepting eventual consistency.

10. Anti-patterns

Anti-patternDescriptionConsequence
Big Ball of MudA monolith without any logical barriers. Every class can call any other class; database tables are modified directly without encapsulation.Paralyzing growth of development costs; inability to predict side effects of even the smallest code change.
Distributed MonolithSystem formally split into microservices (separate repos, containers, databases) but tightly coupled through synchronous dependencies. Deployment of any change requires coordinated release trains.All operational costs of distributed systems (network delays, hard debugging) with none of the monolith's deployment simplicity.
Nano-servicesExcessively fine decomposition — a single helper function extracted as a separate microservice.Network overhead, serialization costs, and infrastructure configuration far exceed any benefit from logical isolation.
Shared DatabaseMultiple independently deployed services read and write directly to the same physical database tables.Any schema change by one team immediately breaks services developed by all other teams. Negates microservice autonomy entirely.
Synchronous ChainMany services connected in deep synchronous call structures: A → B → C → D → E.Drastic performance degradation (latencies add up) and reliability drops to the weakest link in the chain.
Event SpaghettiAn event-based system where, due to lack of central control and rigorous contract documentation, no one can reconstruct the full information flow graph.Uncontrolled feedback loops, hard-to-diagnose state changes, and operational chaos.

11. Comprehensive Comparison

ApproachCore architectural decisionKey advantagesKey costs
Monolith (Classic, Modular, Micromonolith)Single or specialized deployment unit with coherent boundaries or dedicated domain area.Developer simplicity, ACID transactions, low operational cost, high domain purity.No selective scaling, risk of "mud ball," shared runtime for modules.
Monolith FirstStrategy of delaying decomposition until domain stabilization.Low initial cost, fast MVP, flexibility in redefining domain boundaries.Risk of losing boundary control before migration begins.
Distributed ArchitecturePhysical division of system into independent network processes.Horizontal scaling flexibility, infrastructure failure resilience.Network tax (latency), loss of immediate consistency, difficult debugging.
MicroservicesFine-grained services with own databases, aligned to business domains.Maximum team autonomy, independent deployments, precise scaling.Extreme operational complexity, loss of ACID consistency, network integration costs.
Synchronous API (REST / gRPC)Direct request-response communication.Simple mental model, easy error handling, immediate response.Temporal coupling, susceptibility to cascading platform failures.
Event BusIntroduction of a message broker as a transport layer.Asynchrony, load absorption (backpressure), easy consumer addition.Handling eventual consistency, duplicates, and message ordering.
Event-Driven ArchitectureParadigm of controlling systems through reactions to events (business facts).Extremely loose domain coupling, high flexibility and extensibility.Difficult business flow tracking, complex integration testing.

12. Decision Matrix

To structure the architecture selection process, analyze the production system against five fundamental criteria:

  • Step 1 — Independent component scaling needed? NO → Monolith or Modular Monolith (distributing is costly premature optimization). YES → Step 2.
  • Step 2 — Organization absolutely needs independent deployments? NO → Modular Monolith (best trade-off: full domain separation with single-artifact simplicity). YES → Step 3.
  • Step 3 — Domain boundaries fully known, stable, and confirmed? NO → Start with Modular Monolith (Monolith First strategy) to define boundaries safely before physical separation. YES → Step 4.
  • Step 4 — Communication must deliver an immediate response (synchronous request-response)? YES → Synchronous communication (REST/gRPC) with Circuit Breaker, bulkhead, and precise timeouts. NO → Step 5.
  • Step 5 — Multiple different system components should react to the same event? NO → Direct async point-to-point via Event Bus is sufficient and minimizes architectural overhead. YES → Full Event-Driven Architecture based on distributed publish/subscribe of business events.

13. Summary

The most important conclusion for every system architect is to reject the dogma of microservices superiority over the monolith. A monolith is not an anti-pattern or a symbol of outdated technology — it is a fully justified, efficient, and highly desirable architectural style, especially in modular form. Microservices are not an automatic software improvement; they are a specific tool for solving organizational scale problems and asymmetric infrastructure load, burdened with a gigantic distributed tax. Event-Driven Architecture is not the natural next evolutionary stage of every system, but a conscious paradigm applied to eliminate temporal coupling in complex ecosystems.

lightbulb

The best system architecture is not the one that uses the most advanced technological patterns and the newest distributed tools. The best architecture is the one whose level of complexity is fully justified by real, measurable business and technical problems of the system. All decomposition decisions should be made on the basis of hard telemetry data, not industry trends.