Fundamentals of Software Engineering

Chapter 6
Software Architecture and Design

Diploma in Software Engineering
Fundamentals of Software Engineering   •   Diploma

Software Architecture and Design

Introduction

Software architecture and design provide the bridge between a software requirement and an implementable solution. Architecture describes the system’s major structures, responsibilities, interactions, and important quality decisions. Design refines those decisions into modules, interfaces, data structures, algorithms, and user interactions. A well-designed system is not merely functional; it is understandable, changeable, testable, secure, usable, and economical to operate.

Architecture is concerned primarily with the large-scale organisation of a system. Design operates at a more detailed level, deciding how each component behaves and collaborates. The two activities are related and iterative: architectural decisions constrain detailed design, while detailed design may reveal architectural problems.

For example, an online examination system might be organised as a web client, an application service, an assessment component, an identity component, and a database. Design then specifies interfaces such as submitAnswer(), validation rules, database tables, error handling, and screen layouts.

Learning Objectives

By the end of this chapter, you should be able to:

  • explain abstraction, modularity, separation of concerns, cohesion, and coupling;
  • apply SOLID principles to object-oriented designs;
  • compare common architectural styles and select an appropriate style;
  • use design patterns to solve recurring design problems;
  • design clear and reliable APIs;
  • develop a basic relational database design;
  • apply usability principles to software interfaces;
  • evaluate design trade-offs, risks, and quality attributes.

From Requirements to Architecture

A requirement describes a need or constraint. Architecture translates important requirements into structures and relationships. Functional requirements state what the system does, while quality requirements state how well it must do it. Examples of quality attributes include performance, availability, security, maintainability, scalability, portability, and usability.

Architectural decisions should be justified by requirements. A system requiring independent deployment of services may favour a service-oriented architecture. A small desktop tool with modest reliability requirements may be better served by a modular monolith. There is no universally best architecture; suitability depends on context, constraints, team capability, budget, and expected change.

Architecture decisions commonly include:

  • allocation of responsibilities to components;
  • communication mechanisms and interface protocols;
  • data ownership and persistence strategy;
  • deployment nodes and infrastructure;
  • security boundaries and trust relationships;
  • error-handling and recovery strategies;
  • decisions that are difficult or expensive to reverse.

Architectural documentation should record the decision, alternatives considered, reasons for selection, assumptions, and consequences. This prevents important reasoning from being lost when team members change.

Abstraction and Separation of Concerns

Abstraction means representing the essential properties of something while suppressing irrelevant detail. A payment interface may expose pay(amount) without exposing the bank’s internal transaction protocol. Abstraction reduces cognitive load and permits implementation changes behind a stable boundary.

An abstraction must be meaningful and sufficiently complete. An interface that hides every detail but provides unclear behaviour is not useful. Good abstractions describe responsibilities, inputs, outputs, errors, and important side effects.

Separation of concerns divides a system according to distinct responsibilities. User-interface rendering, business rules, persistence, authentication, logging, and external communication should not be unnecessarily mixed. Separation improves comprehension and allows a change in one concern to have limited effect elsewhere.

A common layered separation is:

  1. Presentation layer – screens, controllers, and request formatting.
  2. Application layer – use-case coordination and transaction boundaries.
  3. Domain layer – business concepts and rules.
  4. Infrastructure layer – databases, files, messaging, and external services.

Layers are a design aid, not an excuse for rigid or meaningless indirection. A small program may need only a few well-defined modules. Excessive layers can increase complexity and latency.

Modularity, Cohesion, and Coupling

A module is a unit of software that provides a focused responsibility through a defined interface. Modularity allows developers to understand, test, replace, and deploy parts of a system independently.

Cohesion is the degree to which the responsibilities within a module belong together. High cohesion is desirable. A TaxCalculator containing tax rules is more cohesive than a Utilities class containing unrelated date, file, formatting, and network functions.

