Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.
What does an anti-corruption layer solve? Most systems rely on data or behaviors from another service or an external 3rd party. The problem is they often don’t share the same semantics or data structures. Left unchecked this leads to convoluting up your own boundary with concepts from another boundary. Let me explain how you can use an anti-corruption layer as a way to translate the concepts from another boundary in isolation.
Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.
A Service is the owner of a set of business capabilities and the data behind those capabilities. Service boundaries are defined by those business capabilities that are highly cohesive. There is generally a specific language and terminology used within a service boundary that also defines it.
When you integrate with other service boundaries or external 3rd party services, they may not share the exact same semantics. The terminology and data structures may not align with yours. This can lead to leaking other service boundaries concepts into your own.
In some situations, in my experience, generally, boundaries that are in more supporting or generic roles, may share the same semantics or at least are very close. An example of this is accounting. Let’s say you have a service boundary for financials for receivables and payables.
Since you don’t have full-blown double-entry accounting in your service, you integrate with a 3rd party external accounting system. In this case, since both your service and the external system are likely aligned in data structures and language, there really isn’t any required translation.
However, in other situations, they may not line up at all. And this is when you can run into trouble. As an example, let’s say we have a system for a food delivery service. One of the services is for handling all the incoming orders.
Now we may integrate with other external 3rd party systems that have an entirely different way of structuring order data as well as the terminology they use to represent what we have in our Ordering service.
To prevent any external concepts from leaking into our service boundary, we can place an anti-corruption layer at the very edge of our logical service boundary. This is about a logical boundary, although it can be a physical boundary. The anti-corruption layer can be physically deployed within our primary service process, or it could also be its own physical process that then communicates to our primary service process. Regardless, this is about logically, not physically.
An external service will interact with an anti-corruption layer so we can translate from their semantics to ours. This means that if you have many different integrations, you may also have more than just one anti-corruption layer. Each anti-corruption layer will be responsible for handling the transaction for each specific 3rd party.
To illustrate this, let’s say we have to make an HTTP call to the external 3rd party service to pull new orders that we need to import into our system.
In this example, we’re creating a PlaceOrder which is a command that will be sent to a queue for processing. The PlaceOrder command is how we place an order within our service boundary. This is the only way to place an order. This means that when we get data from an external service, we need to do some translation to build a PlaceOrder command.
Event Driven Architecture
If you’re using an event driven architecture, this translation comes in the form of translating an event into a command.
When you consume an event that was published by another service boundary, you want to translate that into something meaningful to perform some type of action within your own service boundary. To do that means translating the event into a command.
In the example above, when an OrderRefunded event is published by the Billing service boundary and is then consumed by the Ordering service boundary, it needs to cancel the order. To do that it does the proper translation to build the appropriate command.
Developer-level members of my YouTube channel or Patreon get access to the full source for any working demo application that I post on my blog or YouTube. Check out the YouTube Membership or Patreon for more info.
An anti-corruption layer is a great way to isolate the logic of translation from external sources into the semantics defined within your own logical service boundary. It’s all about translation and keeping external concepts out and not leaking in and pushing this translation to the outer edge.
The translation isn’t about creating records to interact with your database, it’s simply translation so that you can perform work within your boundary as you understand it.
As with everything, there are tradeoffs. The downsides are you can be adding latency, especially in my example because I’m enqueuing a command instead of immediately creating an order. While not necessarily a downside, you are adding indirection, which does have a cost. Lastly, your anti-corruption layer can get pretty large if you’re interacting with a large external system or many external systems. If you are, don’t be shocked if over time the anti-corruption layer becomes larger (in code size).