What are Software Architecture Patterns?
An architectural pattern is a general, reusable solution to a commonly occurring problem in software architecture within a given context. The architectural patterns address various issues in software engineering, such as computer hardware performance limitations, high availability and minimization of a business risk.
More specifically, an architectural pattern is a package of design decisions that is found repeatedly in practice, has well defined properties that can be reused and describes a class of architectures. So, developing an architecture can be seen as a process of selecting, tailoring, and combining patterns. The software architect must decide how to instantiate a pattern, how to make it fit with the specific context and the constraints of the problem.
According to Mark Richards, who wrote a book called Software Architecture Patterns, there are five major software architecture patterns: microservices, microkernel, Layered architecture, event-based, and space-based.
1. Microservices Pattern
When you write your application as a set of microservices, you’re actually writing multiple applications that will work together. Each microservice has its own distinct responsibility and teams can develop them independently of other microservices. The only dependency between them is communication. As microservices communicate with each other, you will have to make sure messages sent between them remain backward-compatible.
- You can write, maintain, and deploy each microservice separately
- Easy to scale, as you can scale only the microservices that need to be scaled
- It’s easier to rewrite pieces of the application because they’re smaller and less coupled to other parts
- New team members must quickly become productive
- The application must be easy to understand and modify
- Highly maintainable and testable – enables rapid and frequent development and deployment
- Independently deployable – enables a team to deploy their service without having to coordinate with other teams
- Contrary to what you might expect, it’s actually easier to write a well-structured monolith at first and split it up into microservices later. With microservices, a lot of extra concerns come into play: communication, coordination, backward compatibility, logging, etc. Teams that miss the necessary skill to write a well-structured monolith will probably have a hard time writing a good set of microservices.
- A single action of a user can pass through multiple microservices. There are more points of failure, and when something does go wrong, it can take more time to pinpoint the problem.
- Applications where certain parts will be used intensively and need to be scaled
- Services that provide functionality to several other applications
- Applications that would become very complex if combined into one monolith
- Applications with well-defined boundaries
- Rapidly developing new businesses and web applications
- Development teams that are spread out, often across the globe
- Websites with small components
2. Microkernel Pattern
It is typically used when software teams create systems with interchangeable components. Actually the microkernel architectural pattern is also referred to as a plug-in architectural pattern.
The microkernel architecture pattern is a natural pattern for implementing product-based applications. And a product-based application is one that is packaged and made available for download in versions as a typical third-party product. However, many companies also develop and release their internal business applications like software products, complete with versions, release notes, and pluggable features.
It applies to software systems that must be able to adapt to changing system requirements. It separates a minimal functional core from extended functionality and customer-specific parts. It’ll also serve as a socket for plugging in these extensions and coordinating their collaboration.
The microkernel architecture pattern allows you to add additional application features as plug-ins to the core application, providing extensibility as well as feature separation and isolation.
The microkernel architecture pattern consists of two types of architecture components: a core system and plug-in modules. Application logic is divided between independent plug-in modules and the basic core system, providing extensibility, flexibility, and isolation of application features and custom processing logic. And the core system of the microkernel architecture pattern traditionally contains only the minimal functionality required to make the system operational.
Perhaps the best example of the microkernel architecture is the Eclipse IDE. Downloading the basic Eclipse product provides you little more than an editor. However, once you start adding plug-ins, it becomes a highly customizable and useful product.
- Great flexibility and extensibility
- Some implementations allow for adding plug-ins while the application is running
- Good portability
- Ease of deployment
- Quick response to a constantly changing environment
- Plug-in modules can be tested in isolation and can be easily mocked by the core system to demonstrate or prototype a particular feature with little or no change to the core system.
- High Performance as you can customize and streamline applications to only include those features you need.
- Providing services in a microkernel system is expensive compared to the normal monolithic system. Context switch or a function call needed when the drivers are implemented as procedures or processes, respectively.
- It can be difficult to decide what belongs in the microkernel and what doesn’t.
- The predefined API might not be a good fit for future plug-ins.
- Applications that take data from different sources, transform that data and writes it to different destinations
- Workflow applications
- Task and job scheduling applications
- This pattern provides great flexibility and extensibility.
- Some implementations allow for adding plug-ins while the application is running.
- Microkernel and plug-ins can be developed by separate teams.
3. Layered Architecture Pattern
The layered pattern is probably one of the most well-known software architecture patterns. Many developers use it, without really knowing its name. The idea is to split up your code into “layers”, where each layer has a certain responsibility and provides a service to a higher layer.
Layered architecture patterns are n-tiered patterns where the components are organized in horizontal layers. This is the traditional method for designing most software and is meant to be self-independent. This means that all the components are interconnected but do not depend on each other. Each layer of the layered architecture pattern has a specific role and responsibility within the application. For example, a presentation layer would be responsible for handling all user interface and browser communication logic, whereas a business layer would be responsible for executing specific business rules associated with the request.
One of the powerful features of the layered architecture pattern is the separation of concerns among components. Components within a specific layer deal only with logic that pertains to that layer, in other words each layer has its own responsibility.
- Most developers are familiar with this pattern.
- High testability because components belong to specific layers in the architecture, other layers can be mocked or stubbed, making this pattern is relatively easy to test.
- High ease of development because this pattern is so well known and is not overly complex to implement, also most companies develop applications by separating skill sets by layers, this pattern becomes a natural choice for most business-application development.
- Easy to assign separate “roles”
- Easy to update and enhance layers separately
- It tends to lead to monolithic applications that are hard to split up afterward.
- Developers often find themselves writing a lot of code to pass through the different layers, without adding any value in these layers. If all you are doing is writing a simple CRUD application, the layered pattern might be overkill for you.
- Standard line-of-business apps that do more than just CRUD operations
- New applications that need to be built quickly
- Teams with inexperienced developers who don’t understand other architectures yet
- Applications requiring strict maintainability and testability standards
4. Event-based Pattern
This is the most common distributed asynchronous architecture used to develop highly scalable systems. The architecture consists of single-purpose event processing components that listen on events and process them asynchronously. The event-driven architecture builds a central unit that accepts all data and then delegates it to the separate modules that handle the particular type.
- This software architecture pattern can provide an audit log out of the box. Each event represents a manipulation of the data at a certain point in time.
- Are easily adaptable to complex, often chaotic environments
- Scale easily
- Are easily extendable when new event types appear
- It requires some discipline because you can’t just fix wrong data with a simple edit in the database.
- It’s not a trivial task to change the structure of an event. For example, if you add a property, the database still contains events without that data. Your code will need to handle this missing data graciously.
- Asynchronous systems with asynchronous data flow
- User interfaces
- Need to publish events to external systems
- Will be built with CQRS (Command and Query Responsibility Segregation)
- Have complex domains
- Need an audit log of changes to the data
5. Space-based Pattern
The space-based pattern (also sometimes referred to as the cloud architecture pattern) minimizes the factors that limit application scaling. This pattern gets its name from the concept of tuple space, the idea of distributed shared memory. High scalability is achieved by removing the central database constraint and using replicated in-memory data grids instead. Application data is kept in-memory and replicated among all the active processing units. Processing units can be dynamically started up and shut down as user load increases and decreases, thereby addressing variable scalability. Because there is no central database, the database bottleneck is removed, providing near-infinite scalability within the application.
Most applications that fit into this pattern are standard websites that receive a request from a browser and perform some sort of action. A bidding auction site is a good example of this. The site continually receives bids from internet users through a browser request. The application would receive a bid for a particular item, record that bid with a timestamp, and update the latest bid information for the item, and send the information back to the browser.
The space-based architecture pattern is specifically designed to address and solve scalability and concurrency issues. It is also a useful architecture pattern for applications that have variable and unpredictable concurrent user volumes. High scalability is achieved by removing the central database constraint and using replicated in-memory data grids instead.
The space-based architecture is designed to avoid functional collapse under high load by splitting up both the processing and the storage between multiple servers.
- Responds quickly to a constantly changing environment.
- Although space-based architectures are generally not decoupled and distributed, they are dynamic, and sophisticated cloud-based tools allow for applications to easily be “pushed” out to servers, simplifying deployment.
- High performance is achieved through the in-memory data access and caching mechanisms build into this pattern.
- High scalability comes from the fact that there is little or no dependency on a centralized database, therefore essentially removing this limiting bottleneck from the scalability equation.
- Achieving very high user loads in a test environment is both expensive and time consuming, making it difficult to test the scalability aspects of the application.
- Sophisticated caching and in-memory data grid products make this pattern relatively complex to develop, mostly because of the lack of familiarity with the tools and products used to create this type of architecture. Furthermore, special care must be taken while developing these types of architectures to make sure nothing in the source code impacts performance and scalability.
- High-volume data like clickstreams and user logs
- Low-value data that can be lost occasionally without big consequences
- Social networks
The important thing to remember is that there isn’t one solution that works everywhere. When we ask the question of which pattern to use for an application, the age-old answer still applies: “it depends.” You should weigh in on the pros and cons of a solution and make a well-informed decision.