Coupling is the degree of dependence between modules. Low coupling is desirable because changes in one module then have fewer consequences elsewhere. Passing a small, stable data structure is generally less coupled than allowing one module to manipulate another module’s internal database tables.

Useful design heuristics include:

  • keep public interfaces small and purposeful;
  • hide representation and implementation details;
  • depend on abstractions rather than concrete infrastructure;
  • pass only the information required for a responsibility;
  • avoid global mutable state;
  • make dependencies explicit;
  • keep business rules independent of user-interface technology where possible.

Cohesion and coupling are not absolute numerical properties in ordinary design work. They are judgement-based qualities supported by code review, dependency analysis, testability, and maintenance experience.

SOLID Design Principles

SOLID is a group of object-oriented design principles. They are guidelines rather than laws, and applying them mechanically can create unnecessary abstractions.

Single Responsibility Principle

A class or module should have one primary responsibility and therefore one major reason to change. An invoice class responsible for calculation, PDF layout, and email delivery has several unrelated reasons to change. Separating these responsibilities reduces ripple effects.

The principle does not mean that every class must contain one method. It means that related responsibilities should be grouped and unrelated change forces should be separated.

Open–Closed Principle

Software entities should be open for extension but closed for modification. New behaviour should preferably be added through polymorphism, composition, configuration, or a plug-in rather than by repeatedly editing stable code.

For example, a notification service can depend on a Notifier interface. Email, SMS, and push implementations can be added without changing the service’s core workflow. This principle is most valuable when variation is expected; speculative extension points add complexity.

Liskov Substitution Principle

Objects of a subtype should be usable wherever objects of the supertype are expected without breaking correctness. A subtype must honour the parent’s contracts, including meaningful preconditions, postconditions, and error behaviour.

If a ReadOnlyFile subtype inherits a write() operation but always fails, the abstraction may be misleading. The design should use a more appropriate interface, such as separate readable and writable abstractions.

Interface Segregation Principle

Clients should not be forced to depend on methods they do not use. Several focused interfaces are often better than one large interface. For instance, Printable, Scannable, and Faxable may be preferable to requiring every device to implement a single multifunction interface.

Dependency Inversion Principle

High-level policy should not depend directly on low-level implementation details. Both should depend on abstractions. Details such as a particular database driver should implement an interface required by the application’s policy.

Dependency injection is a common implementation technique. A service receives its repository through a constructor or method rather than creating a concrete repository internally. This supports testing, replacement, and configuration.

Architectural Styles

An architectural style is a recurring organisation of components and communication. Styles are starting points, not complete designs.

StyleMain ideaStrengthsTypical risks
LayeredComponents are arranged in responsibility layersUnderstandable, maintainableExcessive pass-through and performance cost
Client–serverClients request services from serversCentralised services and controlServer bottleneck or single point of failure
MonolithicSystem deployed as one unitSimple deployment and local callsLarge changes and scaling constraints
Modular monolithOne deployment with strong internal modulesSimpler operations with good boundariesModules may become tightly coupled
MicroservicesIndependently deployable servicesIndependent scaling and ownershipDistributed-system complexity
Event-drivenComponents communicate through eventsLoose temporal coupling and responsivenessOrdering, duplication, and observability challenges
Pipe-and-filterFilters transform streams of dataReusable transformationsData-format and error-propagation issues

Layered Architecture

A request commonly flows from presentation to application services, domain logic, and infrastructure. Dependencies should generally point toward stable policies rather than allowing domain rules to depend directly on a web framework or database driver.

Monolithic and Modular Monolithic Systems

A monolith is not necessarily poorly designed. A well-structured monolith can have strong module boundaries, a single deployment pipeline, and straightforward transactions. It is often suitable for a small team or early product. A modular monolith is frequently a practical step before considering distributed services.

Microservices

