Basic Rules for effective Onion Architecture

Onion architecture is one of the two known “clean” software architectures. The other “clean” software architecture is widely known as Ports and Adapters pattern or Hexagonal architecture. Both makes an explicit separation on what belongs in the application core and what belongs outside such as databases, user interfaces and 3rd-party APIs.

It is a software architecture introduced by Jeffrey Palermo back in 2008 with his four-part series called The Onion Architecture. Like the Layered Architecture and Hexagonal Architecture, it uses the concept of layers but the difference lies in the following:

  • Domain Model layer – part of the domain layer where our entities and classes closely related to them e.g. value objects reside
  • Domain Services layer – part of the domain layer where domain-defined processes reside
  • Application Services layer – where application-specific logic i.e. our use cases reside
  • Outer layer (Infrastructure, Interfaces, Tests) – which keeps peripheral concerns like UI, databases or tests

In this article, you will get to see a set of rules that have been very helpful for me when I apply onion architecture in my software projects. Every layer has their respective rules and are categorised as such. These rules allowed me to focus on solving the domain problem and reduce the need to think about what codes should go where. I get increased productivity while also having structured flexibility in my codebase.

Now, some of these rules are derived based from what I have understood about the architecture while some of these rules have been developed by other expert software developers. There are also some rules that are not specific to onion architecture, but rather, just innate to the specific software pattern.

Let us dive in…

General

These rules are applicable to the whole application or software module.

Rule 1

Do not skip layers when calling methods or utilising functions that are in the deeper layers. Typical flow of execution is as follows:

Interfaces -> Application -> Domain or Infrastructure

Rule 2

Use static methods and classes as a last resort.

Rule 3

Use a dependency injection framework to implement the onion architecture.

Interface Layer

Rule 1

The interface layer only contains codes that handle the following:

  1. Deserialisation of incoming objects sent via API request/call.
  2. Serialisation of objects or messages for the purpose of responding to an API request/call.
  3. Exposure and implementation of RESTful API and SOAP-based web services

Rule 2

This is the topmost layer in the onion architecture that can work with a domain object such as aggregate root or entities directly.

Rule 3

Data transfer objects (DTO) are to be used when receiving data from an API or responding to an API request as they define the data contracts. Never use domain entities (e.g. aggregate root and value objects) to receive or return data via an API.

Rule 4

Use a facade to provide a common entry point for multiple endpoints (RESTful API, SOAP and direct function call) if they need to consume a service provided by the application layer.

Rule 5

Facades do not contain any business or domain logic.

Application Service Layer

Rule 1

The Application layer contains only codes related to the following:

  1. Coordination between domain objects, services and utilities.
  2. Database transaction control
  3. Logging
  4. Establishing connections to databases
  5. Application control or startup (e.g. main function/main class)

Rule 2

The Application Service layer is only concerned with the software use cases. Each method or function in a service class typically represents one use case.

Rule 3

Classes in the application layer never hold and maintain the state of any domain entity. The only type of state allowed for the application service layer are transaction state.

Rule 4

They handle injection of repositories into domain services that need them to function.

Rule 5

Application services do not contain any business logic.

Rule 6

Application services typically do not return anything with the exception of query services

Domain Layer

Rule 1

Domain entities such as aggregate root or entities do not know anything about storage and do not work directly with repositories even if they are injected as parameters to the method of an entity.

Rule 2

Aggregate roots and entities are not allowed to exit the Interface or Infrastructure layer.

Rule 3

Repositories generally deal with storage such as file or databases but they exists only as interfaces with methods that follows the ubiquitous language. Implementations are done in the infrastructure.

Rule 4

Services in the domain layer exists only if and only if there is a need for operations that does not quite fit into an aggregate root or entity. Never create unnecessary services when domain entity or aggregate root can handle it internally.

Rule 5

Value objects are to be used to return immutable data or represent a state change in the domain.

Infrastructure Layer

Rule 1

The infrastructure layer contains codes related to the following:

  1. Actual implementation of repositories to make use of ORM frameworks such as Hibernate, Entity Framework or call databases directly.
  2. Consumption or utilisation of external or 3rd party APIs, and the mapping and translation of external models to domain models.
  3. Highly technical implementation of services that are required by the domain such as encryption, document processing and image processing.

Rule 2

This is the bottommost layer that can work directly with domain objects.

Conclusion

These rules serve as guidelines for software developers when they need to work with onion architecture and are by no means exhaustive. And they are only effective if the developers themselves are disciplined enough when it comes to applying the rules in their work.

And, more expert developers may have differing opinions or they have additional rules or principles that they discovered to be very helpful during the development and implementation phase. Therefore, if there are corrections to be made, do leave a comment below and I will update the information here. This way, all of us get to benefit and improve the general quality of software.

Software Architectures – Microservices

In the world of software development, there are a lot of software architecture patterns that emerged as a result of expert developers figuring out the best way to solve certain problems in their line of work.

