mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-10-14 12:16:46 +00:00
feat: anti corruption layer - Issue 1325. (#2777)
* add init impl * add docs * add test * correct tests and specs * add check style corrections * add init impl * add docs * add test * correct tests and specs * add check style corrections * issue_1325: revert accidental damage to the other repos * issue_1325: sort the comments --------- Co-authored-by: Boris Zhguchev <boris.zhguchev@gropyus.com>
This commit is contained in:
1 parent
7034ca5e95
commit
5c47447d9b
19 files changed
+677
No files matched your search
@@ -0,0 +1,160 @@
|
|||||||
|
---
|
||||||
|
title: Anti-corruption layer
|
||||||
|
category: Architectural
|
||||||
|
language: en
|
||||||
|
tag:
|
||||||
|
- Cloud distributed
|
||||||
|
- Decoupling
|
||||||
|
- Microservices
|
||||||
|
---
|
||||||
|
|
||||||
|
## Intent
|
||||||
|
|
||||||
|
Implement a façade or adapter layer between different subsystems that don't share the same semantics.
|
||||||
|
This layer translates requests that one subsystem makes to the other subsystem.
|
||||||
|
Use this pattern to ensure that an application's design is not limited by dependencies on outside subsystems.
|
||||||
|
This pattern was first described by Eric Evans in Domain-Driven Design.
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
|
||||||
|
### Context and problem
|
||||||
|
Most applications rely on other systems for some data or functionality.
|
||||||
|
For example, when a legacy application is migrated to a modern system,
|
||||||
|
it may still need existing legacy resources.
|
||||||
|
New features must be able to call the legacy system.
|
||||||
|
This is especially true of gradual migrations,
|
||||||
|
where different features of a larger application are moved to a modern system over time.
|
||||||
|
|
||||||
|
Often these legacy systems suffer from quality issues such as convoluted data schemas or obsolete APIs.
|
||||||
|
The features and technologies used in legacy systems can vary widely from more modern systems.
|
||||||
|
To interoperate with the legacy system, the new application may need to support outdated infrastructure, protocols, data models, APIs,
|
||||||
|
or other features that you wouldn't otherwise put into a modern application.
|
||||||
|
|
||||||
|
Maintaining access between new and legacy systems can force the new system to adhere to at least some of the legacy system's APIs or other semantics.
|
||||||
|
When these legacy features have quality issues, supporting them "corrupts" what might otherwise be a cleanly designed modern application.
|
||||||
|
Similar issues can arise with any external system that your development team doesn't control, not just legacy systems.
|
||||||
|
|
||||||
|
### Solution
|
||||||
|
Isolate the different subsystems by placing an anti-corruption layer between them.
|
||||||
|
This layer translates communications between the two systems, allowing one system to remain unchanged
|
||||||
|
while the other can avoid compromising its design and technological approach.
|
||||||
|
|
||||||
|
### Programmatic example
|
||||||
|
#### Introduction
|
||||||
|
The example shows why the anti-corruption layer is needed.
|
||||||
|
Here are 2 shop-ordering systems: `Legacy` and `Modern`. \
|
||||||
|
(
|
||||||
|
*It is important to state the separation is very conditional, and is drawn for learning purposes*.
|
||||||
|
*In reality the pattern does not depend on the so-called ageness but rather relies on the different domain models.*)
|
||||||
|
|
||||||
|
The aforementioned systems have different domain models and have to operate simultaneously.
|
||||||
|
Since they work independently the orders can come either from the `Legacy` or `Modern` system.
|
||||||
|
Therefore, the system that receives the legacyOrder needs to check if the legacyOrder is valid and not present in the other system.
|
||||||
|
Then it can place the legacyOrder in its own system.
|
||||||
|
|
||||||
|
But for that, the system needs to know the domain model of the other system and to avoid that,
|
||||||
|
the anti-corruption layer(ACL) is introduced.
|
||||||
|
The ACL is a layer that translates the domain model of the `Legacy` system to the domain model of the `Modern` system and vice versa.
|
||||||
|
Also, it hides all other operations with the other system, uncoupling the systems.
|
||||||
|
|
||||||
|
#### Domain model of the `Legacy` system
|
||||||
|
```java
|
||||||
|
public class LegacyOrder {
|
||||||
|
private String id;
|
||||||
|
private String customer;
|
||||||
|
private String item;
|
||||||
|
private String qty;
|
||||||
|
private String price;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### Domain model of the `Modern` system
|
||||||
|
```java
|
||||||
|
public class ModernOrder {
|
||||||
|
private String id;
|
||||||
|
private Customer customer;
|
||||||
|
|
||||||
|
private Shipment shipment;
|
||||||
|
|
||||||
|
private String extra;
|
||||||
|
}
|
||||||
|
public class Customer {
|
||||||
|
private String address;
|
||||||
|
}
|
||||||
|
public class Shipment {
|
||||||
|
private String item;
|
||||||
|
private String qty;
|
||||||
|
private String price;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### Anti-corruption layer
|
||||||
|
```java
|
||||||
|
public class AntiCorruptionLayer {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ModernShop modernShop;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private LegacyShop legacyShop;
|
||||||
|
|
||||||
|
public Optional<LegacyOrder> findOrderInModernSystem(String id) {
|
||||||
|
return modernShop.findOrder(id).map(o -> /* map to legacyOrder*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<ModernOrder> findOrderInLegacySystem(String id) {
|
||||||
|
return legacyShop.findOrder(id).map(o -> /* map to modernOrder*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### The connection
|
||||||
|
Wherever the `Legacy` or `Modern` system needs to communicate with the counterpart
|
||||||
|
the ACL needs to be used to avoid corrupting the current domain model.
|
||||||
|
The example below shows how the `Legacy` system places an order with a validation from the `Modern` system.
|
||||||
|
```java
|
||||||
|
public class LegacyShop {
|
||||||
|
@Autowired
|
||||||
|
private AntiCorruptionLayer acl;
|
||||||
|
|
||||||
|
public void placeOrder(LegacyOrder legacyOrder) throws ShopException {
|
||||||
|
|
||||||
|
String id = legacyOrder.getId();
|
||||||
|
|
||||||
|
Optional<LegacyOrder> orderInModernSystem = acl.findOrderInModernSystem(id);
|
||||||
|
|
||||||
|
if (orderInModernSystem.isPresent()) {
|
||||||
|
// order is already in the modern system
|
||||||
|
} else {
|
||||||
|
// place order in the current system
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issues and considerations
|
||||||
|
- The anti-corruption layer may add latency to calls made between the two systems.
|
||||||
|
- The anti-corruption layer adds an additional service that must be managed and maintained.
|
||||||
|
- Consider how your anti-corruption layer will scale.
|
||||||
|
- Consider whether you need more than one anti-corruption layer. You may want to decompose functionality into multiple services using different technologies or languages, or there may be other reasons to partition the anti-corruption layer.
|
||||||
|
- Consider how the anti-corruption layer will be managed in relation with your other applications or services. How will it be integrated into your monitoring, release, and configuration processes?
|
||||||
|
- Make sure transaction and data consistency are maintained and can be monitored.
|
||||||
|
- Consider whether the anti-corruption layer needs to handle all communication between different subsystems, or just a subset of features.
|
||||||
|
- If the anti-corruption layer is part of an application migration strategy, consider whether it will be permanent, or will be retired after all legacy functionality has been migrated.
|
||||||
|
- This pattern is illustrated with distinct subsystems above, but can apply to other service architectures as well, such as when integrating legacy code together in a monolithic architecture.
|
||||||
|
|
||||||
|
## Applicability
|
||||||
|
|
||||||
|
Use this pattern when:
|
||||||
|
|
||||||
|
- A migration is planned to happen over multiple stages, but integration between new and legacy systems needs to be maintained.
|
||||||
|
- Two or more subsystems have different semantics, but still need to communicate.
|
||||||
|
|
||||||
|
This pattern may not be suitable if there are no significant semantic differences between new and legacy systems.
|
||||||
|
|
||||||
|
## Tutorials
|
||||||
|
|
||||||
|
* [Microsoft - Anti-Corruption Layer](https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer)
|
||||||
|
* [Amazon - Anti-Corruption Layer](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/acl.html)
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
* [Domain-Driven Design. Eric Evans](https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215)
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
@startuml
|
||||||
|
package com.iluwatar.corruption {
|
||||||
|
class LegacyShop {
|
||||||
|
private Store store;
|
||||||
|
private AntiCorruptionLayer acl;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModernShop {
|
||||||
|
private Store store;
|
||||||
|
private AntiCorruptionLayer acl;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AntiCorruptionLayer{
|
||||||
|
private LegacyShop legacyShop;
|
||||||
|
private ModernShop modernShop;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
LegacyShop ---> "findOrderInModernSystem" AntiCorruptionLayer
|
||||||
|
ModernShop ---> "findOrderInLegacySystem" AntiCorruptionLayer
|
||||||
|
AntiCorruptionLayer ..|> ModernShop
|
||||||
|
AntiCorruptionLayer ..|> LegacyShop
|
||||||
|
}
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
|
||||||
|
The MIT License
|
||||||
|
Copyright © 2014-2022 Ilkka Seppälä
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>java-design-patterns</artifactId>
|
||||||
|
<groupId>com.iluwatar</groupId>
|
||||||
|
<version>1.26.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>anti-corruption-layer</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>com.iluwatar.corruption.App</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2023 Ilkka Seppälä
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.iluwatar.corruption;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This layer translates communications between the two systems,
|
||||||
|
* allowing one system to remain unchanged while the other can avoid compromising
|
||||||
|
* its design and technological approach.
|
||||||
|
*/
|
||||||
|
@SpringBootApplication
|
||||||
|
public class App {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(App.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Context and problem
|
||||||
|
* Most applications rely on other systems for some data or functionality.
|
||||||
|
* For example, when a legacy application is migrated to a modern system,
|
||||||
|
* it may still need existing legacy resources. New features must be able to call the legacy system.
|
||||||
|
* This is especially true of gradual migrations,
|
||||||
|
* where different features of a larger application are moved to a modern system over time.
|
||||||
|
*
|
||||||
|
* <p>Often these legacy systems suffer from quality issues such as convoluted data schemas
|
||||||
|
* or obsolete APIs.
|
||||||
|
* The features and technologies used in legacy systems can vary widely from more modern systems.
|
||||||
|
* To interoperate with the legacy system,
|
||||||
|
* the new application may need to support outdated infrastructure, protocols, data models, APIs,
|
||||||
|
* or other features that you wouldn't otherwise put into a modern application.
|
||||||
|
*
|
||||||
|
* <p>Maintaining access between new and legacy systems can force the new system to adhere to
|
||||||
|
* at least some of the legacy system's APIs or other semantics.
|
||||||
|
* When these legacy features have quality issues, supporting them "corrupts" what might
|
||||||
|
* otherwise be a cleanly designed modern application.
|
||||||
|
* Similar issues can arise with any external system that your development team doesn't control,
|
||||||
|
* not just legacy systems.
|
||||||
|
*
|
||||||
|
* <p>Solution Isolate the different subsystems by placing an anti-corruption layer between them.
|
||||||
|
* This layer translates communications between the two systems,
|
||||||
|
* allowing one system to remain unchanged while the other can avoid compromising
|
||||||
|
* its design and technological approach.
|
||||||
|
*/
|
||||||
|
package com.iluwatar.corruption;
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
package com.iluwatar.corruption.system;
|
||||||
|
|
||||||
|
import com.iluwatar.corruption.system.legacy.LegacyShop;
|
||||||
|
import com.iluwatar.corruption.system.modern.Customer;
|
||||||
|
import com.iluwatar.corruption.system.modern.ModernOrder;
|
||||||
|
import com.iluwatar.corruption.system.modern.Shipment;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class represents an anti-corruption layer.
|
||||||
|
* The main purpose of the class is to provide a layer between the modern and legacy systems.
|
||||||
|
* The class is responsible for converting the data from one system to another
|
||||||
|
* decoupling the systems to each other
|
||||||
|
*
|
||||||
|
* <p>It allows using one system a domain model of the other system
|
||||||
|
* without changing the domain model of the system.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class AntiCorruptionLayer {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private LegacyShop legacyShop;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method converts the order from the legacy system to the modern system.
|
||||||
|
* @param id the id of the order
|
||||||
|
* @return the order in the modern system
|
||||||
|
*/
|
||||||
|
public Optional<ModernOrder> findOrderInLegacySystem(String id) {
|
||||||
|
|
||||||
|
return legacyShop.findOrder(id).map(o ->
|
||||||
|
new ModernOrder(
|
||||||
|
o.getId(),
|
||||||
|
new Customer(o.getCustomer()),
|
||||||
|
new Shipment(o.getItem(), o.getQty(), o.getPrice()),
|
||||||
|
""
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.iluwatar.corruption.system;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class represents a data store for the modern system.
|
||||||
|
* @param <V> the type of the value stored in the data store
|
||||||
|
*/
|
||||||
|
public abstract class DataStore<V> {
|
||||||
|
private final HashMap<String, V> inner;
|
||||||
|
|
||||||
|
public DataStore() {
|
||||||
|
inner = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<V> get(String key) {
|
||||||
|
return Optional.ofNullable(inner.get(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<V> put(String key, V value) {
|
||||||
|
return Optional.ofNullable(inner.put(key, value));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.iluwatar.corruption.system;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class represents an general exception for the shop.
|
||||||
|
*/
|
||||||
|
public class ShopException extends Exception {
|
||||||
|
public ShopException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws an exception that the order is already placed but has an incorrect data.
|
||||||
|
*
|
||||||
|
* @param lhs the incoming order
|
||||||
|
* @param rhs the existing order
|
||||||
|
* @return the exception
|
||||||
|
* @throws ShopException the exception
|
||||||
|
*/
|
||||||
|
public static ShopException throwIncorrectData(String lhs, String rhs) throws ShopException {
|
||||||
|
throw new ShopException("The order is already placed but has an incorrect data:\n"
|
||||||
|
+ "Incoming order: " + lhs + "\n"
|
||||||
|
+ "Existing order: " + rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package com.iluwatar.corruption.system.legacy;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class represents an order in the legacy system.
|
||||||
|
* The class is used by the legacy system to store the data.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class LegacyOrder {
|
||||||
|
private String id;
|
||||||
|
private String customer;
|
||||||
|
|
||||||
|
private String item;
|
||||||
|
private int qty;
|
||||||
|
private int price;
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package com.iluwatar.corruption.system.legacy;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class represents a legacy shop system. The main purpose is to place the order from the
|
||||||
|
* customers.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class LegacyShop {
|
||||||
|
@Autowired private LegacyStore store;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Places the order in the legacy system. If the order is already present in the modern system,
|
||||||
|
* then the order is placed only if the data is the same. If the order is not present in the
|
||||||
|
* modern system, then the order is placed in the legacy system.
|
||||||
|
*/
|
||||||
|
public void placeOrder(LegacyOrder legacyOrder) {
|
||||||
|
store.put(legacyOrder.getId(), legacyOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Finds the order in the legacy system. */
|
||||||
|
public Optional<LegacyOrder> findOrder(String orderId) {
|
||||||
|
return store.get(orderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package com.iluwatar.corruption.system.legacy;
|
||||||
|
|
||||||
|
import com.iluwatar.corruption.system.DataStore;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class represents a data store for the legacy system.
|
||||||
|
* The class is used by the legacy system to store the data.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class LegacyStore extends DataStore<LegacyOrder> {
|
||||||
|
}
|
||||||
|
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package com.iluwatar.corruption.system.modern;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class represents a customer in the modern system.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class Customer {
|
||||||
|
private String address;
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package com.iluwatar.corruption.system.modern;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class represents an order in the modern system.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ModernOrder {
|
||||||
|
private String id;
|
||||||
|
private Customer customer;
|
||||||
|
|
||||||
|
private Shipment shipment;
|
||||||
|
|
||||||
|
private String extra;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package com.iluwatar.corruption.system.modern;
|
||||||
|
|
||||||
|
import com.iluwatar.corruption.system.AntiCorruptionLayer;
|
||||||
|
import com.iluwatar.corruption.system.ShopException;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class represents a modern shop system.
|
||||||
|
* The main purpose of the class is to place orders and find orders.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ModernShop {
|
||||||
|
@Autowired
|
||||||
|
private ModernStore store;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AntiCorruptionLayer acl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Places the order in the modern system.
|
||||||
|
* If the order is already present in the legacy system, then no need to place it again.
|
||||||
|
*/
|
||||||
|
public void placeOrder(ModernOrder order) throws ShopException {
|
||||||
|
|
||||||
|
String id = order.getId();
|
||||||
|
// check if the order is already present in the legacy system
|
||||||
|
Optional<ModernOrder> orderInObsoleteSystem = acl.findOrderInLegacySystem(id);
|
||||||
|
|
||||||
|
if (orderInObsoleteSystem.isPresent()) {
|
||||||
|
var legacyOrder = orderInObsoleteSystem.get();
|
||||||
|
if (!order.equals(legacyOrder)) {
|
||||||
|
throw ShopException.throwIncorrectData(legacyOrder.toString(), order.toString());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
store.put(id, order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the order in the modern system.
|
||||||
|
*/
|
||||||
|
public Optional<ModernOrder> findOrder(String orderId) {
|
||||||
|
return store.get(orderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package com.iluwatar.corruption.system.modern;
|
||||||
|
|
||||||
|
import com.iluwatar.corruption.system.DataStore;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class represents a data store for the modern system.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ModernStore extends DataStore<ModernOrder> {
|
||||||
|
}
|
||||||
|
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package com.iluwatar.corruption.system.modern;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class represents a shipment in the modern system.
|
||||||
|
* The class is used by the modern system to store the data.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class Shipment {
|
||||||
|
private String item;
|
||||||
|
private int qty;
|
||||||
|
private int price;
|
||||||
|
}
|
||||||
Whitespace-only changes.
+87
@@ -0,0 +1,87 @@
|
|||||||
|
package com.iluwatar.corruption.system;
|
||||||
|
|
||||||
|
import com.iluwatar.corruption.system.legacy.LegacyOrder;
|
||||||
|
import com.iluwatar.corruption.system.legacy.LegacyShop;
|
||||||
|
import com.iluwatar.corruption.system.modern.Customer;
|
||||||
|
import com.iluwatar.corruption.system.modern.ModernOrder;
|
||||||
|
import com.iluwatar.corruption.system.modern.ModernShop;
|
||||||
|
import com.iluwatar.corruption.system.modern.Shipment;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
@RunWith(SpringRunner.class)
|
||||||
|
@SpringBootTest
|
||||||
|
public class AntiCorruptionLayerTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private LegacyShop legacyShop;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ModernShop modernShop;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the anti-corruption layer.
|
||||||
|
* Main intention is to demonstrate how the anti-corruption layer works.
|
||||||
|
* <p>
|
||||||
|
* The 2 shops (modern and legacy) should operate independently and in the same time synchronize the data.
|
||||||
|
* To avoid corrupting the domain models of the 2 shops, we use an anti-corruption layer
|
||||||
|
* that transforms one model to another under the hood.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void antiCorruptionLayerTest() throws ShopException {
|
||||||
|
|
||||||
|
// a new order comes to the legacy shop.
|
||||||
|
LegacyOrder legacyOrder = new LegacyOrder("1", "addr1", "item1", 1, 1);
|
||||||
|
// place the order in the legacy shop.
|
||||||
|
legacyShop.placeOrder(legacyOrder);
|
||||||
|
// the order is placed as usual since there is no other orders with the id in the both systems.
|
||||||
|
Optional<LegacyOrder> legacyOrderWithIdOne = legacyShop.findOrder("1");
|
||||||
|
assertEquals(Optional.of(legacyOrder), legacyOrderWithIdOne);
|
||||||
|
|
||||||
|
// a new order (or maybe just the same order) appears in the modern shop.
|
||||||
|
ModernOrder modernOrder = new ModernOrder("1", new Customer("addr1"), new Shipment("item1", 1, 1), "");
|
||||||
|
|
||||||
|
// the system places it, but it checks if there is an order with the same id in the legacy shop.
|
||||||
|
modernShop.placeOrder(modernOrder);
|
||||||
|
|
||||||
|
Optional<ModernOrder> modernOrderWithIdOne = modernShop.findOrder("1");
|
||||||
|
// there is no new order placed since there is already an order with the same id in the legacy shop.
|
||||||
|
assertTrue(modernOrderWithIdOne.isEmpty());
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Test the anti-corruption layer.
|
||||||
|
* Main intention is to demonstrate how the anti-corruption layer works.
|
||||||
|
* <p>
|
||||||
|
* This test tests the anti-corruption layer from the rule the orders should be the same in the both systems.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Test(expected = ShopException.class)
|
||||||
|
public void antiCorruptionLayerWithExTest() throws ShopException {
|
||||||
|
|
||||||
|
// a new order comes to the legacy shop.
|
||||||
|
LegacyOrder legacyOrder = new LegacyOrder("1", "addr1", "item1", 1, 1);
|
||||||
|
// place the order in the legacy shop.
|
||||||
|
legacyShop.placeOrder(legacyOrder);
|
||||||
|
// the order is placed as usual since there is no other orders with the id in the both systems.
|
||||||
|
Optional<LegacyOrder> legacyOrderWithIdOne = legacyShop.findOrder("1");
|
||||||
|
assertEquals(Optional.of(legacyOrder), legacyOrderWithIdOne);
|
||||||
|
|
||||||
|
// a new order but with the same id and different data appears in the modern shop
|
||||||
|
ModernOrder modernOrder = new ModernOrder("1", new Customer("addr1"), new Shipment("item1", 10, 1), "");
|
||||||
|
|
||||||
|
// the system rejects the order since there are 2 orders with contradiction there.
|
||||||
|
modernShop.placeOrder(modernOrder);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -209,6 +209,7 @@
|
|||||||
<module>optimistic-offline-lock</module>
|
<module>optimistic-offline-lock</module>
|
||||||
<module>crtp</module>
|
<module>crtp</module>
|
||||||
<module>log-aggregation</module>
|
<module>log-aggregation</module>
|
||||||
|
<module>anti-corruption-layer</module>
|
||||||
<module>health-check</module>
|
<module>health-check</module>
|
||||||
<module>notification</module>
|
<module>notification</module>
|
||||||
<module>single-table-inheritance</module>
|
<module>single-table-inheritance</module>
|
||||||
|
|||||||
Reference in new issue
Block a user