Microservices divide a system into independently deployable services, commonly organised around business capabilities. They require mature monitoring, automated deployment, service discovery or stable addressing, resilient communication, security, and data-consistency strategies. Splitting a system into services does not automatically improve maintainability.

Event-Driven Architecture

An event records that something has happened, such as OrderPlaced. Producers do not necessarily know every consumer. Consumers may update projections, send notifications, or begin fulfilment. Designers must address event schemas, idempotency, retries, dead-letter handling, ordering, and eventual consistency.

Design Patterns

A design pattern is a named, reusable solution structure for a recurring design problem. It is not a copy-and-paste class diagram and does not guarantee quality.

  • Factory Method or Abstract Factory encapsulates object creation when the concrete type varies.
  • Adapter converts one interface into another expected by a client.
  • Facade provides a simple interface to a complex subsystem.
  • Strategy encapsulates interchangeable algorithms, such as pricing rules.
  • Observer distributes notifications to interested subscribers, though uncontrolled subscriptions can create hidden coupling.
  • Repository provides a domain-oriented interface for accessing persisted objects.
  • Dependency Injection supplies dependencies from outside a component.
  • Model–View–Controller separates data or domain state, presentation, and input coordination.

Patterns should be selected because a real problem exists. Overuse creates indirection, more files, and harder debugging. A simple function is often better than a pattern when variation is not expected.

API Design

An application programming interface (API) is a contract through which software components communicate. An API may be a local programming interface, a REST-style HTTP interface, a message contract, or a library interface.

A useful API specifies:

  • resource or operation names;
  • input types, required fields, and validation rules;
  • output representations;
  • error formats and status meanings;
  • authentication and authorisation requirements;
  • idempotency and retry behaviour;
  • pagination, filtering, and sorting rules;
  • versioning and compatibility policy;
  • rate limits and observability requirements.

For a web API, HTTP methods should communicate intent. GET retrieves a representation, POST commonly creates or invokes a non-idempotent operation, PUT commonly replaces a resource, and DELETE removes a resource. The exact semantics must be documented and applied consistently.

An operation is idempotent if repeating it has the same intended effect as performing it once. A retry-safe payment operation may require an idempotency key so that a network timeout does not create two charges. Idempotency is a behavioural property, not merely a choice of HTTP verb.

Good APIs use stable names, predictable error responses, secure defaults, explicit validation, and documentation such as an OpenAPI description. Sensitive information should not appear in URLs or logs unnecessarily. Authentication identifies a caller; authorisation determines what that caller may do.

Database Design

Database design begins with the information and rules the system must preserve. In a relational design, entities are represented by tables, attributes by columns, and relationships by foreign keys.

A practical process is:

  1. identify business entities and events;
  2. list attributes and candidate identifiers;
  3. identify one-to-one, one-to-many, and many-to-many relationships;
  4. define primary and foreign keys;
  5. apply normalisation where appropriate;
  6. add constraints, indexes, and transaction rules;
  7. review performance and reporting requirements.

Consider a course-registration system. A student can register for many courses, and a course can have many students. This many-to-many relationship requires an associative table:

Student(StudentID, Name)

Course(CourseID, Title)

Enrollment(StudentID, CourseID, EnrolledAt, Grade)

The composite key (StudentID, CourseID) prevents the same student from being enrolled in the same course twice, unless the business explicitly allows repeated attempts. StudentID and CourseID are foreign keys.

Normalisation reduces inappropriate duplication. In first normal form, values are atomic and repeating groups are separated. Second normal form requires first normal form and removal of attributes that depend on only part of a composite key. Third normal form requires second normal form and removal of non-key attributes that depend on other non-key attributes. Normalisation improves consistency, but carefully justified denormalisation may improve read performance.

Integrity constraints should be enforced as close to the data as practical. Examples include NOT NULL, uniqueness, valid ranges, foreign-key rules, and transactions. Indexes accelerate selected queries but consume storage and add write overhead. They should be chosen from measured query patterns rather than added indiscriminately.

