3 Topics
Chapter•110 Minutes

Software Architecture: Monoliths, Microservices, and Modular Design

Understand architecture styles and how to choose service boundaries without creating unnecessary distributed complexity.

Layered Architecture and the Modular Monolith

A layered application separates responsibilities—for example, transport/controller, application service, domain logic, and persistence. The separation is valuable only when dependencies remain intentional; a pile of folders does not automatically create good architecture.

A modular monolith deploys as one application while enforcing boundaries between domains. It can provide strong transaction support, simple local calls, and straightforward debugging. Modules should expose narrow interfaces and own their data access rather than reaching into one another's internal tables.

For an early product, this is often a practical baseline. You can scale stateless instances horizontally and move a module to a separate service later if there is a clear reason such as independent scaling, ownership, deployment, or fault isolation.

Layered modular monolith
Key Takeaways
  • A modular monolith can have clear boundaries without network calls.
  • Enforce module ownership and dependency direction.
  • Extract services only when the benefits justify distributed-systems costs.
Knowledge Check

When is extracting a module into a microservice most justified?

Microservices and Domain-Driven Boundaries

Microservices are independently deployable services aligned to business capabilities. A service should own a cohesive responsibility and the data needed to fulfill it. Splitting by technical layer—for example, a service for every database table—often creates chatty dependencies and coordinated deployments.

Domain-driven design uses concepts such as bounded contexts, ubiquitous language, aggregates, and domain events to discover boundaries. Two teams may use the same word differently; a “customer” in billing may have different rules from a “customer” in support. Explicit contexts reduce accidental coupling.

Microservices introduce network latency, partial failure, distributed tracing, service discovery, deployment coordination, and data consistency challenges. Independent deployment is useful only if teams can operate services safely and contracts are managed carefully.

Microservices are not an automatic scaling solution

A monolith can scale horizontally. Split services for meaningful ownership, independent scaling, isolation, or release needs—not because the system has become popular.

Key Takeaways
  • Align boundaries with business capabilities and data ownership.
  • Avoid synchronous dependency chains that amplify latency and failure.
  • Account for operational cost before extracting a service.

Synchronous and Asynchronous Communication

Synchronous calls are easy to reason about: the caller waits for a response. They are appropriate when the user needs an immediate result, but latency and availability depend on the downstream path. If A calls B, which calls C, a slow C can consume resources in A and B.

Asynchronous communication lets a producer hand work to a broker or durable queue and continue. It improves burst absorption and decoupling, but requires retries, deduplication, dead-letter handling, event schemas, and a way to observe work that has not completed.

Set deadlines across a request chain. A downstream call should not be allowed to outlive the caller's remaining budget. Use asynchronous processing for tasks such as emails, analytics, and media transcoding when immediate completion is unnecessary.

Synchronous request versus asynchronous job
Key Takeaways
  • Synchronous paths couple latency and availability.
  • Queues buffer work but do not remove the need for correctness.
  • Expose job state when work completes asynchronously.