Software Architecture Patterns: MVC, Microservices, Monolith, and More

Software architecture patterns are the structural blueprints that govern how software systems are partitioned, connected, and scaled — decisions that shape team organization, deployment complexity, fault boundaries, and long-term maintainability. This page covers the major recognized patterns, their classification boundaries, the tradeoffs that make pattern selection contested, and the structural mechanics that distinguish one architecture from another. The Software Engineering Authority treats this topic as a foundational reference for engineers, architects, and procurement professionals evaluating system design decisions.


Definition and scope

Software architecture patterns define the macro-level structural arrangement of system components — how responsibilities are divided, how data flows between parts, and how deployment units are organized. The IEEE Standard 1471-2000 (superseded and expanded by ISO/IEC/IEEE 42010:2011) established the formal framework under which architecture is documented as a set of architectural views addressing distinct concerns of distinct stakeholders (ISO/IEC/IEEE 42010:2011). Within that framework, a "pattern" is a recurring, named solution to a recurring structural problem — not a one-off design decision.

The scope of architecture patterns spans three primary levels:

The SWEBOK v4 published by IEEE Computer Society classifies architectural design as a distinct knowledge area within software design, covering structural and behavioral models, architectural styles, and quality attributes (IEEE SWEBOK v4). Enterprise application development — where pattern selection intersects procurement, compliance, and governance — is covered in depth by the App Development Authority, which addresses architectural governance, qualification standards, and the regulatory constraints that drive enterprise-grade pattern choices.


Core mechanics or structure

Monolithic Architecture

A monolithic system deploys all application components — user interface, business logic, and data access — as a single executable unit. All modules share the same process space and are typically compiled and released together. In a traditional 3-tier monolith, the three layers (presentation, application, data) are logically separated but physically co-deployed.

Structural characteristics:
- Single deployable artifact (WAR, JAR, binary)
- Shared in-process memory
- Single database schema (typically)
- Horizontal scaling achieved by replicating the entire application

Model-View-Controller (MVC)

MVC is an application-level structural pattern that separates an application into 3 components: the Model (data and business logic), the View (presentation layer), and the Controller (input handling and orchestration). First formally described in the Smalltalk-80 programming environment at Xerox PARC, MVC became the dominant web framework pattern through implementations in Ruby on Rails, ASP.NET MVC, Django, and Spring MVC.

The controller receives input, invokes model operations, and selects the appropriate view. The model notifies views of state changes, typically through an observer mechanism. This separation means UI changes do not require business logic modifications — a boundary with direct implications for software testing types and test isolation.

Microservices Architecture

Microservices decompose a system into independently deployable services, each responsible for a bounded business capability. Each service owns its own data store (the "database per service" pattern), exposes a well-defined API, and communicates over a network protocol — typically HTTP/REST or asynchronous messaging. The pattern is closely aligned with domain-driven design principles, particularly bounded contexts as defined by Eric Evans in Domain-Driven Design: Tackling Complexity in the Heart of Software (Addison-Wesley, 2003).

The 2015 article by Martin Fowler and James Lewis ("Microservices," martinfowler.com) established the canonical definition used by most practitioners: services organized around business capabilities, independently deployable, and owned by small cross-functional teams.

Event-Driven Architecture

In event-driven architecture, components communicate through events — discrete records of state change — rather than direct invocations. Producers emit events to a broker (Apache Kafka, AWS EventBridge, RabbitMQ); consumers subscribe independently. This decouples producers from consumers at the temporal and logical level.

Layered (N-Tier) Architecture

Layered architecture organizes code into horizontal tiers where each layer communicates only with the layer directly below it. A 4-layer model typically includes: Presentation → Application → Domain → Infrastructure. The Domain-Driven Design community formalizes this as the "Onion Architecture" or "Hexagonal Architecture" (Ports and Adapters), introduced by Alistair Cockburn in 2005.

Service-Oriented Architecture (SOA)

SOA predates microservices and shares the decomposition goal but differs in enterprise scope, heavier service contracts (WSDL/SOAP), and reliance on a central Enterprise Service Bus (ESB). The OASIS SOA Reference Model (OASIS Standard, 2006) defines a service as "a mechanism to enable access to one or more capabilities" under a formal contract (OASIS SOA-RM).


Causal relationships or drivers

Pattern selection is not primarily aesthetic — it is driven by four structural forces:

