2. Architecture Styles and System DecompositionIntroduction and ContextSystem architecture is the set of significant decisions that determine how a software system is divided, how its parts communicate, how data is managed, and how the system evolves. For a full-stack developer, architecture is not an abstract diagram prepared only by senior architects. It directly influences API design, database structure, deployment, testing, observability, security, team organization, and the cost of changing the product. A web application may begin as a small codebase serving a limited number of users. As its functionality, traffic, data volume, and development team grow, decisions about decomposition become increasingly important. A single deployable application may offer speed and simplicity, while independently deployable services may provide autonomy and selective scaling. Neither approach is universally superior. The appropriate choice depends on business boundaries, operational capability, reliability requirements, team structure, and the expected rate of change. This chapter examines major architecture styles and the reasoning used to select and combine them. It focuses particularly on the relationship between structure and change: how a system can be divided so that related responsibilities remain together, unrelated responsibilities remain separate, and changes in one area do not unnecessarily destabilize others. Learning ObjectivesBy the end of this chapter, a learner should be able to: - distinguish monolithic, modular monolithic, layered, microservice, service-oriented, serverless, and hexagonal architectures;
- explain coupling, cohesion, bounded contexts, domain-driven design, and evolutionary decomposition;
- evaluate architecture styles according to scalability, deployability, operational complexity, performance, and organizational context;
- identify suitable decomposition boundaries for a web-based business system;
- analyze the risks of premature distribution and poorly designed service boundaries;
- apply an incremental approach to evolving a monolith toward independently deployable services;
- recognize governance, security, reliability, and data-consistency implications of architectural choices.
Architecture as a Set of Trade-offsArchitecture styles are not complete solutions. They are patterns for organizing responsibilities and interactions. Each style improves some qualities while imposing costs elsewhere. For example, microservices can allow teams to deploy and scale selected capabilities independently, but they also introduce network failure, distributed tracing, data synchronization, deployment automation, and service ownership challenges. A useful way to reason about architecture is to examine quality attributes rather than asking which style is “best.” Important attributes include: - Changeability: How easily can a feature be modified without affecting unrelated functionality?
- Deployability: Can a team release a change independently and safely?
- Scalability: Can capacity be increased where demand requires it?
- Reliability: Can failures be contained, detected, and recovered from?
- Performance: What latency and throughput characteristics result from the design?
- Security: Can access, identity, data protection, and auditability be controlled?
- Operability: Can the system be monitored, diagnosed, and maintained?
- Economic efficiency: What infrastructure, staffing, and operational costs are required?
Architectural decisions should also consider the system’s stage of development. An early product may benefit from a simple deployable unit because requirements are uncertain and the team is small. A mature platform may require stronger isolation, independent scaling, and separate release lifecycles. The architecture should therefore be treated as an evolutionary design rather than a permanent declaration. Essential Concepts for DecompositionCoupling and CohesionCoupling describes the degree of dependency between software components. Tight coupling exists when one component depends heavily on another component’s internal data structures, timing, database tables, or implementation details. Loose coupling limits dependencies to stable contracts such as well-defined interfaces or domain events. Cohesion describes how closely related the responsibilities within a component are. A highly cohesive component contains functions that contribute to a clear purpose. A component that handles user authentication, invoice calculation, image processing, and email delivery has low cohesion, even if all functions happen to reside in one source repository. Good decomposition generally seeks high cohesion within boundaries and low coupling between boundaries. This principle is more useful than simply counting classes, modules, or services. Ten badly related microservices can be more difficult to maintain than one well-structured application. Bounded ContextsA bounded context is a boundary within which a domain model, vocabulary, and set of rules have a particular meaning. The same term may legitimately mean different things in different contexts. For example, “customer” in a marketing context may mean a person receiving campaigns, whereas in a billing context it may mean an account responsible for payment. Bounded contexts help prevent a single universal model from becoming overloaded with incompatible assumptions. They are conceptual boundaries first; they do not automatically require separate databases or network services. A bounded context may initially be implemented as a module inside a monolith and later become a separately deployed service if independent deployment creates sufficient value. Domain-Driven DesignDomain-driven design (DDD) emphasizes understanding the business domain and representing important business rules explicitly in software. It distinguishes between: - Entities, which have identity and continuity over time;
- Value objects, which are defined by their attributes rather than identity;
- Aggregates, which group related objects and establish a consistency boundary;
- Repositories, which provide access to domain objects or aggregates;
- Domain services, which express domain operations that do not naturally belong to one entity;
- Domain events, which record meaningful business occurrences.
DDD is not synonymous with microservices. Its principal contribution is the discovery of meaningful business boundaries. Those boundaries can guide module structure, team ownership, API design, and eventual service decomposition. Contracts and Dependency DirectionA component boundary becomes valuable only when the boundary is respected. Contracts should specify what a component offers, the assumptions it makes, error behavior, security requirements, and compatibility expectations. In a web system, contracts may be represented by REST or GraphQL schemas, asynchronous event schemas, function interfaces, or internal module APIs. Dependency direction is also significant. In a well-organized design, business rules should not depend directly on presentation frameworks, database vendors, or external payment libraries. Instead, stable business abstractions should define what is needed, while infrastructure provides implementations. This idea is central to hexagonal architecture. Major Architecture StylesThe Traditional MonolithA monolith is a system delivered as one deployable unit. Its user interface, application logic, and data-access logic may all be packaged together, and its functions commonly share a database. A monolith can be technically well designed or poorly designed; “monolith” describes deployment and structural scope, not code quality. Monoliths offer several advantages. Local calls are generally faster and simpler than network calls. Transactions across related operations are easier to implement. Development, testing, deployment, and local debugging may require fewer tools. For a small team or an early product, these characteristics can reduce cognitive and operational overhead. The principal risks arise when the codebase becomes internally tangled. A small change may require rebuilding and deploying the entire application. Different functions may compete for resources, and a fault in one area may affect the whole process. A shared database can create hidden coupling because teams begin to rely on each other’s tables and undocumented queries. A monolith is therefore not inherently an anti-pattern. A well-structured monolith can be an effective starting point and, for some systems, an appropriate long-term architecture. The Modular MonolithA modular monolith remains one deployable application but is divided into explicit modules with clear responsibilities and interfaces. Modules may represent bounded contexts such as Catalog, Orders, Payments, and Fulfilment. Each module owns its business rules and should avoid directly accessing another module’s internal data. The modular monolith often provides a practical balance. It preserves simple deployment and in-process communication while encouraging high cohesion and controlled coupling. It also creates a migration path: a module that later requires independent scaling or release can be extracted with less risk because its boundary already exists. A modular monolith requires discipline. Shared utility code, unrestricted database access, circular dependencies, and direct manipulation of another module’s objects gradually destroy modularity. Architectural tests, code ownership, dependency rules, and module-level APIs can help preserve the design. Layered ArchitectureLayered architecture organizes a system by technical responsibility. A common arrangement contains a presentation layer, application layer, domain or business layer, and infrastructure or data-access layer. Requests move through these layers according to defined dependency rules. Layering can make responsibilities easier to understand and can prevent user-interface code from being mixed with persistence operations. It is particularly useful for conventional business applications. However, layers may become broad containers with weak domain boundaries. A “service layer” containing unrelated operations can hide rather than solve coupling. Strictly horizontal layering may also cause one business change to require edits in every layer. Layering and modularity are not alternatives. A modular monolith can use layers inside each business module. The important distinction is that layers organize technical concerns, whereas bounded contexts organize domain meaning. Microservices ArchitectureMicroservices architecture structures an application as a collection of small, independently deployable services. Each service typically owns a focused business capability, exposes a contract, and controls its own implementation and often its own data store. Communication may be synchronous through HTTP or gRPC, or asynchronous through a message broker. The benefits include independent deployment, selective scaling, fault isolation, and clearer ownership by autonomous teams. A payments service, for instance, can be released and scaled separately from a product-search service. Different technologies may be used where justified by the capability’s requirements. The costs are substantial. A local function call becomes a potentially unreliable network interaction. Distributed transactions are difficult, data may become eventually consistent, and diagnosing a request may require tracing across many services. Each service may require deployment pipelines, monitoring, alerting, secrets management, vulnerability scanning, and operational ownership. Microservices work best when services correspond to meaningful business capabilities and when the organization can support the necessary platform and operational practices. Splitting a poorly understood monolith into many services generally distributes confusion rather than eliminating it. Service-Oriented ArchitectureService-oriented architecture (SOA) organizes enterprise capabilities as reusable services that communicate through defined contracts. SOA often emphasizes integration across heterogeneous systems, shared enterprise capabilities, governance, and interoperability. Services may be larger and more broadly shared than those in a typical microservices design. Historically, SOA implementations frequently used enterprise service buses, centralized transformation, and formal governance. These mechanisms can be valuable in large organizations, but excessive centralization may create bottlenecks and obscure service ownership. Microservices can be viewed as a more decentralized approach to service-based architecture, although the terms are not interchangeable and practices vary across organizations. Serverless ArchitectureServerless architecture delegates server provisioning and much of runtime management to a cloud provider. It commonly combines functions-as-a-service, managed databases, queues, object storage, API gateways, and event-processing services. “Serverless” does not mean that servers do not exist; it means that the application team does not manage them directly at the infrastructure level. Serverless can provide automatic scaling, consumption-based pricing, rapid deployment, and convenient integration with cloud services. It is suitable for event-driven processing, scheduled jobs, file transformations, lightweight APIs, and workloads with variable demand. Constraints include cold-start latency, execution limits, provider-specific APIs, distributed debugging, more complex local development, and potentially unpredictable cost at high volume. Serverless functions should still be organized around coherent business capabilities. Creating hundreds of trivial functions without clear ownership can produce a distributed monolith. Hexagonal ArchitectureHexagonal architecture, also called ports-and-adapters architecture, separates the application core from external technologies. The core contains domain and application logic. Ports are interfaces through which the core communicates with the outside world. Adapters implement those interfaces for particular technologies, such as a web controller, SQL repository, message consumer, or payment gateway. The design permits the same business logic to be invoked by an HTTP API, a command-line tool, a background job, or a test. It also makes infrastructure replaceable and supports automated testing without requiring a live database or external service for every test. Hexagonal architecture is not a deployment style. It can be used inside a monolith, a microservice, or a serverless function. Its value lies in dependency control and protection of business rules from technical details. Comparing the Styles in ContextA small product team with uncertain requirements may sensibly choose a modular monolith using layered or hexagonal internal structure. This minimizes operational burden while retaining a path for future change. A large organization integrating numerous legacy systems may require SOA-style contracts and governance. A high-volume platform with independently changing domains may justify microservices, provided that its teams possess strong delivery and operations capabilities. A workload dominated by irregular events may benefit from serverless components. The decision should consider at least five questions: 1. Are the business boundaries understood well enough to divide the system safely? 2. Do teams need independent release and scaling, or would that create unnecessary complexity? 3. Can the organization operate distributed systems with adequate observability and incident response? 4. What consistency, latency, security, and regulatory requirements apply to data? 5. Is the proposed architecture solving a demonstrated problem or anticipating a hypothetical one? Architecture selection is therefore a socio-technical decision. Team autonomy, ownership, communication, skills, budget, compliance obligations, and platform maturity are as important as technical elegance. Evolutionary DecompositionEvolutionary decomposition is the gradual separation of a system as evidence reveals where boundaries are needed. It avoids treating the first architecture as irreversible and avoids making distribution a goal in itself. A practical sequence is to begin with domain discovery. Teams identify business capabilities, major workflows, data ownership, rates of change, and areas of operational risk. They then establish modules and explicit interfaces within a single deployable system. Architecture fitness functions—automated checks that enforce dependency or performance rules—can prevent structural regression. When a module becomes a candidate for extraction, the team should examine its transaction boundaries, consumers, data ownership, failure behavior, and release requirements. The extraction can use an anti-corruption layer, which translates between the old model and the new service model while preventing legacy concepts from contaminating the new boundary. Data may be migrated gradually through dual reads, controlled replication, or carefully managed event publication, but each technique introduces consistency and reconciliation concerns. The strangler pattern is another evolutionary approach. New functionality is implemented outside the old system, while routing gradually moves selected capabilities to the new component. The old implementation is retired once traffic and responsibility have been transferred. This approach reduces the risk of a single large migration, though it requires careful routing, observability, and data management. Applied Case: Evolving an Online Learning PlatformBackgroundConsider LearnSpace, an illustrative online learning platform used by universities and professional learners. Its initial application supports course browsing, enrolment, video access, assessments, payments, notifications, and administrative reporting. It was built by a team of eight developers as a conventional monolith with a shared relational database. Situation and ProblemThe platform has gained users. During peak enrolment, database load affects unrelated functions. Payment changes require deployment of the entire application, and the notification subsystem occasionally delays web requests. Different teams now need to release course-management and billing features on different schedules. Management proposes immediately splitting the application into twenty microservices. AnalysisAn architectural review finds that the problem is not simply that the application is a monolith. Several domains are poorly separated inside it. The payment code directly reads enrolment tables; reporting queries operational tables; and notifications are executed synchronously within enrolment transactions. The proposed twenty-service design would preserve these dependencies across a network and would introduce operational complexity before the domain boundaries were understood. The team identifies four relatively coherent contexts: Learning Content, Enrolment, Billing, and Communication. Reporting is treated as a read-oriented analytical capability rather than a transactional domain. Each context has different data access patterns and rates of change. ApproachThe team first creates a modular monolith. Each context receives an internal API, ownership rules, and restricted database access. Enrolment publishes an internal “Learner Enrolled” event rather than directly invoking notification logic. Communication consumes the event and sends email asynchronously. Reporting receives controlled data exports instead of querying every transactional table. Next, Billing is extracted as a service because it has separate compliance controls, a distinct release schedule, and a need for isolated access to payment-provider credentials. An anti-corruption layer translates the monolith’s enrolment concepts into the billing service’s contract. The platform introduces distributed tracing, idempotency keys for payment requests, central log correlation, and explicit retry policies before production migration. Learning Content remains a module because its current scaling needs are modest. Video files are moved to object storage and a content delivery network, solving the primary delivery bottleneck without creating another business service. OutcomeThe platform gains asynchronous notification, more predictable enrolment performance, and independently controlled billing releases. It operates one extracted service and a modular monolith rather than twenty loosely understood services. Further extraction remains possible, but it is now driven by evidence such as scaling pressure, team ownership, compliance, or release independence. Learning PointsThe case demonstrates that decomposition should follow business and operational boundaries, not fashion. A modular monolith can be an intermediate or durable architecture. Performance problems may be solved through caching, asynchronous processing, or content delivery rather than service extraction. Finally, distributed architecture requires investment in contracts, observability, failure handling, and data ownership. Risks, Limitations, and Common MistakesA frequent mistake is confusing independent deployment with small size. A service should be cohesive and independently valuable, not merely a few classes placed behind an HTTP endpoint. Another mistake is allowing multiple services to write the same database tables. This creates hidden coupling while adding network and deployment complexity. Synchronous call chains can produce cascading failure. If an order request depends synchronously on six services, a delay in any one of them may increase overall latency or cause failure. Timeouts, bounded retries, circuit breakers, bulkheads, queues, and graceful degradation are useful, but they do not compensate for poor boundary design. Event-driven systems introduce their own challenges: duplicate delivery, out-of-order events, schema evolution, replay behavior, and eventual consistency. Consumers should be idempotent, event contracts should be versioned carefully, and business processes should tolerate intermediate states where possible. Shared libraries can reduce duplication but may also create synchronized release dependencies. Shared platforms should provide common capabilities such as identity, telemetry, and deployment automation without forcing every domain to share its internal model. Premature abstraction is another danger. Teams may create generic “common” models that erase meaningful domain differences. Security concerns also increase with distribution: every service-to-service interaction requires authentication, authorization, secret management, encryption, and audit consideration. Governance, Professional Practice, and EthicsArchitecture decisions affect users, organizations, and society. Data boundaries must respect privacy obligations, retention policies, access controls, and applicable regulations. A service boundary should not accidentally expose sensitive payment, health, educational, or identity information to teams that do not need it. Governance should be proportionate. Lightweight architecture decision records can document alternatives, assumptions, consequences, and review dates. Service ownership should be explicit, including responsibility for availability, security patches, data quality, and incident response. Teams should measure outcomes rather than claiming success because a particular style was adopted. Professionally responsible architecture also considers accessibility, reliability for vulnerable users, energy and infrastructure costs, and the consequences of vendor lock-in. Cloud convenience does not remove the need for portability analysis, cost controls, or exit planning. Current DevelopmentsContemporary systems increasingly combine styles rather than adopting one architecture uniformly. A modular core may coexist with event-driven workflows, managed cloud databases, serverless jobs, and a small number of independently deployed services. Platform engineering provides standardized deployment, observability, security, and developer tooling so that product teams can operate services more safely. The growth of internal developer platforms, infrastructure as code, automated architecture checks, and software supply-chain security is making operational concerns part of everyday development. AI-assisted coding can accelerate implementation, but it does not determine appropriate boundaries or validate business assumptions. Generated code still requires review for coupling, data leakage, security vulnerabilities, licensing concerns, and operational behavior. Best Practices for Architectural Decision-Making- Begin with business capabilities, user journeys, and quality attributes rather than technologies.
- Prefer high cohesion and low coupling at every boundary.
- Establish modular boundaries before introducing network boundaries.
- Give each important data set a clear owner.
- Use stable, documented contracts and test compatibility automatically.
- Design explicitly for timeout, retry, duplication, partial failure, and recovery.
- Introduce asynchronous processing where it improves resilience or decouples workloads, not merely because events are fashionable.
- Measure deployment frequency, change failure rate, latency, availability, cost, and incident recovery.
- Use architecture decision records and revisit assumptions as evidence changes.
- Select the simplest architecture that satisfies current and reasonably foreseeable needs.
Chapter SummaryArchitecture styles provide alternative ways to organize software responsibilities, deployment units, communication, and infrastructure. Monoliths simplify deployment and transactions; modular monoliths preserve those advantages while enforcing internal boundaries. Layered architecture separates technical concerns, whereas bounded contexts and domain-driven design organize business meaning. Microservices support independent deployment and scaling but impose distributed-systems and operational costs. SOA emphasizes reusable enterprise services and integration, serverless delegates runtime management to cloud platforms, and hexagonal architecture protects business logic from external technologies. Effective decomposition depends on high cohesion, low coupling, explicit contracts, clear data ownership, and meaningful bounded contexts. Architecture should evolve through evidence. A modular monolith, asynchronous processing, or targeted infrastructure change may solve a problem more safely than immediate migration to many services. Professional decisions must also account for security, privacy, governance, team capability, cost, observability, and long-term maintainability. Key TermsArchitecture style: A recurring way of organizing software components, communication, deployment, and infrastructure. Monolith: A system delivered as one deployable application unit. Modular monolith: A single deployable application divided into explicit, governed internal modules. Layered architecture: An organization of software by technical responsibility, such as presentation, application, domain, and infrastructure layers. Microservices: Independently deployable services organized around focused business capabilities. Service-oriented architecture: An enterprise architecture based on interoperable, contract-driven services. Serverless: A model in which cloud providers manage much of the underlying server infrastructure and runtime capacity. Hexagonal architecture: An architecture that isolates application and domain logic behind ports implemented by external adapters. Coupling: The degree of dependency between components. Cohesion: The degree to which the responsibilities within a component belong together. Bounded context: A boundary within which a domain model and vocabulary have consistent meaning. Domain-driven design: An approach that models software around business domains, rules, and language. Aggregate: A group of domain objects governed by a consistency boundary and an aggregate root. Domain event: A record of a meaningful occurrence in a business domain. Contract: A defined agreement governing an interface, data format, behavior, and compatibility expectations. Anti-corruption layer: A translation boundary that protects one domain model from another model’s concepts. Strangler pattern: An incremental migration approach in which new functionality gradually replaces parts of an old system. Eventual consistency: A condition in which distributed replicas may temporarily differ but converge over time. Architecture fitness function: An automated check that verifies an architectural property, such as dependency direction or response time. Review QuestionsShort Answer Questions1. What is the difference between coupling and cohesion? 2. Why does a bounded context not necessarily require a separate microservice? 3. What distinguishes a modular monolith from an unstructured monolith? 4. What is the purpose of a port in hexagonal architecture? 5. State two operational challenges introduced by microservices. Long Answer Questions1. Compare monolithic, modular monolithic, and microservices architectures in relation to deployment, scalability, testing, and organizational capability. 2. Explain how domain-driven design can assist in identifying service or module boundaries. 3. Discuss the benefits and limitations of serverless architecture for web applications. 4. Explain why layered architecture and hexagonal architecture address different architectural concerns. 5. Describe a disciplined process for evolving a monolith toward independently deployable services. Analytical / Application Questions1. A retail application has a small development team, rapidly changing requirements, and no significant independent scaling needs. Recommend an architecture and justify the decision using at least four quality attributes. 2. An order-processing system contains ten proposed microservices, all writing to the same database and using long synchronous call chains. Analyze the design risks and propose a safer decomposition strategy. 3. A university platform experiences slow email delivery during enrolment but has stable transactional performance. Determine whether service extraction, asynchronous processing, infrastructure change, or a combination is most appropriate, and explain why. 22. Multiple Choice Questions with AnswersQuestion 1A team wants independent business modules but wishes to retain one deployment unit and simple in-process communication. Which architecture is most appropriate? A. Modular monolith B. Microservices C. Enterprise service bus architecture D. Pure serverless architecture Correct Answer: A. Modular monolith Question 2Which situation most clearly indicates high coupling between components? A. A module exposes a stable interface for a business operation B. A service publishes versioned domain events C. Two modules directly modify the same database tables D. An adapter implements a repository port Correct Answer: C. Two modules directly modify the same database tables Question 3What is the primary purpose of a bounded context in domain-driven design? A. To require every domain to use a separate cloud account B. To define where a model and vocabulary have consistent meaning C. To eliminate the need for business rules D. To guarantee that all data is strongly consistent Correct Answer: B. To define where a model and vocabulary have consistent meaning Question 4Which statement best describes hexagonal architecture? A. It requires every component to be deployed as a microservice B. It organizes an application only by presentation, business, and data layers C. It isolates core logic from external technologies through ports and adapters D. It replaces relational databases with event streams Correct Answer: C. It isolates core logic from external technologies through ports and adapters Question 5A company extracts a service but allows both the old application and the new service to write the same customer tables. What is the main architectural problem? A. The system has too much cohesion B. The extraction has preserved hidden data coupling C. The service is necessarily serverless D. The system cannot use asynchronous communication Correct Answer: B. The extraction has preserved hidden data coupling Question 6Which factor most strongly supports adopting microservices? A. The team has not yet understood the domain boundaries B. The organization wants more technologies without operational investment C. A business capability requires independent deployment, scaling, and ownership D. The application has a small codebase and low traffic Correct Answer: C. A business capability requires independent deployment, scaling, and ownership Question 7In the illustrative learning-platform case, why was Billing a stronger candidate for extraction than Learning Content? A. Billing had compliance, credential, and release-isolation requirements B. Learning Content had no business rules C. Billing was implemented entirely as a user interface D. Learning Content could not be represented as a module Correct Answer: A. Billing had compliance, credential, and release-isolation requirements Question 8Which problem is especially associated with distributed event-driven systems? A. The impossibility of defining interfaces B. Duplicate or out-of-order event delivery C. The inability to scale individual workloads D. The elimination of all deployment automation Correct Answer: B. Duplicate or out-of-order event delivery Question 9What does the strangler pattern seek to accomplish? A. It replaces a system through one large, irreversible rewrite B. It gradually routes functionality from an old system to new components C. It combines all services into one shared database D. It prevents any change to legacy software Correct Answer: B. It gradually routes functionality from an old system to new components Question 10A team chooses a cloud function for an irregular file-processing workload. Which consideration is most relevant? A. Cold starts, execution limits, provider dependence, and observability B. The requirement that all functions share one domain model C. The guarantee that every invocation is free of operational cost D. The elimination of security and identity controls Correct Answer: A. Cold starts, execution limits, provider dependence, and observability
|