Usability and Human-Centred Design

Usability concerns whether intended users can learn, operate, and recover from a system effectively and satisfactorily. Usability is not a cosmetic final stage; it is an architectural and design concern because navigation, feedback, accessibility, and error handling affect the whole system.

Important principles include:

  • make system status visible;
  • use language and concepts familiar to users;
  • provide consistent controls and terminology;
  • prevent errors where possible;
  • provide clear, actionable error messages;
  • support recognition rather than requiring users to remember information;
  • allow cancellation, undo, and recovery where feasible;
  • support keyboard operation, readable contrast, labels, and assistive technologies.

A usability test should involve representative users performing realistic tasks. Developers should observe errors and completion difficulties rather than relying only on personal opinion. Accessibility should be considered from the beginning and assessed against applicable organisational and legal requirements.

Worked Design Example: A Clinic Appointment System

This is an illustrative case.

Background and Problem

A small clinic uses telephone bookings and spreadsheets. Patients experience duplicate appointments, staff cannot easily identify available clinicians, and management lacks reliable reports. The clinic wants a web system for patients, receptionists, and clinicians.

Analysis

Key functional requirements are account management, appointment search, booking, cancellation, clinician schedules, reminders, and reports. Important quality requirements include confidentiality, availability during working hours, usability for non-technical staff, auditability, and prevention of double booking.

The team considers microservices, a monolith, and a modular monolith. The clinic has a small development and operations team and no requirement for independent scaling of business capabilities. A modular monolith is selected because it provides clear boundaries without introducing unnecessary distributed-system complexity.

Architecture and Design

The modules are Identity, Patient, Scheduling, Booking, Notification, and Reporting. Presentation controllers call application services. Domain services enforce booking rules. Repositories isolate database access. The Notification module uses a strategy abstraction so email and SMS providers can vary.

The booking use case follows this logic:

  1. authenticate the user and verify authorisation;
  2. validate patient, clinician, time, and appointment type;
  3. begin a database transaction;
  4. check that the requested slot is available;
  5. create the appointment under a database constraint that prevents conflicting bookings;
  6. commit the transaction;
  7. publish a booking event or schedule a notification reliably;
  8. return a confirmation identifier.

The availability check must not rely only on a user-interface check, because two users may submit simultaneously. The database and transaction design must enforce the invariant. An invariant is a condition that must remain true, such as “a clinician cannot have two appointments occupying the same slot.”

A possible API operation is:

POST /appointments

Request fields include patientId, clinicianId, startTime, duration, and an idempotency key. A successful response returns a confirmation identifier. Validation errors identify invalid fields without exposing confidential information. A conflict response indicates that the slot is no longer available.

Outcome

The design reduces duplicate bookings, isolates business rules from the web interface, and permits notification providers to change. It remains operationally simpler than a microservice solution. Future risks include increased reporting load, evolving privacy requirements, and integration with external health-record systems.

Learning Points

The case demonstrates that architecture should follow requirements and organisational capability. Modularity, database constraints, API contracts, and usability work together. A design decision is valuable only when its consequences are understood and managed.

Quality Evaluation and Trade-offs

Architecture involves trade-offs. Strong security controls may add friction. Caching may improve response time but introduce stale data. A highly normalised database may simplify updates but require more joins. Independent services may enable separate deployment but increase network and operational complexity.

Useful evaluation questions include:

  • Which requirements does this decision support?
  • What happens when a dependency fails?
  • How will the design be tested and monitored?
  • What data is duplicated, and how is consistency maintained?
  • How difficult is migration or replacement?
  • Who owns each component and its data?
  • What assumptions could become invalid?

Architecture reviews, threat modelling, prototypes, automated tests, static analysis, dependency graphs, and performance measurements provide evidence. Architectural fitness functions—automated checks for properties such as forbidden dependencies or response-time thresholds—can help preserve important qualities, but they must measure meaningful outcomes.

