Skip to content

SE 07 Software Architecture

Dr M H B Ariyaratne edited this page Jun 8, 2026 · 1 revision

SE-07: Software Architecture

Part of the Software Engineering Principles series


What Is Software Architecture?

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."


Why Architecture Matters

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

Architectural Layers

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.

The Classic Three-Layer Architecture

┌─────────────────────────────────────┐
│         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.

The Rule of Layering

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 — Model-View-Controller

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

Hexagonal Architecture (Ports and Adapters)

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.


Microservices vs. Monolith

Monolith

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

Microservices

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

Mini-Services

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.


Event-Driven Architecture

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.


Architectural Quality Attributes

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.


Common Anti-Patterns

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

Back to Software Engineering Principles

Clone this wiki locally