In this multipart series on Introduction to Software Architecture Patterns, we will be looking at some of the common patterns such as:

  1. Event-driven
  2. Hexagonal
  3. Multitier
  4. Peer-to-peer
  5. Service-oriented
  6. Broker patterns
  7. Microservices
  8. Monolithic
  9. Serverless

For the first article, we will be looking at microservices in detail and describing what it is, what it is not, the pros and cons of it, and when to use it.

What is a microservice and what it is not

Microservice is now one of the most hyped software architecture patterns in the tech industry. Maybe you read about it from some tech news or articles. Or maybe you heard about it from your colleagues who happen to read about it. Maybe your boss ask you to design the company’s next software project as microservices and you are scratching your head.

So what is it really?

In its simplest form, it is a variant of the service-oriented architecture style that arrange and structure a software system as a collection of loosely coupled services.

It can also be said that a microservice takes the single responsibility principle coined by Robert C. Martin to the next level by applying it to the loosely coupled services which can be developed, deployed and maintained independently. Each service is built specifically to work on a discrete task and can communicate with the other services through simple API to solve complex problems.

From the description above, microservice seems simple enough to understand. Yet there is no universal definition on what it is. Different industry experts have a differing opinion on it, but as time went by, they came to a consensus on what are some of the defining characteristics of a microservice.

  1. Services in a microservice architecture are processes that communicate over network using technology-agnostic protocols such as HTTP.
  2. Services are independently deployable
  3. Services are organised around business capabilities
  4. Services can be implemented using different programming languages, databases, hardware and software environment
  5. Services are small in size, built with messaging enabled, bounded by context, autonomously developed, decentralised, built and released with automated processes.

Now that we know what is a microservice, we will take a look at some of the advantages of using a microservice.

Advantages

The microservice architecture comes with several advantages that make it a better option for developing applications but only if it is done correctly and properly.

Resilient to partial service failure

The biggest advantage microservice architecture can offer when compared to the other architectures is that any given service for an application should be able to continue operating even if some others goes down due to software bug or crashes. This is because each service are designed and built to be mostly self-contained and autonomous.

Highly maintainable and testable

The modular nature of a microservice architecture meant that each service is small and specialised. A service can then be easily replaced, changed or updated without affecting the rest of the application.

Furthermore, the developer responsible for the service can also easily test it. They would not have to deal with a large and unwieldy application that would inadvertently happen as the application expands in its capabilities or go in depth into the other areas of the application to understand the business just to test their changes.

Loosely coupled

One of the biggest problem with traditional monolithic architecture is that the application could suffer from a vendor lock-in due to the type or kind of technology used. This creates problem when the vendor goes out of business or the pricing for continual use of the vendor’s product rises beyond what makes sense for the application. Attempts to change the application to use alternative technology or platform can be very costly.

With a microservice, services are loosely coupled from each other. Each service can choose to use different technology or run on different platforms. In the event that a particular service needs to change its technology stack, the team which is responsible for it can do so without affecting the others. Furthermore, the loosely coupled nature also meant that any source code changes would be kept within each service and do not propagate to other services.

Independently deployable

Since services in a microservice are loosely coupled and each could come with their own storage mechanism, there is no real need for a service to wait for the others to go offline or go online before it could be deployed.

Organised around business capabilities

When done properly during the initial phase, each microservice would be designed, developed and deployed to solve problems in a specific business domain (eg. customer relationship management, sales order management, invoicing). Developers can be hired and organised such that they could focus on solving problems in that business domain, creating products instead of projects and writing glue codes. This could translate to having developers who are responsible for a specific or a few service(s) and they develop expertise in that area of the business, leading to faster turn around of new features. The final product or service could also be reused in a different process, other contexts or channels, which would lead to cost savings for the organisation since there is no need to green-lit new projects and hire new developers.

Owned by a small team

By breaking down an application into smaller but autonomous services, teams can become smaller and more efficient. There is no need to have dozens of software developers, managers and support staff to run a service. With the service smaller than a monolithic application, its startup time in the production environment will also be faster and so will the development environment the developers used. With that, they can be more productive and focus on delivery of bug fixes, features and improvements.

Furthermore, they would be responsible for understanding the requirements, developing the necessary features, testing, deployment and support. This will give them more ownership and allow them to see how their work affect the users and the business.

Next up, we will take a look at the disadvantages of using microservices.

Disadvantages

Increased Complexity

For microservices to work together to solve a larger or more complex problem, they communicate with each via API calls over the network. This alone is complex enough to manage and implement. In addition, the e-commerce or finance domain typically feature workload that require transactional processing. Such workloads can be difficult to implement with microservices since APIs by default are stateless. To ensure the data integrity and consistency across the different system, additional workarounds or software components would have to be introduced.

Furthermore, different services would have different demand for their runtime environments. Different servers may have to be introduced, configured and maintained, which ultimately adds more points of failure into the whole system.

