I'm available to deliver your SaaS MVP in a few weeks, full-stack with the new Next.js App Router. Accelerate Your MVP Launch Now.

What is software architecture?

Learn what software architecture is, why it's important, and how components, modules, and layers help you modularize your applications.

Flavio Silva
Flavio Silva • December 20, 2023
Updated on February 8, 2024
What is software architecture?
Image by Freepik

Table of Contents

Introduction

This short article is my brief take on the vast and complex subject of software architecture.

There's no objective way to describe software architecture and its elements. Nonetheless, we must take on the task and use our best judgment to understand it as much as possible, aiming to build better software.

The terms and definitions I use here are used differently elsewhere, as there's no consensus on most definitions of the elements of software architecture.

That said, I hope the present article can help the reader better understand the basics of software architecture, some of its elements, and why it matters.

What is software architecture?

Software architecture is the foundation of any software system.

It is a conceptual framework defining the "what" (the system's high-level structure, components, and relationships) and the "why" (the strategic decisions) to translate functional and non-functional requirements into a roadmap to "how" we should successfully build the system.

How is software design different than software architecture?

While software architecture acts as the blueprint, defining the high-level "what" and "why" of a system, software design delves deeper into "how" to translate that blueprint into working implementations of individual design principles, components, and algorithms.

Why software architecture is important?

A well-defined architecture helps developers understand the system and their role in building it. It promotes effective communication, collaboration, and alignment with business goals across different teams while enforcing clear conventions.

It ensures the system meets its functional requirements, i.e., what functions it must perform, and non-functional requirements, like performance, scalability, stability, modularity, reusability, security, and changeability, while reducing risks and costs.

However, building a complex system is like building a giant puzzle. Imagine assembling a thousand-piece image without any organization or grouping of pieces. It would be chaotic and overwhelming.

That's known as the Big ball of mud in the software engineering world.

The process becomes manageable and efficient by dividing the image into smaller sections based on color or theme.

The same principle applies to software systems, and we use software architecture to break a complex system down into smaller, simpler subsystems.

In this article, we'll see how we can do that with components, modules, and layers.

Components, modules, and layers

Components, modules, and layers are the architectural building blocks of software architecture.

When discussing software architecture at a higher level, we often use the terms components, modules, and layers interchangeably, referring to logical units that provide functionality.

To illustrate, take these two sentences:

  • Software architecture is a conceptual framework defining the system's high-level structure, components, and relationships.

  • The system is built upon three main components: user management, music catalog, and recommendation engine.

In those contexts, components mean coarse-grained logic units, like modules, layers, libraries, or services, instead of fine-grained logic units, like individual classes or functions.

However, when discussing software architecture at a lower level, we distinguish those terms by their level of granularity, among other criteria, and that's what we'll see next.

Most importantly, components, modules, and layers are communication mechanisms that tell a story about your system.

What are components?

Components are the smallest units of architectural building blocks, the finest-grained units of functionality. That's where we typically write our lines of code to solve the actual problems.

Components have the following attributes:

  • They are fine-grained, typically implemented as a single file with a single class, function, or a small set of tightly related functions.
  • They have a single responsibility, i.e., their internal implementation should have high-cohesion.
  • They are loosely coupled among them, i.e., fewer to no dependencies on other components.
  • They encapsulate their internal implementation details, e.g., through private methods/functions.
  • They expose well-defined interfaces through which they communicate with other components, e.g., through public methods/functions.
  • They can be composed of other components, forming hierarchical structures.
  • They are easy to test.
  • They promote modularity, reusability, and separation of concerns at a fine-grained level.

There are different types of components, including user interface, database access, and business logic components.

No mud

Tiny applications have a handful of components.

For example, a currency converter web application with a single page may have only two components: one for handling user input for the amount of money and currency selection and another for retrieving exchange rate data from a third-party service and performing the conversion.

In such cases, breaking the application into two components is enough.

That's a monolith application, and it's appropriate.

A small ball of mud

Small applications have dozens of components.

Imagine a simple task management web application where users can authenticate and create projects and tasks. The entire application could have about 20 components, each in a separate file. It's a simple application, but imagine all those 20 components in the same folder.

I don't know about you, but it would bother me. Of course, if we know that's all we'll ever have for this application, and it won't grow in any way, that's fine. It'd still bother me, but I'd forget about it since I wouldn't touch it again.

However, that's not how things work. In the real world, an application of that size would be a good MVP, with the next iteration adding features.

Now, imagine that small ball of mud growing. You know where things are going.

Issues with communication and work distribution among team members will also arise due to a lack of architecture as simple as it would be.

That's a monolith application, but it's not appropriate.

We broke down the application into proper components, just like we should. Yet, that's not enough; we need some organization among them. We need to break the system into meaningful subsystems to manage the complexity. We need modularization.

Modules and Modular Architectures

What are modules?

Modules (a.k.a. packages) are both organizational and logical units.

They cohesively organize the system's functionality by grouping related components and creating individual, coarse-grained logic units.

That eases the cognitive load. Understanding one module at a time is more manageable than wrapping our heads around a whole system.

We can think of components as individual paragraphs of our system's story. Individually, they tell very little. But when organized into chapters, they become much more meaningful. Modules are the chapters of a system, assembling components meaningfully.

Each module operates independently and encapsulates a specific functionality, and we can develop, maintain, and reuse modules without affecting other modules in the system.

Modules have the following attributes:

  • They are coarse-grained, typically implemented as a single folder containing several component files.
  • They have a single responsibility at a coarse-grained level. For example, a user management module is solely responsible for user management functionality.
  • They are loosely coupled, i.e., fewer to no dependencies on other modules.
  • They encapsulate their internal implementation details, e.g., through private components not exported to outside modules.
  • They expose well-defined interfaces through which they communicate with other modules, e.g., through public components exported to outside modules.
  • They may have submodules, forming hierarchical structures.
  • They are easy to test.
  • They promote modularity, reusability, and separation of concerns at a coarse-grained level.

We can design modules after technical or business features.

A data access module is a technical feature module that groups components interacting with a database. Technical feature modules are typically used as layers in layered architectures and are known as package by layer (more next).

A user management module is a business feature module that groups components managing user accounts and is known as package by feature.

Modules are the foundation of modular software architectures.

Modular Architectures

A modular architecture is an architectural style that breaks the system into meaningful subsystems; each subsystem is a self-contained module. There are many modular architectures, each focusing on solving a particular set of problems.

Some modular architectures allow for the independent deployment of individual modules.

Large companies may assemble small engineering teams responsible for fewer modules. A particular team of business experts works with a specific team of engineers to build an application feature without unnecessary involvement, simplifying and speeding up the process.

The advantages of modular architectures result from the characteristics of the individual modules discussed earlier.

These are some popular modular architectures:

Monolith applications

Before addressing modular monolithic architectures, let's briefly overview monolithic applications.

A monolith application is built as a single, tightly integrated unit. Components are interconnected and dependent on each other. Typically, the application runs as a single process and is deployed as a whole.

That brings simplicity to development and deployment but makes it hard to change and scale. Changing one feature usually requires changing unrelated parts of the application.

Monolithic applications don't scale well. As they grow with new features, they become big balls of mud.

But monolithic applications don't have to be like that. They can be modular.

Modular Monolith architectures

Modular monolithic applications are still built as single units that run in a single process and are deployed as a whole. That makes them easy to manage.

However, they are modular, with most of the benefits of modular architectures.

They're broken down into small, loosely coupled modules. Changing one feature usually requires changing only one module.

Building on the task management app example we saw earlier, we could break it into three modules: user management, projects, and tasks, bringing many of the benefits of a modular architecture to a monolith application.

Modular monolithic applications scale well for dozens of modules and might be an excellent solution for small to mid-sized applications. But for larger applications, we might need something else.

Microservices architectures

Microservices is an architectural style that breaks down an application into small, self-contained, and loosely coupled services (modules) that operate independently and communicate with each other through well-defined interfaces and do not have a strict hierarchical relationship. They are developed, deployed, and scaled independently. We can implement different services in different technologies and deploy them to varying infrastructures according to their needs.

On the other hand, they introduce much complexity in managing, monitoring, and operating distributed systems. Microservices architectures are usually an answer to monolithic applications, and we should consider them for large systems.

Plug-in architectures

Plug-in architectures build upon the principles of modularity to support third-party modules (plug-ins) to be seamlessly plugged into the core of a system to extend its functionalities. Plug-ins interact with the system's core through well-defined interfaces, making them easier to develop, maintain, and replace.

Layers and Layered architectures

The layered architecture is one of the most common architectural patterns and is standard for most Java EE applications.

In a layered architecture, we organize components into horizontal layers, each on top of the other. The communication between layers is typically unidirectional, with a higher layer only using the services of its lower layer.

Although there are many variations of the layered architecture, the industry has converged to some variation of the following four layers:

Layered Architecture

Presentation Layer (UI layer)

The Presentation layer provides reusable UI components responsible for rendering data to the user and handling user interaction events like button clicks. Their components are stateless or have a minimal UI-related state.

Application Layer

The Application layer is a thin layer that provides components responsible for business tasks, translating UI events, like button clicks, into application events and flows, like authenticating users. It coordinates and delegates work by implementing and reusing logic from other layers. It doesn't have a business-related state, but it might have an application-related state necessary to complete a multi-step task.

Domain Layer

The domain layer is the heart of complex software. It provides components representing the concepts of the business responsible for executing business logic and rules. It might have a state related to the business and delegate data access tasks to the Data Access layer.

Data Access Layer

The Data Access layer provides components to access data from different sources, abstracting away the specific details of data persistence.

Layers have the following attributes:

  • They are coarse-grained, typically implemented as a single folder containing several component files.
  • They have a single responsibility at a coarse-grained level. For example, the domain layer is solely responsible for representing business concepts and executing business logic and rules.
  • They are loosely coupled, i.e., they usually communicate only to the layer below it.
  • They encapsulate their internal implementation details, e.g., through private components not exported to other layers.
  • They expose well-defined interfaces through which they communicate with other layers, e.g., through public components exported to other layers.
  • They are easy to test.
  • They promote modularity, reusability, and separation of concerns at a coarse-grained level.

Layered architecture downsides

  • In a layered architecture, code for each business feature is split into layers. Changing one feature might span multiple layers, making them more complicated and time-consuming.
  • Communication between layers adds overhead, especially in cases where data needs to pass through multiple layers.
  • Managing intricate interactions between layers can be challenging for large systems.
  • Tightly coupled layers can hinder maintenance and evolution if not appropriately designed.

Other types of software architectures

Modular and layered architectures are just two styles of architecture, and most of the time, more than one architectural style is used on large applications.

Other architectural styles include Event-driven architecture (EDA), Service-oriented architecture (SOA), and Client-server architecture.

Common questions about software architecture

Is software architecture only relevant for large projects?

Tiny applications and prototypes may not need an architecture.

However, a suitable architecture is always beneficial. Even small teams building MVPs can benefit from a clear understanding of their system's structure and components to ensure efficient development, changeability, and scalability.

Who architects a system?

The responsibility of architecting a system varies depending on the project's size and complexity, as well as the organizational structure and development methodology.

In large organizations, dedicated software architects may design and oversee the system's architecture.

The architecture might emerge collaboratively among developers and other team members in smaller teams or Agile environments.

In small projects, the lead developer may architect the system.

All stakeholders should understand the architecture and its rationale, regardless of who is responsible for the software architecture.

What artifacts make up a software architecture?

Software architecture comprises architectural diagrams, high-level descriptions, decision rationales, design documents, and component specifications. These artifacts reveal the "what" (structure, components, relationships) and "why" (strategic decisions, rationale) of a system, serving as the blueprint for successful development.

Does software architecture restrict flexibility and agility?

A well-defined architecture promotes flexibility and agility by providing a solid foundation for development. It establishes a shared understanding of the system, allows for modularity and independent development, and facilitates adaptation to changing requirements.

Is software architecture a one-time activity?

Software architecture is an ongoing process that starts in the early stages of development and continues throughout the system's lifecycle. The architecture's continued review, adaptation, and communication are crucial to ensure its relevance and effectiveness.

Does software architecture guarantee success?

While a well-designed architecture significantly increases the chances of project success, it's not a magical solution. A combination of skilled developers, effective project management, and continuous improvement is essential for ensuring a high-quality software system's successful development and deployment.

Classic books about software architecture

Here's a list of some classic books on software architecture:

Software Architecture: Perspectives on an Emerging Discipline, by Mary Shaw and David Garlan.

Patterns of Enterprise Application Architecture, by Martin Fowler.

Domain-Driven Design, by Eric Evans.

Clean Architecture: A Craftsman's Guide to Software Structure and Design, by Robert C. Martin (Uncle Bob).

Software Architecture in Practice, by Len Bass, Paul Clements, and Rick Kazman.

Conclusion

Components, modules, and layers are the architectural building blocks of software architecture. But software architecture is much more than that. It's about the important things in a software system that will define how straightforward or convoluted the development of a system will be.

It's important to note that the architecture of software is not something apart from the software itself, the codebase. An architecture might have many non-code artifacts, like diagrams and written documents, but code is the ultimate expression of a software architecture. If the codebase is messy, nothing else matters.

As the system evolves, we must refactor it at all levels: components, modules, layers, diagrams, documents, etc. Otherwise, we lose most of the benefits of software architecture and end up with a big ball of mud that happens to have some elements of architecture on it.

Does this article help you get a better understanding of software architecture?

Share your insights in the comments below. I'd love to hear your thoughts.

I'm using Giscus, so you can comment and give a thumbs up with your GitHub account. 😉

While you're here, please share this article. Your support means a lot to me!



Thanks for reading!


Software Architecture Guide

Bibliography

Evans, Eric. Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley Professional, 2003.

Fowler, Martin. Patterns of Enterprise Application Architecture. Addison-Wesley Professional, 2002.




I incorporated generative AI tools into my workflow, and I love them. But I use them carefully to brainstorm, research information faster, and express myself clearly. It's not copied and pasted in any possible way.

What is software architecture? by Flavio Silva is licensed under a Creative Commons Attribution 4.0 International License.

Leave a comment using your GitHub account

© 2024 Flavio Silva