Ethics, Governance, and Contemporary Developments

Software architecture affects privacy, fairness, safety, and access. Data minimisation, purpose limitation, retention controls, encryption, access logging, and secure deletion should be designed rather than added after deployment. Systems handling health, financial, educational, or identity data require particularly careful governance.

AI-enabled features introduce additional architectural concerns: model versioning, prompt and data protection, output validation, human oversight, monitoring for bias and drift, and clear responsibility when an automated recommendation is wrong. An AI component should be treated as a fallible dependency, not as an unquestionable authority.

Contemporary practice increasingly uses cloud-managed services, containers, infrastructure as code, platform engineering, event streaming, and observability. These technologies can improve delivery but do not replace sound boundaries and requirements analysis. Teams should avoid adopting fashionable technologies without a measurable problem to solve.

Best Practices

  • begin with requirements and quality attributes, not technology preferences;
  • keep architecture as simple as the problem permits;
  • define responsibilities and boundaries explicitly;
  • favour high cohesion and low coupling;
  • protect domain rules from infrastructure details;
  • use interfaces and dependency injection where variation or testing requires them;
  • document significant decisions and their consequences;
  • enforce data integrity with suitable constraints;
  • design APIs for clarity, security, compatibility, and failure;
  • test usability and accessibility with representative users;
  • monitor real systems and revise architecture when evidence demands it;
  • treat security, privacy, and reliability as design properties.

Chapter Summary

Software architecture organises a system’s major structures, responsibilities, interactions, and quality decisions, while software design refines those decisions into implementable modules and interfaces. Abstraction hides irrelevant detail; separation of concerns prevents unrelated responsibilities from becoming entangled. Modularity is strengthened by high cohesion, low coupling, explicit dependencies, and stable interfaces.

SOLID principles provide guidance for object-oriented design: focused responsibilities, controlled extension, substitutable subtypes, small interfaces, and dependency on abstractions. Layered, monolithic, modular monolithic, microservice, event-driven, and pipe-and-filter styles offer different benefits and risks. Design patterns such as Adapter, Strategy, Facade, Repository, and Dependency Injection address recurring problems but should not be applied mechanically.

Effective APIs define inputs, outputs, errors, security, compatibility, and retry behaviour. Database design uses entities, keys, relationships, constraints, normalisation, transactions, and carefully selected indexes. Usability requires visible status, consistency, error prevention, recovery, and accessibility. Professional architecture is evidence-based and must account for operational, ethical, privacy, security, and organisational consequences.

Key Terms

Abstraction: Representation of essential behaviour while hiding irrelevant implementation detail.

API: A contract through which software components communicate.

Architectural style: A recurring organisation of components and their interactions.

Cohesion: The degree to which responsibilities within a module belong together.

Coupling: The degree of dependency between modules.

Dependency injection: Supplying a component’s dependencies from outside the component.

Encapsulation: Restricting direct access to internal state and implementation details.

Event-driven architecture: An architecture in which components communicate through events.

Foreign key: A field that references a key in another database table.

Idempotency: Property whereby repeating an operation has the same intended effect as performing it once.

Invariant: A condition that must remain true during system operation.

Layered architecture: Organisation of software into responsibility-based layers.

Modularity: Division of software into units with defined responsibilities and interfaces.

Normalisation: Relational database organisation that reduces inappropriate redundancy.

Pattern: A reusable solution structure for a recurring design problem.

Primary key: An attribute or set of attributes that uniquely identifies a row.

Separation of concerns: Division of a system into distinct responsibilities.

SOLID: Five guidelines for maintainable object-oriented design.

Usability: The effectiveness, efficiency, and satisfaction with which users achieve goals.

Quality attribute: A non-functional property such as security, performance, or maintainability.

Review Questions

