diff --git a/gateway/README.md b/gateway/README.md new file mode 100644 index 000000000000..f1152ec6cfb4 --- /dev/null +++ b/gateway/README.md @@ -0,0 +1,150 @@ +--- +title: Gateway +category: Structural +language: en +tag: +- Gang of Four +- Decoupling + +--- + +## Intent + +Provide a interface to access a set of external systems or functionalities. Gateway provides a simple uniform view of +external resources to the internals of an application. + +## Explanation + +Real-world example + +> Gateway acts like a real front gate of a certain city. The people inside the city are called +> internal system, and different outside cities are called external services. The gateway is here +> to provide access for internal system to different external services. + +In plain words + +> Gateway can provide an interface which lets internal system to utilize external service. + +Wikipedia says + +> A server that acts as an API front-end, receives API requests, enforces throttling and security +> policies, passes requests to the back-end service and then passes the response back to the requester. + +**Programmatic Example** + +The main class in our example is the `ExternalService` that contains items. + +```java +class ExternalServiceA implements Gateway { + @Override + public void execute() throws Exception { + System.out.println("Executing Service A"); + // Simulate a time-consuming task + Thread.sleep(1000); + } +} + +/** + * ExternalServiceB is one of external services. + */ +class ExternalServiceB implements Gateway { + @Override + public void execute() throws Exception { + System.out.println("Executing Service B"); + // Simulate a time-consuming task + Thread.sleep(1000); + } +} + +/** + * ExternalServiceC is one of external services. + */ +class ExternalServiceC implements Gateway { + @Override + public void execute() throws Exception { + System.out.println("Executing Service C"); + // Simulate a time-consuming task + Thread.sleep(1000); + } + + public void error() throws Exception { + // Simulate an exception + throw new RuntimeException("Service C encountered an error"); + } +} +``` + +To operate these external services, Here's the `App` class: + +```java +public class App { + /** + * Simulate an application calling external services. + */ + public static void main(String[] args) throws Exception { + GatewayFactory gatewayFactory = new GatewayFactory(); + + // Register different gateways + gatewayFactory.registerGateway("ServiceA", new ExternalServiceA()); + gatewayFactory.registerGateway("ServiceB", new ExternalServiceB()); + gatewayFactory.registerGateway("ServiceC", new ExternalServiceC()); + + // Use an executor service for asynchronous execution + Gateway serviceA = gatewayFactory.getGateway("ServiceA"); + Gateway serviceB = gatewayFactory.getGateway("ServiceB"); + Gateway serviceC = gatewayFactory.getGateway("ServiceC"); + + // Execute external services + try { + serviceA.execute(); + serviceB.execute(); + serviceC.execute(); + } catch (ThreadDeath e) { + System.out.println("Interrupted!" + e); + throw e; + } + } +} +``` + +The `Gateway` interface is extremely simple. + +```java +interface Gateway { + void execute() throws Exception; +} +``` + +Program output: + +```java + Executing Service A + Executing Service B + Executing Service C +``` + +## Class diagram + +![alt text](./etc/gateway.urm.png "gateway") + +## Applicability + +Use the Gateway pattern + +* To access an aggregate object's contents without exposing its internal representation. +* To integration with multiple external services or APIs. +* To provide a uniform interface for traversing different aggregate structures. + +## Tutorials + +* [Pattern: API Gateway / Backends for Frontends](https://microservices.io/patterns/apigateway.html) + +## Known uses + +* [API Gateway](https://java-design-patterns.com/patterns/api-gateway/) +* [10 most common use cases of an API Gateway](https://apisix.apache.org/blog/2022/10/27/ten-use-cases-api-gateway/) + +## Credits + +* [Gateway](https://martinfowler.com/articles/gateway-pattern.html) +* [What is the difference between Facade and Gateway design patterns?](https://stackoverflow.com/questions/4422211/what-is-the-difference-between-facade-and-gateway-design-patterns) diff --git a/gateway/etc/gateway.urm.png b/gateway/etc/gateway.urm.png new file mode 100644 index 000000000000..2d8ad6c9a8f8 Binary files /dev/null and b/gateway/etc/gateway.urm.png differ diff --git a/gateway/etc/gateway.urm.puml b/gateway/etc/gateway.urm.puml new file mode 100644 index 000000000000..3439ca17f6ae --- /dev/null +++ b/gateway/etc/gateway.urm.puml @@ -0,0 +1,43 @@ +@startuml GatewayPattern +package com.iluwatar.gateway{ +class App { + +main(args: String[]): void +} + +class GatewayFactory { + -gateways: Map + +registerGateway(key: String, gateway: Gateway): void + +getGateway(key: String): Gateway +} + +interface Gateway { + {abstract} +execute(): void +} + +class ExternalServiceA { + +execute(): void +} + +class ExternalServiceB { + +execute(): void +} + +class ExternalServiceC { + +execute(): void + +error(): void +} + +App --> GatewayFactory : Uses + + +GatewayFactory --> Gateway : Creates + +GatewayFactory --> ExternalServiceA : Registers +GatewayFactory --> ExternalServiceB : Registers +GatewayFactory --> ExternalServiceC : Registers + +ExternalServiceA --> Gateway : Implements +ExternalServiceB --> Gateway : Implements +ExternalServiceC --> Gateway : Implements + +@enduml diff --git a/gateway/pom.xml b/gateway/pom.xml new file mode 100644 index 000000000000..88c15cd586ea --- /dev/null +++ b/gateway/pom.xml @@ -0,0 +1,68 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + jar + gateway + + + org.junit.jupiter + junit-jupiter-engine + test + + + junit + junit + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.gateway.App + + + + + + + + + diff --git a/gateway/src/main/java/com/iluwatar/gateway/App.java b/gateway/src/main/java/com/iluwatar/gateway/App.java new file mode 100644 index 000000000000..811cd4f7cc3b --- /dev/null +++ b/gateway/src/main/java/com/iluwatar/gateway/App.java @@ -0,0 +1,68 @@ +/* + * 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. + */ +package com.iluwatar.gateway; + +import lombok.extern.slf4j.Slf4j; + +/** + * the Gateway design pattern is a structural design pattern that provides a unified interface to a set of + * interfaces in a subsystem. It involves creating a Gateway interface that serves as a common entry point for + * interacting with various services, and concrete implementations of this interface for different external services. + * + *

