Anti-Corruption Layer : Transforming Legacy Applications into Modern Cloud Native Applications

Anti-Corruption Layer : Transforming Legacy Applications into Modern Cloud Native Applications
Photo by Tod S / Unsplash

As part of my team's modernization we decided to review all aspects of our application to make our features and processes streamlined which included the data and the data schemas. We knew we had some outdated schemas that needed to be reorganized. We also decided not to migrate over all the existing data as some of it may not be necessary for our modern application. As part of this process, we decided to use an ACL.

What is an ACL?

It is not an Access Control List. An Anti-Corruption Layer (ACL) is a set of patterns placed between the domain model and other bounded contexts or third party dependencies. The intent of this layer is to prevent the intrusion of foreign concepts and models into the domain model. This ACL layer is typically made up of several well-known design patterns such as Facade, Adapter and Translator. The patterns are used to map between foreign domain models and APIs to types and interfaces that are part of the domain model.

Facade

A facade is a simple interface to a complex system. The facade owns retrieving the legacy data in the legacy data's format. A facade isolates the complex code from other subsystems.

  • A facade can be accessed through raw SQL, API calls, etc.
  • A facade should only own 1 connection, so having multiple facades should be expected when data comes from multiple sources
  • The data is expected to be turned into software data at this point
    • XML -> Java data object
  • A facade may cut own unneeded data from reaching deeper into the ACL
  • A facade may not necessarily implement all services that are available.

Without using an ACL facade, modifying existing integrations or services becomes complex. The services are tightly coupled and modernizing the application becomes difficult.

Without a facade modifying application services becomes complex affecting multiple consumers

Implementing a facade in the ACL loosely couples existing systems together which reduces the complexity of modernizing the existing legacy systems.

With a facade implementing or accessing services becomes more simple. Consumers calls the facade and not directly the services in the complex system

Read this article which better explains a facade.

Adapter

An adapter is the communication between two disparate systems which provides an interface that could be made useful. The adapter owns the process of taking the data in the legacy format and getting to a resulting new data format which could mean calling a translator if the data structures are different. If the data structures are the same or similar a translator might not be necessary.

  • In a two way ACL, it might also own the process of returning to the original format or be separated into two paths
  • Each new data type should only have 1 adapter since it functions mostly as an orchestrator, multiple orchestrators increases complexity with low value add.
  • The adapter process can vary in complexity from simple 1:1 mapping with a new object() to requiring calculations; the more complex the process, the greater the need for a separate translator.

Read this article on adapters for further explanation.

Translator

The translators owns the complexity of the data transformation. A translator contains the rules and logic to convert the data formats and structures between systems. The simplest version of a translator is an object constructor that accepts the previous object as the input. A complex translator may need deep domain knowledge from the legacy system in order to compute a field.

  • The new system wants MPH(Miles Per Hour) or KPH (Kilometers Per Hour) but the legacy system only has distance and time.

A translator will also be responsible for changing the depth data appears, as it is the piece that will know where the data is in the legacy model and where it belongs in the new model.

  • Mapping is one technology used here, and some libraries exist for "Automapping" between two formats
    • some of these are also reversible, depending on the types of data and if calculations are involved
Translators are responsible for converting data structures into the required data structure

Use Cases

Here are some example use cases for using an ACL for an application. The advantage of implementing an ACL between multiple systems eliminates the need to customize the legacy application, the third party systems or SaaS solutions.

  • Legacy application is unable to be modified however the data that resides on the legacy needs to be migrated to the modernized application
  • Sending data from third party systems to a legacy application. Most applications have integrations from third party systems. You can use an ACL instead of flat file integrations.
  • Two systems that have different schemas that share data. For example invoices from one system that is integrated into a Point Of Sale (POS) application
  • Migrating data from old legacy to a modernized application
Example ACL Pattern

In the image above, the outlined green box is an example of one ACL application that has the three patterns of a facade, adaptor and translator that can pull data from multiple sources.

Considerations

  • The ACL should not be long lived wherever possible. In our use case, we needed to modernize the data structures so we wanted to remove the legacy database as soon as possible. However there could be use cases to keep an ACL if you are receiving legacy data from other systems. There is a tendency for the engineers or the business to not prioritize removing the ACL after it is in place and it works well. You must weigh the benefits versus the risks of keeping an ACL.
  • Keep complex business code out of the ACL.
  • Keep the ACL as simple as possible to translate existing data into the receiving data schema.
  • There is an operational overhead on utilizing an ACL. Implement monitoring and alerting tools where possible.
  • An ACL can become a single point of failure if all of your legacy data flows through the ACL.
  • It is not recommended to use one ACL to migrate over all of your data. For example, our team will be using a new ACL for migrating transactional data versus including it in the existing ACL. This allows us to keep each ACL as simple as possible. Consider eliminating the existing ACL before you start migrating additional data to your new modern application to reduce complexity.
  • There could be an increase in latency for utilizing an ACL.
  • It is costly to build and maintain an ACL for the life of the application. The business must weigh the advantages and disadvantages of building an ACL.

