+ * 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); + } +}