In this example, GateFactory is the factory class, and it provides a method to create different kinds of external + * services. ExternalServiceA, B, and C are virtual implementations of the external services. Each service provides its + * own implementation of the execute() method. The Gateway interface is the common interface for all external services. + * The App class serves as the main entry point for the application implementing the Gateway design pattern. Through + * the Gateway interface, the App class could call each service with much less complexity. + */ +@Slf4j +public class App { + /** + * Simulate an application calling external services. + */ + public static void main(String[] args) throws Exception { + GatewayFactory gatewayFactory = new GatewayFactory(); + + // Register different gateways + gatewayFactory.registerGateway("ServiceA", new ExternalServiceA()); + gatewayFactory.registerGateway("ServiceB", new ExternalServiceB()); + gatewayFactory.registerGateway("ServiceC", new ExternalServiceC()); + + // Use an executor service for execution + Gateway serviceA = gatewayFactory.getGateway("ServiceA"); + Gateway serviceB = gatewayFactory.getGateway("ServiceB"); + Gateway serviceC = gatewayFactory.getGateway("ServiceC"); + + // Execute external services + try { + serviceA.execute(); + serviceB.execute(); + serviceC.execute(); + } catch (ThreadDeath e) { + LOGGER.info("Interrupted!" + e); + throw e; + } + } +} diff --git a/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceA.java b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceA.java new file mode 100644 index 000000000000..d6437e61197f --- /dev/null +++ b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceA.java @@ -0,0 +1,43 @@ +/* + * 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. + */ +package com.iluwatar.gateway; + + +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; + +/** +* ExternalServiceA is one of external services. +*/ +@Slf4j +class ExternalServiceA implements Gateway { + @Override + public void execute() throws Exception { + LOGGER.info("Executing Service A"); + // Simulate a time-consuming task + Thread.sleep(1000); + } +} + diff --git a/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceB.java b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceB.java new file mode 100644 index 000000000000..153cfc5b2d4c --- /dev/null +++ b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceB.java @@ -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-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. + */ +package com.iluwatar.gateway; + + +import lombok.extern.slf4j.Slf4j; + +/** +* ExternalServiceB is one of external services. +*/ +@Slf4j +class ExternalServiceB implements Gateway { + @Override + public void execute() throws Exception { + LOGGER.info("Executing Service B"); + // Simulate a time-consuming task + Thread.sleep(1000); + } +} + diff --git a/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceC.java b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceC.java new file mode 100644 index 000000000000..7b6d2c6fd324 --- /dev/null +++ b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceC.java @@ -0,0 +1,46 @@ +/* + * 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. + */ +package com.iluwatar.gateway; + + +import lombok.extern.slf4j.Slf4j; + +/** +* ExternalServiceC is one of external services. +*/ +@Slf4j +class ExternalServiceC implements Gateway { + @Override + public void execute() throws Exception { + LOGGER.info("Executing Service C"); + // Simulate a time-consuming task + Thread.sleep(1000); + } + + public void error() throws Exception { + // Simulate an exception + throw new RuntimeException("Service C encountered an error"); + } +} diff --git a/gateway/src/main/java/com/iluwatar/gateway/Gateway.java b/gateway/src/main/java/com/iluwatar/gateway/Gateway.java new file mode 100644 index 000000000000..912f78b4737b --- /dev/null +++ b/gateway/src/main/java/com/iluwatar/gateway/Gateway.java @@ -0,0 +1,8 @@ +package com.iluwatar.gateway; + +/** + * Service interface. + */ +interface Gateway { + void execute() throws Exception; +} \ No newline at end of file diff --git a/gateway/src/main/java/com/iluwatar/gateway/GatewayFactory.java b/gateway/src/main/java/com/iluwatar/gateway/GatewayFactory.java new file mode 100644 index 000000000000..0a76368a349c --- /dev/null +++ b/gateway/src/main/java/com/iluwatar/gateway/GatewayFactory.java @@ -0,0 +1,20 @@ +package com.iluwatar.gateway; + +import java.util.HashMap; +import java.util.Map; + +/** + * The "GatewayFactory" class is responsible for providing different external services in this Gateway design pattern + * example. It allows clients to register and retrieve specific gateways based on unique keys. + */ +public class GatewayFactory { + private Map gateways = new HashMap<>(); + + public void registerGateway(String key, Gateway gateway) { + gateways.put(key, gateway); + } + + public Gateway getGateway(String key) { + return gateways.get(key); + } +} diff --git a/gateway/src/test/java/com/iluwatar/gateway/AppTest.java b/gateway/src/test/java/com/iluwatar/gateway/AppTest.java new file mode 100644 index 000000000000..ad75d517576e --- /dev/null +++ b/gateway/src/test/java/com/iluwatar/gateway/AppTest.java @@ -0,0 +1,85 @@ +package com.iluwatar.gateway; + +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class AppTest { + private GatewayFactory gatewayFactory; + private ExecutorService executorService; + + @Before + public void setUp() { + gatewayFactory = new GatewayFactory(); + executorService = Executors.newFixedThreadPool(2); + gatewayFactory.registerGateway("ServiceA", new ExternalServiceA()); + gatewayFactory.registerGateway("ServiceB", new ExternalServiceB()); + gatewayFactory.registerGateway("ServiceC", new ExternalServiceC()); + } + + @Test + public void testServiceAExecution() throws InterruptedException, ExecutionException { + // Test Service A execution + Future serviceAFuture = executorService.submit(() -> { + try { + Gateway serviceA = gatewayFactory.getGateway("ServiceA"); + serviceA.execute(); + } catch (Exception e) { + fail("Service A should not throw an exception."); + } + }); + + // Wait for Service A to complete + serviceAFuture.get(); + } + + @Test + public void testServiceCExecutionWithException() throws InterruptedException, ExecutionException { + // Test Service B execution with an exception + Future serviceBFuture = executorService.submit(() -> { + try { + Gateway serviceB = gatewayFactory.getGateway("ServiceB"); + serviceB.execute(); + } catch (Exception e) { + fail("Service B should not throw an exception."); + } + }); + + // Wait for Service B to complete + serviceBFuture.get(); + } + + @Test + public void testServiceCExecution() throws InterruptedException, ExecutionException { + // Test Service C execution + Future serviceCFuture = executorService.submit(() -> { + try { + Gateway serviceC = gatewayFactory.getGateway("ServiceC"); + serviceC.execute(); + } catch (Exception e) { + fail("Service C should not throw an exception."); + } + }); + + // Wait for Service C to complete + serviceCFuture.get(); + } + + @Test + public void testServiceCError() { + try { + ExternalServiceC serviceC = (ExternalServiceC) gatewayFactory.getGateway("ServiceC"); + serviceC.error(); + fail("Service C should throw an exception."); + } catch (Exception e) { + assertEquals("Service C encountered an error", e.getMessage()); + } + } +} diff --git a/gateway/src/test/java/com/iluwatar/gateway/ServiceFactoryTest.java b/gateway/src/test/java/com/iluwatar/gateway/ServiceFactoryTest.java new file mode 100644 index 000000000000..f6dd640f31b9 --- /dev/null +++ b/gateway/src/test/java/com/iluwatar/gateway/ServiceFactoryTest.java @@ -0,0 +1,69 @@ +package com.iluwatar.gateway; + + +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ServiceFactoryTest { + private GatewayFactory gatewayFactory; + private ExecutorService executorService; + + @Before + public void setUp() { + gatewayFactory = new GatewayFactory(); + executorService = Executors.newFixedThreadPool(2); + gatewayFactory.registerGateway("ServiceA", new ExternalServiceA()); + gatewayFactory.registerGateway("ServiceB", new ExternalServiceB()); + gatewayFactory.registerGateway("ServiceC", new ExternalServiceC()); + } + + @Test + public void testGatewayFactoryRegistrationAndRetrieval() { + Gateway serviceA = gatewayFactory.getGateway("ServiceA"); + Gateway serviceB = gatewayFactory.getGateway("ServiceB"); + Gateway serviceC = gatewayFactory.getGateway("ServiceC"); + + // Check if the retrieved instances match their expected types + assertTrue("ServiceA should be an instance of ExternalServiceA", serviceA instanceof ExternalServiceA); + assertTrue("ServiceB should be an instance of ExternalServiceB", serviceB instanceof ExternalServiceB); + assertTrue("ServiceC should be an instance of ExternalServiceC", serviceC instanceof ExternalServiceC); + } + + @Test + public void testGatewayFactoryRegistrationWithNonExistingKey() { + Gateway nonExistingService = gatewayFactory.getGateway("NonExistingService"); + assertEquals(null, nonExistingService); + } + + @Test + public void testGatewayFactoryConcurrency() throws InterruptedException { + int numThreads = 10; + CountDownLatch latch = new CountDownLatch(numThreads); + AtomicBoolean failed = new AtomicBoolean(false); + + for (int i = 0; i < numThreads; i++) { + executorService.submit(() -> { + try { + Gateway serviceA = gatewayFactory.getGateway("ServiceA"); + serviceA.execute(); + } catch (Exception e) { + failed.set(true); + } finally { + latch.countDown(); + } + }); + } + + latch.await(); + assertTrue("This should not fail", !failed.get()); + } +}