Our Approach

Our existing legacy application has a relational database plus we had many flags and fields that were not being utilized. We decided to use an ACL to migrate over existing data without any extra fields that are currently unnecessary. Our existing schema currently has nine associated tables joined by foreign keys.

Our modern application will be deployed to Kubernetes and utilizing a JSONB column to store the majority of these nine records into one record in PostgreSQL database. This should allow us to access the data with one query versus the nine joined tables in our legacy database.

In Kubernetes, we will be able to utilize the scaling features to scale our application vertically or horizontally when needed as well as have more robust monitoring tools such as Grafana and Splunk. In our context, we are using Grafana for monitoring metrics such as performance of the application whereas we use Splunk for logging and some alerting functionality.

Legacy schema with nine associated tables

Example Adapter Code

Example Translator Code

This snippet shows us taking the output from the legacy application and building one line of data for our modern application

Modernized schema

Our modernized schema is one table with a large majority of the data in the JSONB field.

Example of schema with JSONB object

Architecture

Here is an example of our modernized architecture. Since we are using the Strangler Fig Pattern to migrate features over one at a time rather than using the traditional Agile Waterfall method, we are able to provide modern features to our customer sooner without affecting the legacy application.

The Strangler Pattern allows us to migrate over incremental functionality into a new modern platform which allows our team to get faster feedback from the user community. We are releasing the new functionality one small slice at a time instead of a big bang migration.

Architecture utilizing an ACL with existing legacy application

Lessons Learned

In our approach to use the Strangler Fig Pattern to migrate over features to our new microservices oriented application, we used an ACL for this Cam Service feature so that the user can view the data and edit the data on the existing legacy database in the new modernized front end.

Our plan was to migrate from the ACL pattern to microservices while we were working on the create Cam functionality which allows users to create new data via the modernized application. The ACL pattern would allow users to update existing data in the legacy database while keeping a new copy in the modern data schema. Once we migrated over all the data to the new schema, we could turn off the ACL. Sounds simple, right? Not exactly...

Most of the code that was written to pull data into the ACL included other static data tables such as tires makes, series and sizes including the existing Cam Product data that we were migrating to the new Cam Service feature. The ACL has rather complex code to merge the nine tables of data into the one record. The decision to put this complex code into the ACL versus a separate microservice could prove to be detrimental to our modern application and our timelines when we remove the ACL. We must carefully remove all the complex code from the ACL to a new microservice without affecting our customers. As of today, we still have the ACL in place but expect to remove it in the coming months.

Another lesson learned was about deciding when to own the data structures in the new schema. This was a rather complex decision.

  • Do we experiment and see if we need to own the data when editing?
  • Or when creating new data?
  • Maybe we keep the ACL and migrate over data as it is edited or created?
  • Or have a big bang migration to the new schema?

We decided to experiment and attempt to own the data at creation of new records. This decision has increased the time for our modernized application to own the data. Our ACL is now much more complex than the original solution. The time to remove this complexity will increase our timelines and ability to migrate over all the existing data to the new format. This was our first experience with an ACL and we learned that owning the data sooner might be a better decision to reduce complexity in our migration plans. In the coming few months, we will find out if owning the data at read or editing is truly a better decision.

We are continually working toward completing our feature. I'm sure we will continue to learn more about implementing an ACL as we continue to strangle the existing features into our new modern application.

Below is a list of further reading about an ACL that I referenced in the explanation in my article.

References for Further Reading

Anti-corruption layer pattern - AWS Prescriptive Guidance
Modernization pattern that acts as an adapter that translates semantics from a monolith to a microservice.
Adapter
Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate.
Facade
Facade is a structural design pattern that provides a simplified interface to a library, a framework, or any other complex set of classes.
The Anti-Corruption Layer Pattern
Isolating your model from legacy systems using an Anti-Corruption Layer.
Anti-corruption Layer pattern - Azure Architecture Center
Examine the Anti-corruption Layer pattern. Implement a fa├žade or adapter layer between a modern application and a legacy system.