diff --git a/pom.xml b/pom.xml index 44461b091..d811ef883 100644 --- a/pom.xml +++ b/pom.xml @@ -145,7 +145,8 @@ cqrs event-sourcing data-transfer-object - + throttling + @@ -486,4 +487,4 @@ - + \ No newline at end of file diff --git a/throttling/README.md b/throttling/README.md new file mode 100644 index 000000000..adb067200 --- /dev/null +++ b/throttling/README.md @@ -0,0 +1,16 @@ +--- +layout: pattern +title: Throttling pattern +folder: throttling +permalink: /patterns/throttling/ +tags: + - Java + - Difficulty-Beginner + - Throttling +--- + +## Applicability +The Throttling pattern should be used: + +* when a service access needs to be restricted to not have high impacts on the performance of the service. +* when multiple tenants are consuming the same resources and restriction has to be made according to the usage per tenant. diff --git a/throttling/pom.xml b/throttling/pom.xml new file mode 100644 index 000000000..60517c8de --- /dev/null +++ b/throttling/pom.xml @@ -0,0 +1,20 @@ + + + + java-design-patterns + com.iluwatar + 1.17.0-SNAPSHOT + + 4.0.0 + + throttling + + + junit + junit + + + + \ No newline at end of file diff --git a/throttling/src/main/java/com/iluwatar/tls/App.java b/throttling/src/main/java/com/iluwatar/tls/App.java new file mode 100644 index 000000000..f476b10af --- /dev/null +++ b/throttling/src/main/java/com/iluwatar/tls/App.java @@ -0,0 +1,85 @@ +/** + * The MIT License + * Copyright (c) 2014-2016 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.tls; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.file.AccessDeniedException; + +/** + * Throttling pattern is a design pattern to throttle or limit the use of resources or even a complete service by + * users or a particular tenant. This can allow systems to continue to function and meet service level agreements, + * even when an increase in demand places load on resources. + *

+ * In this example we have ({@link App}) as the initiating point of the service. + * This is a time based throttling, i.e. only a certain number of calls are allowed per second. + *

