Which Software Architecture is right for you?
As a software architect, it’s natural to focus on the big picture of how a system, or combination of software systems are structured and fit together. However, I’ll be the first to admit that there are so many different ways of designing software that it’s easy to be very narrow-minded on the one or two approaches you are familiar with, without fully broadening your approach and identifying other structures which could actually be more suited to the problem you are trying to solve.
So with this in mind and as a result of a lot of research I have conducted myself on the different approaches and methodologies I have come across over the past few months, I would like to share my thoughts on the different popular software architectures out there and when one might be better suited to your needs.
What makes an architecture?
Before I get too far into the different types of architecture though, I think it’s worth defining exactly what a software architecture is. Well, most commonly it refers to the structure of your different software programs. A guideline to whether your system is broken down into small chunks or operates as a bigger piece in unison. It determines the rule and protocols by which the whole system should communicate and how data, security and endpoints are defined. It can also be extended further to include the choice of programming languages that a system should be developed in and the different rules applying to that, as well the overall structure of the code, linting, testing and even its deployment and repository configurations. So, in essence, almost everything.
However, for the purpose of this article I am going to be focusing only on the basic structure of a software layout and how the different pieces of software should fit together. In determining the other complex decisions around tooling and programming languages, I will perhaps leave that for another time or even suggest that you study out the different programming languages and their compilers in determining what would work best for you.
In short, software architecture can be defined as determining how the pieces of a complex puzzle fit together. While every part of a system is different, they need to fit together to form one bigger system (or complete puzzle) and software architecture is the design process of ensuring the pieces fit together to for a complete whole and not an incomplete puzzle.
The Different Types of Architectures
Layered architecture
This approach is probably the most common because it is usually built around the database, and many applications in business naturally lend themselves to storing information in tables. This is something of a self-fulfilling prophecy though as many of the biggest and best software frameworks—like Java EE, Drupal, and Express—were built with this structure in mind, so its inherently the architecture that comes most naturally to mind and which people use most often. Whether they know it or not.
In a layered architecture the code is arranged so the data enters the top layer and works its way down each layer until it reaches the bottom, which is usually a database. Along the way, each layer has a specific task, like checking the data for consistency or reformatting the values to keep them consistent. It’s common for different programmers to work independently on different layers.
As an example most of the popular web frameworks follow a layered architecture. Just above the database is the model layer, which often contains business logic and information about the types of data in the database. At the top is the view layer, which is often CSS, JavaScript, and HTML with dynamic embedded code. In the middle, you have the controller, which has various rules and methods for transforming the data moving between the view and the model.
The advantage of a layered architecture is the separation of concerns, which means that each layer can focus solely on its role. This makes it:
- Maintainable
- Testable
- Easy to assign separate roles
- Easy to update and enhance layers separately
Proper layered architectures will have isolated layers that aren’t affected by certain changes in other layers, allowing for easier refactoring. This architecture can also contain additional open layers, like a service layer, that can be used to access shared services only in the business layer but also get bypassed for speed.
Caveats:
- Source code can become big and messy if it is unorganized and the modules don’t have clear roles or relationships.
- Much of the code can be devoted to passing data through layers without using any logic. This can also make it slow from a performance perspective.
- Layer isolation, which is an important goal for the architecture, can also make it hard to understand the architecture without understanding every module.
- Coders can skip past layers to create tight coupling and produce a mess of complex interdependencies.
- Monolithic deployment is often unavoidable, which means small changes can require a complete redeployment of the application or a the very least, in-depth testing of the entire system.
Best for:
- New applications that need to be built quickly
- Enterprise or business applications that need to mirror traditional IT departments and processes
- Teams with inexperienced developers who don’t understand other architectures yet
- Applications requiring strict maintainability and testability standards
Event-driven architecture
Many programs spend most of their time waiting for something to happen. This is especially true for computers that work directly with humans, but it’s also common in areas like networks. Sometimes there’s data that needs processing, and other times there isn’t.
The event-driven architecture helps manage this by building a central unit that accepts all data and then delegates it to the separate modules that handle the particular type. This handler generates an “event,” and delegates to the code assigned to that type.
As an example, programming a web page with JavaScript involves writing the small modules that react to events like mouse clicks or keystrokes. The browser itself orchestrates all of the input and makes sure that only the right code sees the right events. Many different types of events are common in the browser, but the modules interact only with the events that concern them. This is very different from the layered architecture where all data will typically pass through all layers. Overall, event-driven architectures:
- Are easily adaptable to complex, often chaotic environments
- Scale easily
- Are easily extendable when new event types appear
Caveats:
- Testing can be complex if the modules can affect each other. While individual modules can be tested independently, the interactions between them can only be tested in a fully functioning system.
- Error handling can be difficult to structure, especially when several modules must handle the same events.
- When modules fail, the central unit must have a backup plan.
- Messaging overhead can slow down processing speed, especially when the central unit must buffer messages that arrive in bursts.
- Developing a system-wide data structure for events can be complex when the events have very different needs.
- Maintaining a transaction-based mechanism for consistency is difficult because the modules are so decoupled and independent.
Best for:
- Asynchronous systems with asynchronous data flow
- Applications where the individual data blocks interact with only a few of the many modules
- User interfaces
Microkernel architecture
Many applications have a core set of operations that are used again and again in different patterns that depend upon the data and the task at hand. The popular development tool Eclipse, for instance, will open files, annotate them, edit them, and start up background processors. The tool is famous for doing all of these jobs with Java code and then, when a button is pushed, compiling the code and running it.
In this case, the basic routines for displaying a file and editing it are part of the microkernel. The Java compiler is just an extra part that’s bolted on to support the basic features in the microkernel. Other programmers have extended Eclipse to develop code for other languages with other compilers. Many don’t even use the Java compiler, but they all use the same basic routines for editing and annotating files.
The extra features that are layered on top are often called plug-ins. Many call this extensible approach a plug-in architecture instead.
The solution is to push some basic tasks—like asking for a name or checking on payment—into the microkernel. The different business units can then write plug-ins for the different types of claims by knitting together the rules with calls to the basic functions in the kernel.
Caveats:
- Deciding what belongs in the microkernel is often an art. It ought to hold the code that’s used frequently.
- The plug-ins must include a fair amount of handshaking code so the microkernel is aware that the plug-in is installed and ready to work.
- Modifying the microkernel can be very difficult or even impossible once a number of plug-ins depend upon it. The only solution is to modify the plug-ins too.
- Choosing the right granularity for the kernel functions is difficult to do in advance but almost impossible to change later in the game.
Best for:
- Tools used by a wide variety of people
- Applications with a clear division between basic routines and higher order rules
- Applications with a fixed set of core routines and a dynamic set of rules that must be updated frequently
Microservices architecture
Designing software can be fun an easy in small packages, but the bigger it gets, the more unwieldy and difficult to manage it becomes. The microservice architecture is designed to prevent systems from becoming unwieldy, monolithic, and inflexible. Instead of building one big program, the goal is to create a number of different tiny programs and then create a new little program every time someone wants to add a new feature.
This approach is similar to the event-driven and microkernel approaches, but it’s used mainly when the different tasks are easily separated. In many cases, different tasks can require different amounts of processing and may vary in use. Think streaming sites which need to quickly scale up and be available to a new streaming customer at any given time, independent of other users. This type of scale is what is best suited to this type of architecture.
Caveats:
- The services must be largely independent or else interaction can cause the cloud to become imbalanced.
- Not all applications have tasks that can’t be easily split into independent units.
- Performance can suffer when tasks are spread out between different microservices. The communication costs can be significant.
- Too many microservices can confuse users as parts of the web page appear much later than others.
Best for:
- Websites with small components
- Corporate data centers with well-defined boundaries
- Rapidly developing new businesses and web applications
- Development teams that are spread out, often across the globe
Space-based / Cloud Architecture
Many websites/applications are built around a database, and they function well as long as the database is able to keep up with the load. But when usage peaks, and the database can’t keep up with the constant challenge of writing a log of the transactions, the entire website fails.
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. The data is spread out across the nodes just like the responsibility for servicing calls. This is a similar approach which is applied to cloud computing, which is why this architecture can also be referred to as the preferred Cloud Architecture. Many companies though are unable to break down their data sufficiently to make this work effectively.
Storing the information in memory makes many jobs much faster, and spreading out the storage with the processing can simplify many basic tasks. But the distributed architecture can make some types of analysis more complex. Computations that must be spread out across the entire data set—like finding an average or doing a statistical analysis—must be split up into sub tasks, spread out across all of the nodes, and then aggregated when it’s done.
Caveats:
- Transactional support is more difficult with RAM databases.
- Generating enough load to test the system can be challenging, but the individual nodes can be tested independently.
- Developing the expertise to cache the data for speed without corrupting multiple copies is difficult.
Best for:
- High-volume data like click streams and user logs
- Low-value data that can be lost occasionally without big consequences—in other words, not bank transactions
- Social networks
Building Your Puzzle
The below list is not a complete list of ways you can structure or design your software components but does cover some of the more popular ideologies out there. In any particular system, it is also possible that more than one approach exists depending on need and so most often you will find a hybrid of designs speaking together. Especially in bigger and more complex companies.
Hopefully at least in understanding some of the important ways you can structure your software programs it will help you to better understand how your software as a company/team could better fit together and better meet the solutions you require.