Compare commits

..

2 Commits

22 changed files with 158 additions and 259 deletions

View File

@ -1758,24 +1758,6 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "yuhangbin",
"name": "CharlieYu",
"avatar_url": "https://avatars.githubusercontent.com/u/17566866?v=4",
"profile": "https://github.com/yuhangbin",
"contributions": [
"code"
]
},
{
"login": "Leisterbecker",
"name": "Leisterbecker",
"avatar_url": "https://avatars.githubusercontent.com/u/20650323?v=4",
"profile": "https://github.com/Leisterbecker",
"contributions": [
"code"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

View File

@ -10,7 +10,7 @@
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
[![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-195-orange.svg?style=flat-square)](#contributors-) [![All Contributors](https://img.shields.io/badge/all_contributors-193-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END --> <!-- ALL-CONTRIBUTORS-BADGE:END -->
<br/> <br/>
@ -323,8 +323,6 @@ This project is licensed under the terms of the MIT license.
<td align="center"><a href="http://no website"><img src="https://avatars.githubusercontent.com/u/47126749?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3AKevinyl3" title="Reviewed Pull Requests">👀</a></td> <td align="center"><a href="http://no website"><img src="https://avatars.githubusercontent.com/u/47126749?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3AKevinyl3" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/Shrirang97"><img src="https://avatars.githubusercontent.com/u/28738668?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Shrirang</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3AShrirang97" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/iluwatar/java-design-patterns/commits?author=Shrirang97" title="Code">💻</a></td> <td align="center"><a href="https://github.com/Shrirang97"><img src="https://avatars.githubusercontent.com/u/28738668?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Shrirang</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/pulls?q=is%3Apr+reviewed-by%3AShrirang97" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/iluwatar/java-design-patterns/commits?author=Shrirang97" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/interactwithankush"><img src="https://avatars.githubusercontent.com/u/18613127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>interactwithankush</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=interactwithankush" title="Code">💻</a></td> <td align="center"><a href="https://github.com/interactwithankush"><img src="https://avatars.githubusercontent.com/u/18613127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>interactwithankush</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=interactwithankush" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/yuhangbin"><img src="https://avatars.githubusercontent.com/u/17566866?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CharlieYu</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=yuhangbin" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Leisterbecker"><img src="https://avatars.githubusercontent.com/u/20650323?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Leisterbecker</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=Leisterbecker" title="Code">💻</a></td>
</tr> </tr>
</table> </table>

View File

@ -49,28 +49,13 @@ public class App {
public static void main(String[] args) { public static void main(String[] args) {
var kingJoffrey = new KingJoffrey(); var kingJoffrey = new KingJoffrey();
var kingsHand = new KingsHand(kingJoffrey);
var kingsHand = new KingsHand();
kingsHand.registerObserver(kingJoffrey, Event.TRAITOR_DETECTED);
kingsHand.registerObserver(kingJoffrey, Event.STARK_SIGHTED);
kingsHand.registerObserver(kingJoffrey, Event.WARSHIPS_APPROACHING);
kingsHand.registerObserver(kingJoffrey, Event.WHITE_WALKERS_SIGHTED);
var varys = new LordVarys();
varys.registerObserver(kingsHand, Event.TRAITOR_DETECTED);
varys.registerObserver(kingsHand, Event.WHITE_WALKERS_SIGHTED);
var scout = new Scout();
scout.registerObserver(kingsHand, Event.WARSHIPS_APPROACHING);
scout.registerObserver(varys, Event.WHITE_WALKERS_SIGHTED);
var baelish = new LordBaelish(kingsHand, Event.STARK_SIGHTED);
var emitters = List.of( var emitters = List.of(
kingsHand, kingsHand,
baelish, new LordBaelish(kingsHand),
varys, new LordVarys(kingsHand),
scout new Scout(kingsHand)
); );
Arrays.stream(Weekday.values()) Arrays.stream(Weekday.values())

View File

@ -31,7 +31,6 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor @RequiredArgsConstructor
public enum Event { public enum Event {
WHITE_WALKERS_SIGHTED("White walkers sighted"),
STARK_SIGHTED("Stark sighted"), STARK_SIGHTED("Stark sighted"),
WARSHIPS_APPROACHING("Warships approaching"), WARSHIPS_APPROACHING("Warships approaching"),
TRAITOR_DETECTED("Traitor detected"); TRAITOR_DETECTED("Traitor detected");

View File

@ -23,48 +23,31 @@
package com.iluwatar.event.aggregator; package com.iluwatar.event.aggregator;
import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* EventEmitter is the base class for event producers that can be observed. * EventEmitter is the base class for event producers that can be observed.
*/ */
public abstract class EventEmitter { public abstract class EventEmitter {
private final Map<Event, List<EventObserver>> observerLists; private final List<EventObserver> observers;
public EventEmitter() { public EventEmitter() {
observerLists = new HashMap<>(); observers = new LinkedList<>();
} }
public EventEmitter(EventObserver obs, Event e) { public EventEmitter(EventObserver obs) {
this(); this();
registerObserver(obs, e); registerObserver(obs);
} }
/** public final void registerObserver(EventObserver obs) {
* Registers observer for specific event in the related list. observers.add(obs);
*
* @param obs the observer that observers this emitter
* @param e the specific event for that observation occurs
* */
public final void registerObserver(EventObserver obs, Event e) {
if (!observerLists.containsKey(e)) {
observerLists.put(e, new LinkedList<>());
}
if (!observerLists.get(e).contains(obs)) {
observerLists.get(e).add(obs);
}
} }
protected void notifyObservers(Event e) { protected void notifyObservers(Event e) {
if (observerLists.containsKey(e)) { observers.forEach(obs -> obs.onEvent(e));
observerLists
.get(e)
.forEach(observer -> observer.onEvent(e));
}
} }
public abstract void timePasses(Weekday day); public abstract void timePasses(Weekday day);

View File

@ -31,8 +31,8 @@ public class KingsHand extends EventEmitter implements EventObserver {
public KingsHand() { public KingsHand() {
} }
public KingsHand(EventObserver obs, Event e) { public KingsHand(EventObserver obs) {
super(obs, e); super(obs);
} }
@Override @Override
@ -42,5 +42,6 @@ public class KingsHand extends EventEmitter implements EventObserver {
@Override @Override
public void timePasses(Weekday day) { public void timePasses(Weekday day) {
// NOP
} }
} }

View File

@ -31,8 +31,8 @@ public class LordBaelish extends EventEmitter {
public LordBaelish() { public LordBaelish() {
} }
public LordBaelish(EventObserver obs, Event e) { public LordBaelish(EventObserver obs) {
super(obs, e); super(obs);
} }
@Override @Override

View File

@ -23,19 +23,16 @@
package com.iluwatar.event.aggregator; package com.iluwatar.event.aggregator;
import lombok.extern.slf4j.Slf4j;
/** /**
* LordVarys produces events. * LordVarys produces events.
*/ */
@Slf4j public class LordVarys extends EventEmitter {
public class LordVarys extends EventEmitter implements EventObserver {
public LordVarys() { public LordVarys() {
} }
public LordVarys(EventObserver obs, Event e) { public LordVarys(EventObserver obs) {
super(obs, e); super(obs);
} }
@Override @Override
@ -44,10 +41,4 @@ public class LordVarys extends EventEmitter implements EventObserver {
notifyObservers(Event.TRAITOR_DETECTED); notifyObservers(Event.TRAITOR_DETECTED);
} }
} }
@Override
public void onEvent(Event e) {
notifyObservers(e);
}
} }

View File

@ -31,8 +31,8 @@ public class Scout extends EventEmitter {
public Scout() { public Scout() {
} }
public Scout(EventObserver obs, Event e) { public Scout(EventObserver obs) {
super(obs, e); super(obs);
} }
@Override @Override
@ -40,8 +40,5 @@ public class Scout extends EventEmitter {
if (day == Weekday.TUESDAY) { if (day == Weekday.TUESDAY) {
notifyObservers(Event.WARSHIPS_APPROACHING); notifyObservers(Event.WARSHIPS_APPROACHING);
} }
if (day == Weekday.WEDNESDAY) {
notifyObservers(Event.WHITE_WALKERS_SIGHTED);
}
} }
} }

View File

@ -31,7 +31,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.verifyZeroInteractions;
import java.util.Objects; import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -47,7 +46,7 @@ abstract class EventEmitterTest<E extends EventEmitter> {
/** /**
* Factory used to create a new instance of the test object with a default observer * Factory used to create a new instance of the test object with a default observer
*/ */
private final BiFunction<EventObserver, Event, E> factoryWithDefaultObserver; private final Function<EventObserver, E> factoryWithDefaultObserver;
/** /**
* Factory used to create a new instance of the test object without passing a default observer * Factory used to create a new instance of the test object without passing a default observer
@ -68,7 +67,7 @@ abstract class EventEmitterTest<E extends EventEmitter> {
* Create a new event emitter test, using the given test object factories, special day and event * Create a new event emitter test, using the given test object factories, special day and event
*/ */
EventEmitterTest(final Weekday specialDay, final Event event, EventEmitterTest(final Weekday specialDay, final Event event,
final BiFunction<EventObserver, Event, E> factoryWithDefaultObserver, final Function<EventObserver, E> factoryWithDefaultObserver,
final Supplier<E> factoryWithoutDefaultObserver) { final Supplier<E> factoryWithoutDefaultObserver) {
this.specialDay = specialDay; this.specialDay = specialDay;
@ -130,8 +129,8 @@ abstract class EventEmitterTest<E extends EventEmitter> {
final var observer2 = mock(EventObserver.class); final var observer2 = mock(EventObserver.class);
final var emitter = this.factoryWithoutDefaultObserver.get(); final var emitter = this.factoryWithoutDefaultObserver.get();
emitter.registerObserver(observer1, event); emitter.registerObserver(observer1);
emitter.registerObserver(observer2, event); emitter.registerObserver(observer2);
testAllDays(specialDay, event, emitter, observer1, observer2); testAllDays(specialDay, event, emitter, observer1, observer2);
} }
@ -147,9 +146,9 @@ abstract class EventEmitterTest<E extends EventEmitter> {
final var observer1 = mock(EventObserver.class); final var observer1 = mock(EventObserver.class);
final var observer2 = mock(EventObserver.class); final var observer2 = mock(EventObserver.class);
final var emitter = this.factoryWithDefaultObserver.apply(defaultObserver, event); final var emitter = this.factoryWithDefaultObserver.apply(defaultObserver);
emitter.registerObserver(observer1, event); emitter.registerObserver(observer1);
emitter.registerObserver(observer2, event); emitter.registerObserver(observer2);
testAllDays(specialDay, event, emitter, defaultObserver, observer1, observer2); testAllDays(specialDay, event, emitter, defaultObserver, observer1, observer2);
} }

View File

@ -55,11 +55,7 @@ class KingsHandTest extends EventEmitterTest<KingsHand> {
@Test @Test
void testPassThrough() throws Exception { void testPassThrough() throws Exception {
final var observer = mock(EventObserver.class); final var observer = mock(EventObserver.class);
final var kingsHand = new KingsHand(); final var kingsHand = new KingsHand(observer);
kingsHand.registerObserver(observer, Event.STARK_SIGHTED);
kingsHand.registerObserver(observer, Event.WARSHIPS_APPROACHING);
kingsHand.registerObserver(observer, Event.TRAITOR_DETECTED);
kingsHand.registerObserver(observer, Event.WHITE_WALKERS_SIGHTED);
// The kings hand should not pass any events before he received one // The kings hand should not pass any events before he received one
verifyZeroInteractions(observer); verifyZeroInteractions(observer);

View File

@ -34,9 +34,7 @@ class ScoutTest extends EventEmitterTest<Scout> {
* Create a new test instance, using the correct object factory * Create a new test instance, using the correct object factory
*/ */
public ScoutTest() { public ScoutTest() {
super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new); super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new);
} }
} }

View File

@ -96,11 +96,9 @@ public class NioReactor {
* @throws IOException if any I/O error occurs. * @throws IOException if any I/O error occurs.
*/ */
public void stop() throws InterruptedException, IOException { public void stop() throws InterruptedException, IOException {
reactorMain.shutdown();
selector.wakeup();
if (!reactorMain.awaitTermination(4, TimeUnit.SECONDS)) {
reactorMain.shutdownNow(); reactorMain.shutdownNow();
} selector.wakeup();
reactorMain.awaitTermination(4, TimeUnit.SECONDS);
selector.close(); selector.close();
LOGGER.info("Reactor stopped"); LOGGER.info("Reactor stopped");
} }

View File

@ -64,8 +64,6 @@ public class ThreadPoolDispatcher implements Dispatcher {
@Override @Override
public void stop() throws InterruptedException { public void stop() throws InterruptedException {
executorService.shutdown(); executorService.shutdown();
if (executorService.awaitTermination(4, TimeUnit.SECONDS)) { executorService.awaitTermination(4, TimeUnit.SECONDS);
executorService.shutdownNow();
}
} }
} }

View File

@ -18,11 +18,11 @@ threads and eliminating the latency of creating new threads.
## Explanation ## Explanation
Real-world example Real world example
> We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes > We have a large number of relatively short tasks at hand. We need to peel huge amounts of potatoes
> and serve a mighty amount of coffee cups. Creating a new thread for each task would be a waste so > and serve mighty amount of coffee cups. Creating a new thread for each task would be a waste so we
> we establish a thread pool. > establish a thread pool.
In plain words In plain words
@ -99,7 +99,7 @@ public class PotatoPeelingTask extends Task {
} }
``` ```
Next, we present a runnable `Worker` class that the thread pool will utilize to handle all the potato Next we present a runnable `Worker` class that the thread pool will utilize to handle all the potato
peeling and coffee making. peeling and coffee making.
```java ```java

View File

@ -16,12 +16,10 @@ Ensure that a given client is not able to access service resources more than the
## Explanation ## Explanation
Real-world example Real world example
> A young human and an old dwarf walk into a bar. They start ordering beers from the bartender. > A large multinational corporation offers API to its customers. The API is rate-limited and each
> The bartender immediately sees that the young human shouldn't consume too many drinks too fast > customer can only make certain amount of calls per second.
> and refuses to serve if enough time has not passed. For the old dwarf, the serving rate can
> be higher.
In plain words In plain words
@ -35,18 +33,15 @@ In plain words
**Programmatic Example** **Programmatic Example**
`BarCustomer` class presents the clients of the `Bartender` API. `CallsCount` tracks the number of Tenant class presents the clients of the API. CallsCount tracks the number of API calls per tenant.
calls per `BarCustomer`.
```java ```java
public class BarCustomer { public class Tenant {
@Getter
private final String name; private final String name;
@Getter
private final int allowedCallsPerSecond; private final int allowedCallsPerSecond;
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) { public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
if (allowedCallsPerSecond < 0) { if (allowedCallsPerSecond < 0) {
throw new InvalidParameterException("Number of calls less than 0 not allowed"); throw new InvalidParameterException("Number of calls less than 0 not allowed");
} }
@ -54,6 +49,14 @@ public class BarCustomer {
this.allowedCallsPerSecond = allowedCallsPerSecond; this.allowedCallsPerSecond = allowedCallsPerSecond;
callsCount.addTenant(name); callsCount.addTenant(name);
} }
public String getName() {
return name;
}
public int getAllowedCallsPerSecond() {
return allowedCallsPerSecond;
}
} }
@Slf4j @Slf4j
@ -73,14 +76,14 @@ public final class CallsCount {
} }
public void reset() { public void reset() {
LOGGER.debug("Resetting the map.");
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0)); tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
LOGGER.info("reset counters");
} }
} }
``` ```
Next, the service that the tenants are calling is introduced. To track the call count, a throttler Next we introduce the service that the tenants are calling. To track the call count we use the
timer is used. throttler timer.
```java ```java
public interface Throttler { public interface Throttler {
@ -108,31 +111,26 @@ public class ThrottleTimerImpl implements Throttler {
}, 0, throttlePeriod); }, 0, throttlePeriod);
} }
} }
```
`Bartender` offers the `orderDrink` service to the `BarCustomer`s. The customers probably don't class B2BService {
know that the beer serving rate is limited by their appearances.
```java private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
class Bartender {
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class);
private final CallsCount callsCount; private final CallsCount callsCount;
public Bartender(Throttler timer, CallsCount callsCount) { public B2BService(Throttler timer, CallsCount callsCount) {
this.callsCount = callsCount; this.callsCount = callsCount;
timer.start(); timer.start();
} }
public int orderDrink(BarCustomer barCustomer) { public int dummyCustomerApi(Tenant tenant) {
var tenantName = barCustomer.getName(); var tenantName = tenant.getName();
var count = callsCount.getCount(tenantName); var count = callsCount.getCount(tenantName);
if (count >= barCustomer.getAllowedCallsPerSecond()) { LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName); if (count >= tenant.getAllowedCallsPerSecond()) {
LOGGER.error("API access per second limit reached for: {}", tenantName);
return -1; return -1;
} }
callsCount.incrementCount(tenantName); callsCount.incrementCount(tenantName);
LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count+1);
return getRandomCustomerId(); return getRandomCustomerId();
} }
@ -142,36 +140,35 @@ class Bartender {
} }
``` ```
Now it is possible to see the full example in action. `BarCustomer` young human is rate-limited to 2 Now we are ready to see the full example in action. Tenant Adidas is rate-limited to 5 calls per
calls per second and the old dwarf to 4. second and Nike to 6.
```java ```java
public static void main(String[] args) { public static void main(String[] args) {
var callsCount = new CallsCount(); var callsCount = new CallsCount();
var human = new BarCustomer("young human", 2, callsCount); var adidas = new Tenant("Adidas", 5, callsCount);
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount); var nike = new Tenant("Nike", 6, callsCount);
var executorService = Executors.newFixedThreadPool(2); var executorService = Executors.newFixedThreadPool(2);
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
executorService.execute(() -> makeServiceCalls(human, callsCount)); executorService.execute(() -> makeServiceCalls(nike, callsCount));
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
executorService.shutdown(); executorService.shutdown();
try { try {
executorService.awaitTermination(10, TimeUnit.SECONDS); executorService.awaitTermination(10, TimeUnit.SECONDS);
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOGGER.error("Executor service terminated: {}", e.getMessage()); LOGGER.error("Executor Service terminated: {}", e.getMessage());
} }
} }
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) { private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
var timer = new ThrottleTimerImpl(1000, callsCount); var timer = new ThrottleTimerImpl(10, callsCount);
var service = new Bartender(timer, callsCount); var service = new B2BService(timer, callsCount);
// Sleep is introduced to keep the output in check and easy to view and analyze the results. // Sleep is introduced to keep the output in check and easy to view and analyze the results.
IntStream.range(0, 50).forEach(i -> { IntStream.range(0, 20).forEach(i -> {
service.orderDrink(barCustomer); service.dummyCustomerApi(tenant);
try { try {
Thread.sleep(100); Thread.sleep(1);
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOGGER.error("Thread interrupted: {}", e.getMessage()); LOGGER.error("Thread interrupted: {}", e.getMessage());
} }
@ -179,32 +176,6 @@ private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCo
} }
``` ```
An excerpt from the example's console output:
```
18:46:36.218 [Timer-0] INFO com.iluwatar.throttling.CallsCount - reset counters
18:46:36.218 [Timer-1] INFO com.iluwatar.throttling.CallsCount - reset counters
18:46:36.242 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [1 consumed]
18:46:36.242 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [1 consumed]
18:46:36.342 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [2 consumed]
18:46:36.342 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [2 consumed]
18:46:36.443 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
18:46:36.443 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [3 consumed]
18:46:36.544 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
18:46:36.544 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [4 consumed]
18:46:36.645 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
18:46:36.645 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
18:46:36.745 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
18:46:36.745 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
18:46:36.846 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
18:46:36.846 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
18:46:36.947 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
18:46:36.947 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
18:46:37.048 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
18:46:37.048 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
18:46:37.148 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
18:46:37.148 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
```
## Class diagram ## Class diagram
@ -214,7 +185,7 @@ An excerpt from the example's console output:
The Throttling pattern should be used: The Throttling pattern should be used:
* When service access needs to be restricted not to have high impact on the performance of the service. * When a service access needs to be restricted to not have high impacts on the performance of the service.
* When multiple clients are consuming the same service resources and restriction has to be made according to the usage per client. * When multiple clients are consuming the same service resources and restriction has to be made according to the usage per client.
## Credits ## Credits

View File

@ -34,11 +34,11 @@ import lombok.extern.slf4j.Slf4j;
* complete service by users or a particular tenant. This can allow systems to continue to function * 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. * and meet service level agreements, even when an increase in demand places load on resources.
* <p> * <p>
* In this example there is a {@link Bartender} serving beer to {@link BarCustomer}s. This is a time * 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. * based throttling, i.e. only a certain number of calls are allowed per second.
* </p> * </p>
* ({@link BarCustomer}) is the service tenant class having a name and the number of calls allowed. * ({@link Tenant}) is the Tenant POJO class with which many tenants can be created ({@link
* ({@link Bartender}) is the service which is consumed by the tenants and is throttled. * B2BService}) is the service which is consumed by the tenants and is throttled.
*/ */
@Slf4j @Slf4j
public class App { public class App {
@ -50,35 +50,33 @@ public class App {
*/ */
public static void main(String[] args) { public static void main(String[] args) {
var callsCount = new CallsCount(); var callsCount = new CallsCount();
var human = new BarCustomer("young human", 2, callsCount); var adidas = new Tenant("Adidas", 5, callsCount);
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount); var nike = new Tenant("Nike", 6, callsCount);
var executorService = Executors.newFixedThreadPool(2); var executorService = Executors.newFixedThreadPool(2);
executorService.execute(() -> makeServiceCalls(human, callsCount)); executorService.execute(() -> makeServiceCalls(adidas, callsCount));
executorService.execute(() -> makeServiceCalls(dwarf, callsCount)); executorService.execute(() -> makeServiceCalls(nike, callsCount));
executorService.shutdown(); executorService.shutdown();
try { try {
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) { executorService.awaitTermination(10, TimeUnit.SECONDS);
executorService.shutdownNow();
}
} catch (InterruptedException e) { } catch (InterruptedException e) {
executorService.shutdownNow(); LOGGER.error("Executor Service terminated: {}", e.getMessage());
} }
} }
/** /**
* Make calls to the bartender. * Make calls to the B2BService dummy API.
*/ */
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) { private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
var timer = new ThrottleTimerImpl(1000, callsCount); var timer = new ThrottleTimerImpl(10, callsCount);
var service = new Bartender(timer, callsCount); var service = new B2BService(timer, callsCount);
// Sleep is introduced to keep the output in check and easy to view and analyze the results. // Sleep is introduced to keep the output in check and easy to view and analyze the results.
IntStream.range(0, 50).forEach(i -> { IntStream.range(0, 20).forEach(i -> {
service.orderDrink(barCustomer); service.dummyCustomerApi(tenant);
try { try {
Thread.sleep(100); Thread.sleep(1);
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOGGER.error("Thread interrupted: {}", e.getMessage()); LOGGER.error("Thread interrupted: {}", e.getMessage());
} }

View File

@ -29,32 +29,33 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* Bartender is a service which accepts a BarCustomer (tenant) and throttles * A service which accepts a tenant and throttles the resource based on the time given to the
* the resource based on the time given to the tenant. * tenant.
*/ */
class Bartender { class B2BService {
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class); private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
private final CallsCount callsCount; private final CallsCount callsCount;
public Bartender(Throttler timer, CallsCount callsCount) { public B2BService(Throttler timer, CallsCount callsCount) {
this.callsCount = callsCount; this.callsCount = callsCount;
timer.start(); timer.start();
} }
/** /**
* Orders a drink from the bartender. * Calls dummy customer api.
*
* @return customer id which is randomly generated * @return customer id which is randomly generated
*/ */
public int orderDrink(BarCustomer barCustomer) { public int dummyCustomerApi(Tenant tenant) {
var tenantName = barCustomer.getName(); var tenantName = tenant.getName();
var count = callsCount.getCount(tenantName); var count = callsCount.getCount(tenantName);
if (count >= barCustomer.getAllowedCallsPerSecond()) { LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName); if (count >= tenant.getAllowedCallsPerSecond()) {
LOGGER.error("API access per second limit reached for: {}", tenantName);
return -1; return -1;
} }
callsCount.incrementCount(tenantName); callsCount.incrementCount(tenantName);
LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count + 1);
return getRandomCustomerId(); return getRandomCustomerId();
} }

View File

@ -69,7 +69,7 @@ public final class CallsCount {
* Resets the count of all the tenants in the map. * Resets the count of all the tenants in the map.
*/ */
public void reset() { public void reset() {
LOGGER.debug("Resetting the map.");
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0)); tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
LOGGER.info("reset counters");
} }
} }

View File

@ -25,26 +25,22 @@ package com.iluwatar.throttling;
import java.security.InvalidParameterException; import java.security.InvalidParameterException;
import lombok.Getter;
/** /**
* BarCustomer is a tenant with a name and a number of allowed calls per second. * A Pojo class to create a basic Tenant with the allowed calls per second.
*/ */
public class BarCustomer { public class Tenant {
@Getter
private final String name; private final String name;
@Getter
private final int allowedCallsPerSecond; private final int allowedCallsPerSecond;
/** /**
* Constructor. * Constructor.
* *
* @param name Name of the BarCustomer * @param name Name of the tenant
* @param allowedCallsPerSecond The number of calls allowed for this particular tenant. * @param allowedCallsPerSecond The number of calls allowed for a particular tenant.
* @throws InvalidParameterException If number of calls is less than 0, throws exception. * @throws InvalidParameterException If number of calls is less than 0, throws exception.
*/ */
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) { public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
if (allowedCallsPerSecond < 0) { if (allowedCallsPerSecond < 0) {
throw new InvalidParameterException("Number of calls less than 0 not allowed"); throw new InvalidParameterException("Number of calls less than 0 not allowed");
} }
@ -52,4 +48,12 @@ public class BarCustomer {
this.allowedCallsPerSecond = allowedCallsPerSecond; this.allowedCallsPerSecond = allowedCallsPerSecond;
callsCount.addTenant(name); callsCount.addTenant(name);
} }
public String getName() {
return name;
}
public int getAllowedCallsPerSecond() {
return allowedCallsPerSecond;
}
} }

View File

@ -32,18 +32,19 @@ import org.junit.jupiter.api.Test;
/** /**
* B2BServiceTest class to test the B2BService * B2BServiceTest class to test the B2BService
*/ */
public class BartenderTest { public class B2BServiceTest {
private final CallsCount callsCount = new CallsCount(); private final CallsCount callsCount = new CallsCount();
@Test @Test
void dummyCustomerApiTest() { void dummyCustomerApiTest() {
var tenant = new BarCustomer("pirate", 2, callsCount); var tenant = new Tenant("testTenant", 2, callsCount);
// In order to assure that throttling limits will not be reset, we use an empty throttling implementation // In order to assure that throttling limits will not be reset, we use an empty throttling implementation
var timer = (Throttler) () -> {}; var timer = (Throttler) () -> {
var service = new Bartender(timer, callsCount); };
var service = new B2BService(timer, callsCount);
IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::orderDrink); IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::dummyCustomerApi);
var counter = callsCount.getCount(tenant.getName()); var counter = callsCount.getCount(tenant.getName());
assertEquals(2, counter, "Counter limit must be reached"); assertEquals(2, counter, "Counter limit must be reached");
} }

View File

@ -23,21 +23,20 @@
package com.iluwatar.throttling; package com.iluwatar.throttling;
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertThrows;
import java.security.InvalidParameterException; import java.security.InvalidParameterException;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;
/** /**
* TenantTest to test the creation of Tenant with valid parameters. * TenantTest to test the creation of Tenant with valid parameters.
*/ */
public class BarCustomerTest { public class TenantTest {
@Test @Test
void constructorTest() { void constructorTest() {
assertThrows(InvalidParameterException.class, () -> { assertThrows(InvalidParameterException.class, () -> {
new BarCustomer("sirBrave", -1, new CallsCount()); new Tenant("FailTenant", -1, new CallsCount());
}); });
} }
} }