mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-05-14 08:58:26 +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:
@@ -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;
|
||||
}
|
||||
+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);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user