What Is an Architectural Pattern?
An architectural pattern is a reusable solution to a commonly occurring structural problem in software design. It’s not a specific piece of code — it’s a template for organizing components, defining their responsibilities, and specifying how they interact.
Think of it like structural systems in civil engineering. A moment frame, a braced frame, and a shear wall system are all patterns for resisting lateral loads. Each has trade-offs: moment frames offer open floor plans but are expensive; braced frames are efficient but restrict openings; shear walls are stiff but heavy. You don’t invent a new structural system for every building — you select and adapt a proven pattern.
Architectural patterns work the same way. They encode decades of collective experience about what structures work well for specific kinds of problems. This lesson covers three foundational patterns: Layered Architecture, Model-View-Controller (MVC), and the Monolith.
Pattern 1: Layered Architecture
Layered Architecture organizes a system into horizontal layers, where each layer provides services to the layer above it and consumes services from the layer below it. It’s the most common pattern in enterprise software and many engineering applications.
+-------------------------------+
| Presentation Layer | ← UI, CLI, API endpoints
+-------------------------------+
| Application Layer | ← Use cases, workflows, orchestration
+-------------------------------+
| Domain/Business Layer | ← Core logic, calculations, rules
+-------------------------------+
| Data Access Layer | ← Database queries, file I/O, external APIs
+-------------------------------+
| Infrastructure Layer | ← OS, network, hardware abstraction
+-------------------------------+
The Rules
- Strict layering: Each layer only communicates with the layer directly below it. The Presentation Layer never talks to the Data Access Layer directly.
- Relaxed layering: A layer can communicate with any layer below it (skipping intermediate layers). More flexible but harder to maintain.
- No upward dependencies: Lower layers never depend on upper layers. The Domain Layer doesn’t know the Presentation Layer exists.
When to Use Layered Architecture
- Standard business applications with CRUD operations, data validation, and reporting.
- Engineering tools where the core computation is distinct from input handling and output presentation.
- Teams organized by technical specialty (front-end team, back-end team, database team).
- Regulatory environments where separating concerns aids auditing and compliance.
When Layered Architecture Fails
- High-performance systems where the overhead of passing through layers is unacceptable. Each layer boundary adds latency.
- Highly interactive applications where the separation between presentation and logic creates awkward communication patterns.
- Microservices architectures where vertical slicing (by feature) is more appropriate than horizontal slicing (by technical concern).
- “Layer lasagna”: When layers become pass-through boilerplate — the Application Layer just calls the Domain Layer which just calls the Data Layer — the layering adds complexity without benefit.
Pattern 2: Model-View-Controller (MVC)
MVC separates an application into three interconnected components:
User Input
|
v
+-------------------+
| Controller | ← Handles input, updates Model, selects View
+-------------------+
| |
v v
+-----------+ +-----------+
| Model | | View |
+-----------+ +-----------+
| Data & | | Display |
| Logic | | & UI |
+-----------+ +-----------+
| ^
+-----------+
Notifies of changes
- Model: The data and business logic. It doesn’t know how data is displayed or how users interact with it. In an engineering context, this is your solver, your data structures, your domain calculations.
- View: The presentation layer. It displays data from the Model but contains no business logic. It could be a GUI, a web page, a CLI output, or a PDF report.
- Controller: The intermediary. It receives user input (button clicks, form submissions, command-line arguments), translates it into actions on the Model, and selects the appropriate View to display the result.
When to Use MVC
- Applications with a user interface — web apps, desktop apps, mobile apps.
- When the same data needs multiple presentations — the same structural model displayed as a 3D rendering, a data table, and a PDF report.
- When UI changes frequently but the underlying logic is stable.
- Web frameworks: Django, Rails, ASP.NET MVC, Spring MVC all implement this pattern.
Common Misuse of MVC
- Fat Controllers: Putting business logic in the Controller instead of the Model. The Controller should be thin — it translates input to Model actions and selects Views. If your Controller contains calculations, validation, or data transformation, those belong in the Model.
- Fat Views: Putting logic in the View (templates). If your HTML template contains
if balance > 0checks or complex formatting logic, that belongs in the Model or a helper. - Treating MVC as a silver bullet: MVC is for interactive applications. If you’re building a batch processing pipeline with no UI, MVC adds unnecessary structure.
Pattern 3: The Monolith
A monolith is a single, unified application where all components are part of one deployable unit. The entire application is built, tested, and deployed as one artifact.
+---------------------------------------------+
| Monolithic Application |
| |
| +----------+ +----------+ +----------+ |
| | Module A | | Module B | | Module C | |
| | (Users) | | (Orders) | | (Reports) | |
| +----------+ +----------+ +----------+ |
| |
| +----------+ +----------+ +----------+ |
| | Module D | | Module E | | Shared | |
| | (Auth) | | (Billing) | | Utilities | |
| +----------+ +----------+ +----------+ |
| |
| +---------------------------------------+ |
| | Shared Database | |
| +---------------------------------------+ |
+---------------------------------------------+
Advantages of the Monolith
- Simplicity. One codebase, one deployment, one database. No network calls between services, no distributed transactions, no service discovery.
- Easy to develop initially. Start coding, everything is in one place. IDE features like refactoring and “find all references” work across the entire codebase.
- Easy to test. Integration tests are straightforward — everything runs in one process.
- Easy to deploy. One artifact, one deployment process.
- Performance. In-process function calls are orders of magnitude faster than network calls between services.
Disadvantages of the Monolith
- Scaling is all-or-nothing. If one module needs more resources, you scale the entire application. You can’t scale the computation-heavy solver module independently from the lightweight user authentication module.
- Deployment risk. A bug in any module can bring down the entire application. Deploying a small change requires redeploying everything.
- Technology lock-in. The entire application must use the same language, framework, and runtime. You can’t write the solver in C++ and the UI in Python (easily).
- Team scaling is hard. As the team grows, developers step on each other’s toes. Merge conflicts increase, build times grow, and coordination overhead explodes.
- Modularity decay. Without discipline, module boundaries erode over time. Modules start importing each other’s internals, and the monolith becomes a “big ball of mud.”
The Monolith-First Heuristic
The reasoning:
- You don’t know where the module boundaries should be until you’ve built the system and seen how it’s actually used.
- Microservices introduce distributed systems complexity (network failures, eventual consistency, deployment orchestration) that you don’t need until you need it.
- A well-structured monolith with clear internal module boundaries can be decomposed into services later. A poorly-structured set of microservices is much harder to fix.
We’ll explore microservices and event-driven architectures in the next lesson — but understand that they are tools for specific scaling challenges, not default best practices.
Pattern Trade-Off Table
| Attribute | Layered | MVC | Monolith |
|---|---|---|---|
| Complexity | Low–Medium | Low–Medium | Low (initially) |
| Scalability | Vertical scaling | Depends on deployment | Limited (all-or-nothing) |
| Testability | Good (per layer) | Good (isolated Model) | Good (everything in-process) |
| Team scaling | Good (teams per layer) | Moderate | Poor at scale |
| Deployment | Single unit | Single unit | Single unit |
| Performance | Layer overhead | Minimal overhead | Best (in-process calls) |
| Best for | Enterprise apps, clear technical layers | Interactive apps with UI | Small–medium projects, early-stage products |
Exercise 5.1: Pattern Identification
- A desktop application for structural beam design. Engineers input beam dimensions, material properties, and loading conditions. The app calculates deflections, stresses, and code compliance ratios. Results are displayed in tables and diagrams. The app runs locally on the engineer’s workstation.
- An internal data pipeline for a research lab. It reads sensor data from CSV files, validates it, runs statistical analysis, and stores results in a PostgreSQL database. No user interface — it runs as a scheduled batch job every night.
- A web-based project management tool for a construction company. Project managers create projects, assign tasks, upload documents, and track budgets. Subcontractors can log in to view their assignments. The application needs to handle 50 concurrent users.
- A Python package that provides finite element analysis functions. Other engineers import it into their scripts. It has modules for mesh generation, element formulation, assembly, solving, and post-processing. It’s distributed via pip.
For each system, also answer: at what point would you consider moving to a different pattern, and why?
Quiz
Question: Your team has built a simulation platform as a well-structured monolith. It’s been running in production for two years. Recently, the solver module has become the performance bottleneck — it needs 10x more compute than the rest of the system. The web UI and database modules are fine. What is the most appropriate architectural response?
- Rewrite the entire system as microservices to improve scalability.
- Extract only the solver module into a separate service that can be scaled independently, keeping everything else as a monolith.
- Buy a bigger server — vertical scaling solves everything.
- Switch to MVC architecture to separate the solver from the UI.
Answer
b) Extract only the solver module into a separate service that can be scaled independently, keeping everything else as a monolith.
This is the “monolith first, extract when needed” approach in action. You have a proven bottleneck in a specific module, so you extract that module into its own service. The rest of the system works fine as a monolith — there’s no reason to add distributed systems complexity to modules that don’t need it. Option (a) is the classic over-engineering mistake: solving a localized problem with a system-wide rewrite. Option (c) might work short-term but doesn’t address the fundamental issue that one module has different scaling needs. Option (d) confuses architectural patterns — MVC is about separating UI concerns, not about scaling compute resources.