1. Team topology. Conway's Law (Melvin Conway, 1967) states that systems reflect the communication structures of the organizations that build them. A monolithic architecture is structurally aligned with a single integrated team; microservices are structurally aligned with multiple autonomous teams. The "Inverse Conway Maneuver" — deliberately restructuring teams to produce desired architecture — is documented in Team Topologies (Skelton and Pais, IT Revolution Press, 2019).

2. Deployment cadence requirements. Systems requiring independent release of subsystems cannot sustain a monolithic deployment model without significant coordination overhead. Continuous integration and continuous delivery pipelines are architecturally easier to implement when deployment units are small and independently versioned.

3. Regulatory and compliance boundaries. PCI DSS, HIPAA, and FedRAMP impose data isolation and audit trail requirements that often mandate architectural separation of components handling sensitive data. A single monolithic database serving payment and non-payment functions creates a wider PCI DSS scope than a separated payment microservice with its own isolated store (PCI DSS v4.0, PCI Security Standards Council).

4. Scale asymmetry. When one system component receives 50 times more traffic than others, monolithic scaling forces all components to scale together — wasting resources. Microservices allow per-service horizontal scaling, a direct driver for adoption in high-traffic consumer platforms.

Software scalability considerations are inseparable from architecture pattern decisions — the two topics interact directly at the design phase.


Classification boundaries

The critical distinctions that separate architectures from each other:

Boundary Monolith SOA Microservices
Deployment unit Single artifact Per-service (coarse-grained) Per-service (fine-grained)
Data ownership Shared database Shared database or federated Database per service
Communication In-process ESB / SOAP REST / async messaging
Service granularity N/A Business domain Bounded context
Team alignment Single team Multiple teams, shared bus Independent product teams

At the application level, MVC, MVP, and MVVM share the same separation of concerns principle but differ in how the presentation layer interacts with business logic:


Tradeoffs and tensions

Operational complexity vs. modularity

Microservices introduce distributed system problems — network latency, partial failure, distributed tracing, service discovery — that do not exist in monolithic systems. A 2020 analysis published by Salesforce Engineering documented that their internal microservices infrastructure required 3 dedicated platform teams to manage service mesh, observability, and deployment tooling before individual product teams could operate independently. The monitoring and observability discipline exists largely to address this complexity.

MVC tight coupling in large systems

MVC at scale — particularly in server-side web frameworks — frequently produces "fat controllers" where controller classes accumulate business logic that should reside in the model. This is a well-documented failure mode in Ruby on Rails and Django codebases and is the primary architectural reason frameworks like Rails introduced Service Objects and Interactor patterns as MVC extensions.

Distributed monolith anti-pattern

A system can be deployed as separate services while remaining architecturally monolithic if those services share a database, deploy together, or have synchronous dependency chains covering the full call graph. This anti-pattern is called the "distributed monolith" and combines the operational complexity of microservices with none of the modularity benefits. Technical debt accumulates rapidly in distributed monolith systems because the architectural problems are invisible until the coupling is tested under failure conditions.

Serverless and FaaS boundaries

Function-as-a-Service (FaaS) platforms (AWS Lambda, Google Cloud Functions, Azure Functions) represent an extreme of microservices decomposition where the deployment unit is a single function. The tradeoff is cold start latency — measured in 100–800 millisecond ranges for JVM-based functions under AWS Lambda — against near-zero infrastructure management overhead (AWS Lambda documentation, AWS).

Cloud-native software engineering frameworks, including the CNCF (Cloud Native Computing Foundation) landscape, document the full ecosystem of tooling that supports microservices and FaaS deployments.


Common misconceptions

Misconception: Microservices are always more scalable than monoliths.
Scalability is a property of infrastructure and code, not exclusively of architecture. A well-optimized monolith running on horizontally scaled infrastructure can outperform a poorly designed microservices system burdened by synchronous inter-service calls. StackOverflow's engineering blog documented that their monolithic architecture served over 1.3 billion page views per month from a small server cluster as of their 2016 infrastructure post — a performance profile most microservices implementations do not match per-node.

Misconception: MVC is a microservices alternative.
MVC operates at the application-level concern-separation layer; microservices operate at the system deployment layer. A single microservice can internally implement MVC. These patterns exist at different abstraction levels and are not mutually exclusive.