Short Answer Questions

  1. Distinguish software architecture from detailed software design.
  2. What is the relationship between cohesion and coupling?
  3. State the purpose of the Dependency Inversion Principle.
  4. Define API idempotency and give one example.
  5. Why are database constraints important in an appointment system?

Long Answer Questions

  1. Explain abstraction, modularity, and separation of concerns, using a layered application as an example.
  2. Describe all five SOLID principles and discuss one possible misuse of SOLID.
  3. Compare a modular monolith with microservices for a small organisation.
  4. Explain the main stages of designing a relational database for course registration.
  5. Discuss how usability and accessibility should influence architecture and interface design.

Analytical / Application Questions

  1. A payment application has duplicated validation logic in its web, mobile, and reporting modules. Analyse the design problem and propose a better structure.
  2. Two users can book the same appointment because both screens show a slot as available before either request is saved. Design an API, transaction, and database strategy to prevent this race condition.
  3. A team proposes converting every module of a new student-management system into a microservice. Evaluate the proposal using requirements, team capability, operational risks, and alternative architectures.

Multiple Choice Questions with Answers

Question 1

Which statement best describes cohesion?

A. The degree to which a module’s responsibilities belong together

B. The number of users who access a module

C. The speed at which a module executes

D. The number of database tables in a system

Correct Answer: A. The degree to which a module’s responsibilities belong together

Question 2

Which SOLID principle states that high-level policy should not depend directly on low-level implementation details?

A. Single Responsibility Principle

B. Interface Segregation Principle

C. Dependency Inversion Principle

D. Liskov Substitution Principle

Correct Answer: C. Dependency Inversion Principle

Question 3

Which architectural choice is often suitable for a small team seeking strong internal boundaries without distributed-system complexity?

A. Modular monolith

B. Unmanaged peer-to-peer system

C. Fully duplicated database architecture

D. Microservices without monitoring

Correct Answer: A. Modular monolith

Question 4

What is the main purpose of an Adapter pattern?

A. To notify all subscribers of an event

B. To convert one interface into another expected by a client

C. To store every object permanently

D. To enforce database normalisation

Correct Answer: B. To convert one interface into another expected by a client

Question 5

Which database structure correctly represents a many-to-many relationship between students and courses?

A. One Student row containing an unlimited list of Course columns

B. One Course row containing all Student names in one text field

C. An Enrollment table referencing both Student and Course

D. A separate database for every student

Correct Answer: C. An Enrollment table referencing both Student and Course

Question 6

Why should an appointment system enforce booking rules in the database or transaction boundary, rather than only in the user interface?

A. User interfaces cannot display dates

B. Concurrent requests can bypass a screen-level availability check

C. Databases cannot store appointment times

D. Transactions are used only for reporting

Correct Answer: B. Concurrent requests can bypass a screen-level availability check

Question 7

Which property means that repeating an operation has the same intended effect as performing it once?

A. Encapsulation

B. Idempotency

C. Polymorphism

D. Normalisation

Correct Answer: B. Idempotency

Question 8

Which is a usability best practice?

A. Hide system status to reduce visual complexity

B. Use different terminology for the same action on each screen

C. Provide clear, actionable error messages

D. Require users to remember information between screens

Correct Answer: C. Provide clear, actionable error messages

Question 9

What is a likely risk of adopting microservices without suitable operational maturity?

A. All components must use the same class

B. Distributed communication, monitoring, and consistency become more complex

C. The system cannot have APIs

D. The system automatically loses all modularity

Correct Answer: B. Distributed communication, monitoring, and consistency become more complex

Question 10

Which database mechanism most directly prevents duplicate enrollment for the same student and course?

A. A composite key on StudentID and CourseID

B. A larger screen resolution

C. A presentation-layer colour scheme

D. An event subscriber without persistence

Correct Answer: A. A composite key on StudentID and CourseID

Fundamentals of Software Engineering
Chapter 6 of 15