At APNIC we’ve recently ported our Autonomous System Number (ASN) delegation service from Perl to Java, as part of an overall shift in technology platform. We designed the replacement service using onion architecture, an inversion of the usual n-tier layered architecture.
In this post I will explore some of the consequences of our choice to use this form of architecture.
Onion architecture is part of a set of architectures that invert dependencies at an architectural level. Dependency inversion is a software design principle that decreases coupling and increases the locality of code by requiring lower level modules to conform to abstractions defined in higher level modules. Applying this principle to the n-tier layered architecture means inverting the flow of dependency to make the business logic layers independent of interfaces and infrastructure.
This principle leads to a model where the business logic is in the centre of the architecture, with additional layers placed around it in concentric rings, like an onion.
All dependencies flow inwards only.
This is the golden rule of these architectures. Choices that could potentially be hard to reverse, such as which database technology to use, are moved to the outer layer where nothing depends on them, making those choices easier to revisit. The most important code goes in the middle, and depends only on very stable APIs such as the host language and one or two well tested and trusted libraries. The middle is where business rules are applied, and nothing else happens.
The business rules for delegating ASNs are straightforward and easy to understand:
- An ASN can be delegated if it is currently in an APNIC resource pool, but not allocated already.
This particular body of code doesn’t perform policy validation, as that’s the role of the APNIC Resource Services team, so it can assume that if it’s invoked with a request to delegate an ASN, policy has been met.
This rule depends on the context of previous delegations, returns and transfers. It doesn’t depend on email, or databases, or networks or web servers. The aim of the onion architecture is to keep it that way. The Pool object contains a list of Delegation objects, and that’s all it needs to perform its job.
When a Pool object is tasked with making a delegation, it checks its rules and throws an exception if anything isn’t right, then it returns a DelegationEvent object describing the delegation it just made. Until this DelegationEvent has been persisted, the delegation won’t form part of the context of future delegation decisions.
Persistence is handled by a module in an outer layer of the onion, one which is tasked with taking a request to perform a delegation, loading the context of previous actions into a Pool, instructing the Pool to perform the delegation, and persisting the result. However, this module doesn’t depend on a database implementation. Infrastructure needs such as databases are always in the outermost layer of the onion, and so the module defines a Repository abstraction, which can load a Pool and persist events.
The current implementation persists events by translating them into changes on a set of relational tables. This implementation aids transition from the old platform to the new by allowing all existing consumers of the information to carry on unaffected by this particular change.
A moderate success story
While this architecture will be used again here at APNIC, it’s worth noting that this is a small project in terms of code size, and that no one architecture suits all contexts.
But here, for this project, the onion architecture has freed the business rules from concerns of database and interface and enabled a transitional approach to switching technology platform. Coupling is decreased, cohesion is increased and developers are happy.
The views expressed by the authors of this blog are their own and do not necessarily reflect the views of APNIC. Please note a Code of Conduct applies to this blog.