Misconception: SOA and microservices are the same thing.
SOA typically relies on a shared ESB, coarse-grained services, and shared data stores. Microservices explicitly reject the shared ESB, require data isolation, and are fine-grained at the bounded context level. Martin Fowler's 2014 characterization — "microservices are SOA done right" — is an oversimplification that obscures concrete architectural distinctions.

Misconception: Monoliths are legacy patterns.
The "Majestic Monolith" and "Modular Monolith" are recognized architectural choices for systems where team size, traffic volume, and organizational structure do not justify microservices overhead. Basecamp (37signals), Shopify, and Stack Overflow have published defenses of monolithic architectures at commercial scale.

Misconception: Event-driven architecture requires microservices.
Event-driven communication can exist within a monolith through in-process event buses (e.g., Guava EventBus, Spring ApplicationEvents). Event-driven architecture as a deployment pattern and event-driven communication as an in-process pattern are distinct.


Checklist or steps

Architecture Pattern Selection Evaluation Sequence

The following sequence reflects the structural decision points evaluated during architecture selection — not a prescriptive recommendation:

  1. Define deployment unit requirements — Identify whether independent release of subsystems is required. If all components must deploy together, evaluate layered monolith or modular monolith patterns first.

  2. Map team topology — Document team count, size, and communication boundaries. Cross-reference against Conway's Law implications. Teams fewer than 8 developers managing a microservices system incur coordination overhead that typically exceeds benefit.

  3. Identify data isolation requirements — Determine regulatory constraints (PCI DSS, HIPAA, SOC 2) that impose hard data boundary requirements. These may mandate specific architecture choices regardless of other factors.

  4. Assess traffic asymmetry — Profile per-component request volume and compute requirements. Asymmetry ratios above 10:1 between components are a quantified indicator that per-component scaling may be warranted.

  5. Evaluate organizational maturity for distributed operations — Confirm presence of DevOps practices, service mesh tooling, distributed tracing, and on-call engineering culture. Microservices in organizations without this infrastructure generate operational failures.

  6. Select application-level pattern — Independently of system topology, select MVC, MVP, MVVM, or hexagonal architecture based on UI framework requirements and testability constraints per design patterns standards.

  7. Define integration pattern — Select synchronous (REST, gRPC) or asynchronous (event-driven, message queue) communication based on latency tolerance and consistency requirements. Document API design and development contracts for each service boundary.

  8. Document architectural decisions — Record pattern selection, rejected alternatives, and driving quality attributes using Architecture Decision Records (ADRs) per ISO/IEC/IEEE 42010:2011 documentation requirements.

  9. Plan for legacy system modernization — If the current system is a monolith being decomposed, identify the Strangler Fig pattern vs. big-bang rewrite as the migration approach and establish service extraction sequencing.


Reference table or matrix

Architecture Pattern Comparison Matrix

Pattern Deployment Unit Data Model Scaling Unit Communication Organizational Fit Complexity Level
Monolith (Traditional) Single binary Shared DB Full application In-process Single team ≤20 Low
Modular Monolith Single binary Shared DB (module-isolated schemas) Full application In-process (bounded) Single team, modular discipline Medium
Layered / N-Tier Single or per-tier Shared DB Full application or per-tier In-process or HTTP Enterprise IT, clear separation needs Medium
MVC Within application Model layer N/A (app-level) In-process Any web/GUI team Low–Medium
SOA Per coarse-grained service Shared or federated Per service ESB / SOAP / HTTP Large enterprise, centralized governance High
Microservices Per bounded context Database per service Per service REST / async messaging Multiple autonomous teams High
Event-Driven Per producer/consumer Event store or per-service Per consumer Message broker Teams requiring decoupled async workflows High
Serverless / FaaS Per function External (DynamoDB, S3, RDS) Per function auto-scaled HTTP trigger / event Small teams, variable load, low-ops preference Medium–High
Hexagonal (Ports & Adapters) Within application Domain-isolated N/A (app-level) Port interfaces TDD-focused teams, high testability requirements Medium

Application-Level Pattern Comparison

Pattern Presentation Logic Location Data Binding Primary Use Case Framework Examples
MVC Controller + View Manual / framework-managed Server-side web, REST APIs Rails, Django, Spring MVC, ASP.NET MVC
MVP Presenter (all UI logic) Manual Android (legacy), WinForms Android MVP libraries
MVVM ViewModel (reactive) Two-way