Compare commits
48 Commits
all-contri
...
updateThro
Author | SHA1 | Date | |
---|---|---|---|
68a5df1cc9 | |||
1c70cdc333 | |||
55f4ecc234 | |||
3855f0bc15 | |||
d71d2a4abd | |||
67f0a201e2 | |||
3f08aebcd3 | |||
8604e65fce | |||
618b90147a | |||
405495f0a8 | |||
f9ca7f752e | |||
b2a22e506e | |||
1fb1073436 | |||
c28b8edf9e | |||
35ad973730 | |||
a4d77e65f8 | |||
0d1e226258 | |||
dc73d57f3e | |||
4f2becf96f | |||
6fed7cbbb2 | |||
c7c3cb8e19 | |||
6bd6f07bc2 | |||
b0d17a00cd | |||
18b3fa312f | |||
fc75edc7ca | |||
c78d5a3220 | |||
11dcf5a4aa | |||
df84a3cc74 | |||
6523047361 | |||
4a4c136757 | |||
1b5da55d45 | |||
e9b20f674e | |||
23162f474f | |||
1610876fdd | |||
be55b8ebb8 | |||
72aaea5333 | |||
e62a412318 | |||
ff955b4d95 | |||
a7650013a9 | |||
876d2a5466 | |||
636a826bb7 | |||
2230c55582 | |||
f003844c25 | |||
f670ae547b | |||
df73d80365 | |||
4588e09939 | |||
69883196d2 | |||
4dcc20b733 |
@ -1749,6 +1749,33 @@
|
||||
"review",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "interactwithankush",
|
||||
"name": "interactwithankush",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/18613127?v=4",
|
||||
"profile": "https://github.com/interactwithankush",
|
||||
"contributions": [
|
||||
"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,
|
||||
|
@ -10,7 +10,7 @@
|
||||
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
|
||||
[](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 -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
<br/>
|
||||
@ -322,6 +322,9 @@ This project is licensed under the terms of the MIT license.
|
||||
<td align="center"><a href="https://www.linkedin.com/in/abhinav-vashisth-06613b208/"><img src="https://avatars.githubusercontent.com/u/89785800?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Abhinav Vashisth</b></sub></a><br /><a href="https://github.com/iluwatar/java-design-patterns/commits?author=vashisthabhinav" title="Documentation">📖</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/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>
|
||||
</table>
|
||||
|
||||
|
@ -49,13 +49,28 @@ public class App {
|
||||
public static void main(String[] args) {
|
||||
|
||||
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(
|
||||
kingsHand,
|
||||
new LordBaelish(kingsHand),
|
||||
new LordVarys(kingsHand),
|
||||
new Scout(kingsHand)
|
||||
baelish,
|
||||
varys,
|
||||
scout
|
||||
);
|
||||
|
||||
Arrays.stream(Weekday.values())
|
||||
|
@ -31,6 +31,7 @@ import lombok.RequiredArgsConstructor;
|
||||
@RequiredArgsConstructor
|
||||
public enum Event {
|
||||
|
||||
WHITE_WALKERS_SIGHTED("White walkers sighted"),
|
||||
STARK_SIGHTED("Stark sighted"),
|
||||
WARSHIPS_APPROACHING("Warships approaching"),
|
||||
TRAITOR_DETECTED("Traitor detected");
|
||||
|
@ -23,31 +23,48 @@
|
||||
|
||||
package com.iluwatar.event.aggregator;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* EventEmitter is the base class for event producers that can be observed.
|
||||
*/
|
||||
public abstract class EventEmitter {
|
||||
|
||||
private final List<EventObserver> observers;
|
||||
private final Map<Event, List<EventObserver>> observerLists;
|
||||
|
||||
public EventEmitter() {
|
||||
observers = new LinkedList<>();
|
||||
observerLists = new HashMap<>();
|
||||
}
|
||||
|
||||
public EventEmitter(EventObserver obs) {
|
||||
public EventEmitter(EventObserver obs, Event e) {
|
||||
this();
|
||||
registerObserver(obs);
|
||||
registerObserver(obs, e);
|
||||
}
|
||||
|
||||
public final void registerObserver(EventObserver obs) {
|
||||
observers.add(obs);
|
||||
/**
|
||||
* Registers observer for specific event in the related list.
|
||||
*
|
||||
* @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) {
|
||||
observers.forEach(obs -> obs.onEvent(e));
|
||||
if (observerLists.containsKey(e)) {
|
||||
observerLists
|
||||
.get(e)
|
||||
.forEach(observer -> observer.onEvent(e));
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void timePasses(Weekday day);
|
||||
|
@ -31,8 +31,8 @@ public class KingsHand extends EventEmitter implements EventObserver {
|
||||
public KingsHand() {
|
||||
}
|
||||
|
||||
public KingsHand(EventObserver obs) {
|
||||
super(obs);
|
||||
public KingsHand(EventObserver obs, Event e) {
|
||||
super(obs, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -42,6 +42,5 @@ public class KingsHand extends EventEmitter implements EventObserver {
|
||||
|
||||
@Override
|
||||
public void timePasses(Weekday day) {
|
||||
// NOP
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ public class LordBaelish extends EventEmitter {
|
||||
public LordBaelish() {
|
||||
}
|
||||
|
||||
public LordBaelish(EventObserver obs) {
|
||||
super(obs);
|
||||
public LordBaelish(EventObserver obs, Event e) {
|
||||
super(obs, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -23,16 +23,19 @@
|
||||
|
||||
package com.iluwatar.event.aggregator;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* LordVarys produces events.
|
||||
*/
|
||||
public class LordVarys extends EventEmitter {
|
||||
@Slf4j
|
||||
public class LordVarys extends EventEmitter implements EventObserver {
|
||||
|
||||
public LordVarys() {
|
||||
}
|
||||
|
||||
public LordVarys(EventObserver obs) {
|
||||
super(obs);
|
||||
public LordVarys(EventObserver obs, Event e) {
|
||||
super(obs, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -41,4 +44,10 @@ public class LordVarys extends EventEmitter {
|
||||
notifyObservers(Event.TRAITOR_DETECTED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onEvent(Event e) {
|
||||
notifyObservers(e);
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ public class Scout extends EventEmitter {
|
||||
public Scout() {
|
||||
}
|
||||
|
||||
public Scout(EventObserver obs) {
|
||||
super(obs);
|
||||
public Scout(EventObserver obs, Event e) {
|
||||
super(obs, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -40,5 +40,8 @@ public class Scout extends EventEmitter {
|
||||
if (day == Weekday.TUESDAY) {
|
||||
notifyObservers(Event.WARSHIPS_APPROACHING);
|
||||
}
|
||||
if (day == Weekday.WEDNESDAY) {
|
||||
notifyObservers(Event.WHITE_WALKERS_SIGHTED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -46,7 +47,7 @@ abstract class EventEmitterTest<E extends EventEmitter> {
|
||||
/**
|
||||
* Factory used to create a new instance of the test object with a default observer
|
||||
*/
|
||||
private final Function<EventObserver, E> factoryWithDefaultObserver;
|
||||
private final BiFunction<EventObserver, Event, E> factoryWithDefaultObserver;
|
||||
|
||||
/**
|
||||
* Factory used to create a new instance of the test object without passing a default observer
|
||||
@ -67,7 +68,7 @@ abstract class EventEmitterTest<E extends EventEmitter> {
|
||||
* Create a new event emitter test, using the given test object factories, special day and event
|
||||
*/
|
||||
EventEmitterTest(final Weekday specialDay, final Event event,
|
||||
final Function<EventObserver, E> factoryWithDefaultObserver,
|
||||
final BiFunction<EventObserver, Event, E> factoryWithDefaultObserver,
|
||||
final Supplier<E> factoryWithoutDefaultObserver) {
|
||||
|
||||
this.specialDay = specialDay;
|
||||
@ -129,8 +130,8 @@ abstract class EventEmitterTest<E extends EventEmitter> {
|
||||
final var observer2 = mock(EventObserver.class);
|
||||
|
||||
final var emitter = this.factoryWithoutDefaultObserver.get();
|
||||
emitter.registerObserver(observer1);
|
||||
emitter.registerObserver(observer2);
|
||||
emitter.registerObserver(observer1, event);
|
||||
emitter.registerObserver(observer2, event);
|
||||
|
||||
testAllDays(specialDay, event, emitter, observer1, observer2);
|
||||
}
|
||||
@ -146,9 +147,9 @@ abstract class EventEmitterTest<E extends EventEmitter> {
|
||||
final var observer1 = mock(EventObserver.class);
|
||||
final var observer2 = mock(EventObserver.class);
|
||||
|
||||
final var emitter = this.factoryWithDefaultObserver.apply(defaultObserver);
|
||||
emitter.registerObserver(observer1);
|
||||
emitter.registerObserver(observer2);
|
||||
final var emitter = this.factoryWithDefaultObserver.apply(defaultObserver, event);
|
||||
emitter.registerObserver(observer1, event);
|
||||
emitter.registerObserver(observer2, event);
|
||||
|
||||
testAllDays(specialDay, event, emitter, defaultObserver, observer1, observer2);
|
||||
}
|
||||
|
@ -55,7 +55,11 @@ class KingsHandTest extends EventEmitterTest<KingsHand> {
|
||||
@Test
|
||||
void testPassThrough() throws Exception {
|
||||
final var observer = mock(EventObserver.class);
|
||||
final var kingsHand = new KingsHand(observer);
|
||||
final var kingsHand = new KingsHand();
|
||||
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
|
||||
verifyZeroInteractions(observer);
|
||||
|
@ -34,7 +34,9 @@ class ScoutTest extends EventEmitterTest<Scout> {
|
||||
* Create a new test instance, using the correct object factory
|
||||
*/
|
||||
public ScoutTest() {
|
||||
super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new);
|
||||
|
||||
super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -96,9 +96,11 @@ public class NioReactor {
|
||||
* @throws IOException if any I/O error occurs.
|
||||
*/
|
||||
public void stop() throws InterruptedException, IOException {
|
||||
reactorMain.shutdownNow();
|
||||
reactorMain.shutdown();
|
||||
selector.wakeup();
|
||||
reactorMain.awaitTermination(4, TimeUnit.SECONDS);
|
||||
if (!reactorMain.awaitTermination(4, TimeUnit.SECONDS)) {
|
||||
reactorMain.shutdownNow();
|
||||
}
|
||||
selector.close();
|
||||
LOGGER.info("Reactor stopped");
|
||||
}
|
||||
|
@ -64,6 +64,8 @@ public class ThreadPoolDispatcher implements Dispatcher {
|
||||
@Override
|
||||
public void stop() throws InterruptedException {
|
||||
executorService.shutdown();
|
||||
executorService.awaitTermination(4, TimeUnit.SECONDS);
|
||||
if (executorService.awaitTermination(4, TimeUnit.SECONDS)) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,11 +18,11 @@ threads and eliminating the latency of creating new threads.
|
||||
|
||||
## 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
|
||||
> and serve mighty amount of coffee cups. Creating a new thread for each task would be a waste so we
|
||||
> establish a thread pool.
|
||||
> and serve a mighty amount of coffee cups. Creating a new thread for each task would be a waste so
|
||||
> we establish a thread pool.
|
||||
|
||||
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.
|
||||
|
||||
```java
|
||||
|
@ -16,10 +16,12 @@ Ensure that a given client is not able to access service resources more than the
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
Real-world example
|
||||
|
||||
> A large multinational corporation offers API to its customers. The API is rate-limited and each
|
||||
> customer can only make certain amount of calls per second.
|
||||
> A young human and an old dwarf walk into a bar. They start ordering beers from the bartender.
|
||||
> The bartender immediately sees that the young human shouldn't consume too many drinks too fast
|
||||
> and refuses to serve if enough time has not passed. For the old dwarf, the serving rate can
|
||||
> be higher.
|
||||
|
||||
In plain words
|
||||
|
||||
@ -33,30 +35,25 @@ In plain words
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Tenant class presents the clients of the API. CallsCount tracks the number of API calls per tenant.
|
||||
`BarCustomer` class presents the clients of the `Bartender` API. `CallsCount` tracks the number of
|
||||
calls per `BarCustomer`.
|
||||
|
||||
```java
|
||||
public class Tenant {
|
||||
public class BarCustomer {
|
||||
|
||||
private final String name;
|
||||
private final int allowedCallsPerSecond;
|
||||
@Getter
|
||||
private final String name;
|
||||
@Getter
|
||||
private final int allowedCallsPerSecond;
|
||||
|
||||
public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
if (allowedCallsPerSecond < 0) {
|
||||
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
||||
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
if (allowedCallsPerSecond < 0) {
|
||||
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
||||
}
|
||||
this.name = name;
|
||||
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
||||
callsCount.addTenant(name);
|
||||
}
|
||||
this.name = name;
|
||||
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
||||
callsCount.addTenant(name);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getAllowedCallsPerSecond() {
|
||||
return allowedCallsPerSecond;
|
||||
}
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
@ -76,14 +73,14 @@ public final class CallsCount {
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
LOGGER.debug("Resetting the map.");
|
||||
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
|
||||
LOGGER.info("reset counters");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Next we introduce the service that the tenants are calling. To track the call count we use the
|
||||
throttler timer.
|
||||
Next, the service that the tenants are calling is introduced. To track the call count, a throttler
|
||||
timer is used.
|
||||
|
||||
```java
|
||||
public interface Throttler {
|
||||
@ -111,71 +108,103 @@ public class ThrottleTimerImpl implements Throttler {
|
||||
}, 0, throttlePeriod);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
class B2BService {
|
||||
`Bartender` offers the `orderDrink` service to the `BarCustomer`s. The customers probably don't
|
||||
know that the beer serving rate is limited by their appearances.
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
|
||||
private final CallsCount callsCount;
|
||||
```java
|
||||
class Bartender {
|
||||
|
||||
public B2BService(Throttler timer, CallsCount callsCount) {
|
||||
this.callsCount = callsCount;
|
||||
timer.start();
|
||||
}
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class);
|
||||
private final CallsCount callsCount;
|
||||
|
||||
public int dummyCustomerApi(Tenant tenant) {
|
||||
var tenantName = tenant.getName();
|
||||
var count = callsCount.getCount(tenantName);
|
||||
LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
|
||||
if (count >= tenant.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("API access per second limit reached for: {}", tenantName);
|
||||
return -1;
|
||||
public Bartender(Throttler timer, CallsCount callsCount) {
|
||||
this.callsCount = callsCount;
|
||||
timer.start();
|
||||
}
|
||||
callsCount.incrementCount(tenantName);
|
||||
return getRandomCustomerId();
|
||||
}
|
||||
|
||||
private int getRandomCustomerId() {
|
||||
return ThreadLocalRandom.current().nextInt(1, 10000);
|
||||
}
|
||||
public int orderDrink(BarCustomer barCustomer) {
|
||||
var tenantName = barCustomer.getName();
|
||||
var count = callsCount.getCount(tenantName);
|
||||
if (count >= barCustomer.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName);
|
||||
return -1;
|
||||
}
|
||||
callsCount.incrementCount(tenantName);
|
||||
LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count+1);
|
||||
return getRandomCustomerId();
|
||||
}
|
||||
|
||||
private int getRandomCustomerId() {
|
||||
return ThreadLocalRandom.current().nextInt(1, 10000);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now we are ready to see the full example in action. Tenant Adidas is rate-limited to 5 calls per
|
||||
second and Nike to 6.
|
||||
Now it is possible to see the full example in action. `BarCustomer` young human is rate-limited to 2
|
||||
calls per second and the old dwarf to 4.
|
||||
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
public static void main(String[] args) {
|
||||
var callsCount = new CallsCount();
|
||||
var adidas = new Tenant("Adidas", 5, callsCount);
|
||||
var nike = new Tenant("Nike", 6, callsCount);
|
||||
var human = new BarCustomer("young human", 2, callsCount);
|
||||
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount);
|
||||
|
||||
var executorService = Executors.newFixedThreadPool(2);
|
||||
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(nike, callsCount));
|
||||
executorService.shutdown();
|
||||
|
||||
try {
|
||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Executor Service terminated: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(10, callsCount);
|
||||
var service = new B2BService(timer, callsCount);
|
||||
executorService.execute(() -> makeServiceCalls(human, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
|
||||
|
||||
executorService.shutdown();
|
||||
try {
|
||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Executor service terminated: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(1000, callsCount);
|
||||
var service = new Bartender(timer, callsCount);
|
||||
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
|
||||
IntStream.range(0, 20).forEach(i -> {
|
||||
service.dummyCustomerApi(tenant);
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
||||
}
|
||||
IntStream.range(0, 50).forEach(i -> {
|
||||
service.orderDrink(barCustomer);
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
@ -185,7 +214,7 @@ second and Nike to 6.
|
||||
|
||||
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 service access needs to be restricted not to have high impact 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.
|
||||
|
||||
## Credits
|
||||
|
@ -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
|
||||
* and meet service level agreements, even when an increase in demand places load on resources.
|
||||
* <p>
|
||||
* In this example we have ({@link App}) as the initiating point of the service. This is a time
|
||||
* In this example there is a {@link Bartender} serving beer to {@link BarCustomer}s. This is a time
|
||||
* based throttling, i.e. only a certain number of calls are allowed per second.
|
||||
* </p>
|
||||
* ({@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.
|
||||
* ({@link BarCustomer}) is the service tenant class having a name and the number of calls allowed.
|
||||
* ({@link Bartender}) is the service which is consumed by the tenants and is throttled.
|
||||
*/
|
||||
@Slf4j
|
||||
public class App {
|
||||
@ -50,33 +50,35 @@ public class App {
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
var callsCount = new CallsCount();
|
||||
var adidas = new Tenant("Adidas", 5, callsCount);
|
||||
var nike = new Tenant("Nike", 6, callsCount);
|
||||
var human = new BarCustomer("young human", 2, callsCount);
|
||||
var dwarf = new BarCustomer("dwarf soldier", 4, callsCount);
|
||||
|
||||
var executorService = Executors.newFixedThreadPool(2);
|
||||
|
||||
executorService.execute(() -> makeServiceCalls(adidas, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(nike, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(human, callsCount));
|
||||
executorService.execute(() -> makeServiceCalls(dwarf, callsCount));
|
||||
|
||||
executorService.shutdown();
|
||||
try {
|
||||
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Executor Service terminated: {}", e.getMessage());
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make calls to the B2BService dummy API.
|
||||
* Make calls to the bartender.
|
||||
*/
|
||||
private static void makeServiceCalls(Tenant tenant, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(10, callsCount);
|
||||
var service = new B2BService(timer, callsCount);
|
||||
private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) {
|
||||
var timer = new ThrottleTimerImpl(1000, callsCount);
|
||||
var service = new Bartender(timer, callsCount);
|
||||
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
|
||||
IntStream.range(0, 20).forEach(i -> {
|
||||
service.dummyCustomerApi(tenant);
|
||||
IntStream.range(0, 50).forEach(i -> {
|
||||
service.orderDrink(barCustomer);
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Thread interrupted: {}", e.getMessage());
|
||||
}
|
||||
|
@ -25,22 +25,26 @@ package com.iluwatar.throttling;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
|
||||
/**
|
||||
* A Pojo class to create a basic Tenant with the allowed calls per second.
|
||||
*/
|
||||
public class Tenant {
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* BarCustomer is a tenant with a name and a number of allowed calls per second.
|
||||
*/
|
||||
public class BarCustomer {
|
||||
|
||||
@Getter
|
||||
private final String name;
|
||||
@Getter
|
||||
private final int allowedCallsPerSecond;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param name Name of the tenant
|
||||
* @param allowedCallsPerSecond The number of calls allowed for a particular tenant.
|
||||
* @param name Name of the BarCustomer
|
||||
* @param allowedCallsPerSecond The number of calls allowed for this particular tenant.
|
||||
* @throws InvalidParameterException If number of calls is less than 0, throws exception.
|
||||
*/
|
||||
public Tenant(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
public BarCustomer(String name, int allowedCallsPerSecond, CallsCount callsCount) {
|
||||
if (allowedCallsPerSecond < 0) {
|
||||
throw new InvalidParameterException("Number of calls less than 0 not allowed");
|
||||
}
|
||||
@ -48,12 +52,4 @@ public class Tenant {
|
||||
this.allowedCallsPerSecond = allowedCallsPerSecond;
|
||||
callsCount.addTenant(name);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getAllowedCallsPerSecond() {
|
||||
return allowedCallsPerSecond;
|
||||
}
|
||||
}
|
@ -29,33 +29,32 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A service which accepts a tenant and throttles the resource based on the time given to the
|
||||
* tenant.
|
||||
* Bartender is a service which accepts a BarCustomer (tenant) and throttles
|
||||
* the resource based on the time given to the tenant.
|
||||
*/
|
||||
class B2BService {
|
||||
class Bartender {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(B2BService.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Bartender.class);
|
||||
private final CallsCount callsCount;
|
||||
|
||||
public B2BService(Throttler timer, CallsCount callsCount) {
|
||||
public Bartender(Throttler timer, CallsCount callsCount) {
|
||||
this.callsCount = callsCount;
|
||||
timer.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls dummy customer api.
|
||||
*
|
||||
* Orders a drink from the bartender.
|
||||
* @return customer id which is randomly generated
|
||||
*/
|
||||
public int dummyCustomerApi(Tenant tenant) {
|
||||
var tenantName = tenant.getName();
|
||||
public int orderDrink(BarCustomer barCustomer) {
|
||||
var tenantName = barCustomer.getName();
|
||||
var count = callsCount.getCount(tenantName);
|
||||
LOGGER.debug("Counter for {} : {} ", tenant.getName(), count);
|
||||
if (count >= tenant.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("API access per second limit reached for: {}", tenantName);
|
||||
if (count >= barCustomer.getAllowedCallsPerSecond()) {
|
||||
LOGGER.error("I'm sorry {}, you've had enough for today!", tenantName);
|
||||
return -1;
|
||||
}
|
||||
callsCount.incrementCount(tenantName);
|
||||
LOGGER.debug("Serving beer to {} : [{} consumed] ", barCustomer.getName(), count + 1);
|
||||
return getRandomCustomerId();
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ public final class CallsCount {
|
||||
* Resets the count of all the tenants in the map.
|
||||
*/
|
||||
public void reset() {
|
||||
LOGGER.debug("Resetting the map.");
|
||||
tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0));
|
||||
LOGGER.info("reset counters");
|
||||
}
|
||||
}
|
||||
|
@ -23,20 +23,21 @@
|
||||
|
||||
package com.iluwatar.throttling;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
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.
|
||||
*/
|
||||
public class TenantTest {
|
||||
public class BarCustomerTest {
|
||||
|
||||
@Test
|
||||
void constructorTest() {
|
||||
assertThrows(InvalidParameterException.class, () -> {
|
||||
new Tenant("FailTenant", -1, new CallsCount());
|
||||
new BarCustomer("sirBrave", -1, new CallsCount());
|
||||
});
|
||||
}
|
||||
}
|
@ -32,19 +32,18 @@ import org.junit.jupiter.api.Test;
|
||||
/**
|
||||
* B2BServiceTest class to test the B2BService
|
||||
*/
|
||||
public class B2BServiceTest {
|
||||
public class BartenderTest {
|
||||
|
||||
private final CallsCount callsCount = new CallsCount();
|
||||
|
||||
@Test
|
||||
void dummyCustomerApiTest() {
|
||||
var tenant = new Tenant("testTenant", 2, callsCount);
|
||||
var tenant = new BarCustomer("pirate", 2, callsCount);
|
||||
// In order to assure that throttling limits will not be reset, we use an empty throttling implementation
|
||||
var timer = (Throttler) () -> {
|
||||
};
|
||||
var service = new B2BService(timer, callsCount);
|
||||
var timer = (Throttler) () -> {};
|
||||
var service = new Bartender(timer, callsCount);
|
||||
|
||||
IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::dummyCustomerApi);
|
||||
IntStream.range(0, 5).mapToObj(i -> tenant).forEach(service::orderDrink);
|
||||
var counter = callsCount.getCount(tenant.getName());
|
||||
assertEquals(2, counter, "Counter limit must be reached");
|
||||
}
|
Reference in New Issue
Block a user