-
Notifications
You must be signed in to change notification settings - Fork 135
SE 07 Software Architecture
Part of the Software Engineering Principles series
Software architecture is the high-level structure of a software system — the set of significant decisions about how the system is decomposed into components, how those components interact, and what principles guide those decisions.
Architecture is distinct from implementation in that architectural decisions are:
- Difficult to reverse once the system is built
- Systemic — they affect the entire codebase, not individual classes
- Driven by quality attributes — performance, scalability, security, maintainability
A useful definition: "Architecture is the decisions you wish you could get right early, because they're hard to change later."
Poor architectural decisions compound over time. A system with no clear layering becomes a "big ball of mud" — everything depends on everything else, no part can be changed safely, and no one understands the whole. The cost of adding new features grows while confidence in the stability of existing ones falls.
Good architecture:
- Makes the system easier to reason about
- Allows teams to work on different parts independently
- Localises the impact of change
- Enables testing without the full system
The most fundamental architectural pattern in enterprise applications is layering — organising code into horizontal tiers where each layer has a defined responsibility and depends only on the layers below it.
┌─────────────────────────────────────┐
│ Presentation Layer │ JSF views, REST controllers
├─────────────────────────────────────┤
│ Business Logic Layer │ Services, domain rules, workflows
├─────────────────────────────────────┤
│ Data Access Layer │ Repositories, JPA entities, queries
└─────────────────────────────────────┘
│
▼
Database
Presentation Layer Handles user interaction and display. In a web application this is the JSF XHTML views and their backing beans. In a REST API it is the JAX-RS resource classes. This layer should contain no business logic — only coordination between the UI and the service layer.
Business Logic Layer Contains the domain rules, workflows, and calculations that make the application valuable. This is where decisions are made: whether a patient is eligible for a service, how a bill is calculated, whether a stock issue is approved.
Data Access Layer Handles persistence — reading and writing to the database. JPA entities, repositories, and JPQL queries live here. This layer should contain no business logic.
Each layer may only depend on the layer directly below it. The presentation layer must not import from the data access layer. The data access layer must not call service methods.
Violations create tangled dependencies that are hard to test and dangerous to change.
MVC is a pattern for organising the presentation layer itself. It separates:
- Model — the data and business state (JPA entities, service return values)
- View — the rendered output (XHTML pages, JSON responses)
- Controller — the coordination layer that handles input, invokes services, and prepares the view
User Input
│
▼
┌─────────┐ invokes ┌─────────────────┐
│Controller│ ─────────────▶ │ Service Layer │
└─────────┘ └────────┬────────┘
│ │
│ updates │ reads/writes
▼ ▼
┌────────┐ ┌──────────┐
│ View │◀── renders ───────│ Model │
└────────┘ └──────────┘
In Java EE with JSF:
- The View is the XHTML page
- The Controller is the CDI backing bean (
@ViewScoped,@SessionScoped) - The Model is the JPA entity returned by the service
A more sophisticated pattern that places business logic at the centre, surrounded by adapters for the outside world.
┌──────────────────────────────────┐
│ │
DB ───▶│ ┌────────────────────────────┐ │◀─── REST API
│ │ │ │
UI ───▶│ │ Business Logic │ │◀─── Messaging
│ │ │ │
LIMS ───▶│ └────────────────────────────┘ │◀─── Batch Jobs
│ │
└──────────────────────────────────┘
Ports (interfaces)
Adapters (implementations)
Port: An interface defined by the business logic describing what it needs from the outside world (StockRepository, NotificationPort).
Adapter: A concrete implementation of a port that connects the business logic to a real technology (JpaStockRepository, EmailNotificationAdapter).
Key benefit: The business logic can be tested completely without any infrastructure — substitute test adapters for real ones.
A single deployable unit containing all application functionality.
Advantages:
- Simple to develop, test, and deploy
- No network overhead between modules
- Transactions span the whole system easily
- Suitable for small-to-medium systems and small teams
Disadvantages:
- Scaling requires scaling everything, not just the bottleneck
- A fault anywhere can affect everything
- Large teams have difficulty working independently
- Technology choices are uniform — you cannot use a different language for one module
Separate, independently deployable services that each own a specific business capability and communicate over a network.
Advantages:
- Services scale independently
- Teams can deploy independently
- Technology choice per service
- Failure is isolated
Disadvantages:
- Significant operational complexity (service discovery, load balancing, distributed tracing)
- Network calls are slower and less reliable than in-process calls
- Distributed transactions are hard
- Requires mature DevOps practices
A pragmatic middle ground: a modular monolith or a small number of clearly bounded services. Modules have clean interfaces between them but share a deployment and database. This allows iterative extraction into true microservices later without premature complexity.
Components communicate by emitting and consuming events rather than calling each other directly.
Stock Issue Service ──▶ [STOCK_ISSUED event] ──▶ Billing Service
──▶ Inventory Reporter
──▶ Reorder Monitor
Strengths:
- Very loose coupling — the emitter does not know who consumes the event
- New consumers can be added without touching the emitter
- Natural audit trail
Weaknesses:
- Harder to trace request flow
- Error handling is more complex
- Eventual consistency must be managed
In Java EE, CDI Events provide a lightweight in-process event mechanism without requiring a message broker.
Good architecture is explicitly designed for quality attributes (also called non-functional requirements):
| Attribute | Question it answers |
|---|---|
| Performance | How fast does the system respond under load? |
| Scalability | Can capacity be added as load increases? |
| Availability | What is the acceptable downtime? |
| Security | How does the system protect data and prevent unauthorised access? |
| Maintainability | How easy is it to change the system? |
| Testability | How easy is it to verify the system works correctly? |
| Deployability | How easy and safe is it to release changes? |
| Interoperability | How easily does the system integrate with others? |
Architectural decisions are trade-offs between these attributes. A decision that improves performance may reduce maintainability. Naming the trade-offs explicitly is part of good architectural practice.
| Anti-pattern | Description |
|---|---|
| Big Ball of Mud | No structure; everything depends on everything |
| God Service | One service that does everything |
| Chatty Interface | Hundreds of fine-grained calls where a few coarse ones would do |
| Shared Database | Multiple services writing directly to each other's tables |
| Distributed Monolith | Microservices that are so tightly coupled they must always deploy together |
| Anemic Domain Model | Entities that are pure data with all logic in service classes |
Previous: SE-06: Clean Code
Next: SE-08: Refactoring