Lastly, there is also the complexity in terms of collaboration amongst the different stakeholders. Ideally, different parts of the microservices will have their respective owners. These developers would have to coordinate amongst themselves on the best approach to solve a problem, deploy and maintain the system. And when there is a miscommunication, the parts or maybe the whole of the application may not work properly, resulting in loss in productivity and cost.

Requires cultural changes

Traditional application built using the monolithic approach have a singular codebase that developers work on together. Organisations tend to split the project team based on their technical specialties (eg. Infrastructure, networking, UI/UX, backend). This can create a situation where the development team has no access to the production environment and lead to a disconnect between the developers and the customers.

On the other hand, microservices generally require the developers to have ownership of the full lifecycle of what they build. Organisations would need to think about and implement major changes in terms of the following:

  1. The size and the responsibilities of the team.
  2. The development and quality assurance process.
  3. The structure of the software and how it aligns to the business capabilities so that it can be broken down into small and autonomous services connected via APIs.
  4. How to deploy and maintain the services.

Depending on the organisation, this restructuring and cultural shift may impact the business operations and may cause productivity to drop as the people shift into a different mode of operating. Some employees may choose to leave if they are unable to adapt to such a change or unwilling to be responsible for the full lifecycle of an application.

More expensive

When it comes to microservices, it is more expensive to run and maintain them over time because of the number of moving parts. In the event of a major software fault that brings down multiple services, multiple teams of developers/engineers would have to be involved to resolve issues, which translate to hundreds if not thousands of man-hours.

Furthermore, due to the complexity of microservices, it may take a longer time to restore the services back to operation when compared to the traditional monolithic applications. This could translate in monetary losses if the services are part of a on-demand product offered by a company or a realtime processing system.

More resources could also be required if there is a need to scale up the services by adding more virtual machine instances or servers in realtime to handle huge workload or sudden spikes in activities.

Pose security challenges

An application is split into multiple independent running services in a microservice architecture. In order for the different services to coordinate their action to support the business operation, they communicate with each other via APIs that are independent of the machine architecture and programming languages. This creates a large surface area for cybercriminals to disrupt the services and bring down a few services. This means that the security team will need to be hyper-vigilant about any possible interruption.

Organisations, in order to maintain their competitiveness and growth, would prefer to convert existing monolithic application into microservices. Different programming languages and frameworks may be used by different teams to build the services. As no programming languages or frameworks are completely safe and without vulnerability, each new introduction of language or framework to build new services and adding these services into the existing mix of microservices can increase instabilities and the number of security loopholes that can be exploited.

Furthermore, due to the distributed nature of microservices, traditional logs are not as effective when it comes to tracing what is going on with the application. In addition, there will also be more logs generated in a concurrent manner. This means there is a need to consolidate all the logs and correlate the events to generate a good picture of what is happening. Otherwise, there is a high probability that issues will be masked or covered up and prevent effective mitigation.

With this, we now know what is a microservice, the advantages and disadvantages. Next up, we will see what is not a microservice.

What is not a microservice

After reading through the advantages and disadvantages, maybe you came to the conclusion to microservices is easy to do and you feel confident about it. Maybe you think that you could just ask your software engineers to make individual function as a microservice for other parts of the software to use.

But that would be a huge mistake. The benefits of microservices will immediately be overwhelmed by the runtime overhead and operational complexity. Your project will suffer from over-engineering and timeline slip.

Justin Etheredge wrote an article called You’re not actually building microservices where he talks about the possible symptoms of a software system that is not built using microservices.

  1. Any changes to one microservice often require changes to others.
  2. Deploying one microservice requires other microservices to be deployed at the same time
  3. Excessive communication between microservices
  4. Sharing of the same datastore by multiple microservices
  5. Microservices share a same code or models

However, even if an application shows one of the above symptoms, it could still remain as a microservice because there will always be exceptional cases. But alarm bells should go off if a service or application displays more than one of the above symptoms. That could mean it is not a true microservice.

So when do you use it?

Even though microservice architecture is the trendy thing to do now, it is generally advisable not to do it for a new software project or a proof of concept due to the complexity, cost and cultural changes needed. It may even slow down the development process.

But that is not to say to we should stay away from microservices. Rather, the only time to consider converting an application into using microservice architecture is when the following criteria are met:

  1. The software has grown too big to be managed as a monolith as described by Martin Fowler in his article.
  2. The organisation has the resources in place and is ready to go onboard with it.
  3. The software needs to adapt quickly to market needs.
  4. Parts of the application needs to be extremely efficient.

Jake Lumetta wrote an article titled Monolith vs microservices: which architecture is right for your team? that reinforced the above points 2-4.

In the same article, Jake also list down the scenarios that are not suitable for microservices:

  1. Your team is at founding stage and has only a few members.
  2. You are building an unproven product or proof of concept.
  3. You have no microservice experience.