+ * ({@link Tenant}) is the Tenant POJO class with which many tenants can be created + * ({@link B2BService}) is the service which is consumed by the tenants and is throttled. + */ +public class App { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * Application entry point + * @param args main arguments + */ + public static void main(String[] args) { + + Tenant adidas = new Tenant("Adidas", 80); + Tenant nike = new Tenant("Nike", 70); + + B2BService adidasService = new B2BService(adidas); + B2BService nikeService = new B2BService(nike); + + Runnable adidasTask = () -> makeServiceCalls(adidasService); + Runnable nikeTask = () -> makeServiceCalls(nikeService); + + new Thread(adidasTask).start(); + new Thread(nikeTask).start(); + } + + /** + * Make calls to the B2BService dummy API + * @param service an instance of B2BService + */ + private static void makeServiceCalls(B2BService service) { + for (int i = 0; i < 500; i++) { + try { + service.dummyCustomerApi(); +// This block is introduced to keep the output in check and easy to view and analyze the results. + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } +// It can be removed if required. + } catch (AccessDeniedException e) { + LOGGER.error("### {} ###", e.getMessage()); + } + } + } +} diff --git a/throttling/src/main/java/com/iluwatar/tls/B2BService.java b/throttling/src/main/java/com/iluwatar/tls/B2BService.java new file mode 100644 index 000000000..832e24f1f --- /dev/null +++ b/throttling/src/main/java/com/iluwatar/tls/B2BService.java @@ -0,0 +1,64 @@ +package com.iluwatar.tls; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.file.AccessDeniedException; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ThreadLocalRandom; + +/** + * A service which accepts a tenant and throttles the resource based on the time given to the tenant. + */ +class B2BService { + + private Tenant tenant; + private int callsCounter; + + private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class); + + /** + * A timer is initiated as soon as the Service is initiated. The timer runs every minute and resets the + * counter. + * @param tenant the Tenant which will consume the service. + */ + public B2BService(Tenant tenant) { + this.tenant = tenant; + Timer timer = new Timer(true); + + timer.schedule(new TimerTask() { + @Override + public void run() { + callsCounter = 0; + } + }, 0, 1000); + } + + /** + * + * @return customer id which is randomly generated + * @throws AccessDeniedException when the limit is reached + */ + public int dummyCustomerApi() throws AccessDeniedException { + LOGGER.debug("Counter for {} : {} ", tenant.getName(), callsCounter); + + if (callsCounter >= tenant.getAllowedCallsPerSecond()) { + throw new AccessDeniedException("API access per second limit reached for: " + tenant.getName()); + } + callsCounter++; + return getRandomCustomerId(); + } + + private int getRandomCustomerId() { + return ThreadLocalRandom.current().nextInt(1, 10000); + } + + /** + * + * @return current count of the calls made. + */ + public int getCurrentCallsCount() { + return callsCounter; + } +} diff --git a/throttling/src/main/java/com/iluwatar/tls/Tenant.java b/throttling/src/main/java/com/iluwatar/tls/Tenant.java new file mode 100644 index 000000000..6ef9bd28b --- /dev/null +++ b/throttling/src/main/java/com/iluwatar/tls/Tenant.java @@ -0,0 +1,42 @@ +package com.iluwatar.tls; + +import java.security.InvalidParameterException; + +/** + * A Pojo class to create a basic Tenant with the allowed calls per second. + */ +public class Tenant { + + private String name; + private int allowedCallsPerSecond; + + /** + * + * @param name Name of the tenant + * @param allowedCallsPerSecond The number of calls allowed for a particular tenant. + * @throws InvalidParameterException If number of calls is less than 0, throws exception. + */ + public Tenant(String name, int allowedCallsPerSecond) { + if (allowedCallsPerSecond < 0) { + throw new InvalidParameterException("Number of calls less than 0 not allowed"); + } + this.name = name; + this.allowedCallsPerSecond = allowedCallsPerSecond; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAllowedCallsPerSecond() { + return allowedCallsPerSecond; + } + + public void setAllowedCallsPerSecond(int allowedCallsPerSecond) { + this.allowedCallsPerSecond = allowedCallsPerSecond; + } +} diff --git a/throttling/src/test/java/com/iluwatar/tls/B2BServiceTest.java b/throttling/src/test/java/com/iluwatar/tls/B2BServiceTest.java new file mode 100644 index 000000000..13921f1c9 --- /dev/null +++ b/throttling/src/test/java/com/iluwatar/tls/B2BServiceTest.java @@ -0,0 +1,29 @@ +package com.iluwatar.tls; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.file.AccessDeniedException; + +/** + * B2BServiceTest class to test the B2BService + */ +public class B2BServiceTest { + + @Test + public void counterResetTest() throws AccessDeniedException { + Tenant tenant = new Tenant("testTenant", 100); + B2BService service = new B2BService(tenant); + + for (int i = 0; i < 20; i++) { + service.dummyCustomerApi(); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + int counter = service.getCurrentCallsCount(); + Assert.assertTrue("", counter < 11); + } +} diff --git a/throttling/src/test/java/com/iluwatar/tls/TenantTest.java b/throttling/src/test/java/com/iluwatar/tls/TenantTest.java new file mode 100644 index 000000000..17853c7d6 --- /dev/null +++ b/throttling/src/test/java/com/iluwatar/tls/TenantTest.java @@ -0,0 +1,16 @@ +package com.iluwatar.tls; + +import org.junit.Test; + +import java.security.InvalidParameterException; + +/** + * TenantTest to test the creation of Tenant with valid parameters. + */ +public class TenantTest { + + @Test(expected = InvalidParameterException.class) + public void constructorTest() { + Tenant tenant = new Tenant("FailTenant", -1); + } +}