@@ -16,10 +16,12 @@ Ensure that a given client is not able to access service resources more than the
16
16
17
17
## Explanation
18
18
19
- Real world example
19
+ Real- world example
20
20
21
- > A large multinational corporation offers API to its customers. The API is rate-limited and each
22
- > customer can only make certain amount of calls per second.
21
+ > A young human and an old dwarf walk into a bar. They start ordering beers from the bartender.
22
+ > The bartender immediately sees that the young human shouldn't consume too many drinks too fast
23
+ > and refuses to serve if enough time has not passed. For the old dwarf, the serving rate can
24
+ > be higher.
23
25
24
26
In plain words
25
27
@@ -33,30 +35,25 @@ In plain words
33
35
34
36
** Programmatic Example**
35
37
36
- Tenant class presents the clients of the API. CallsCount tracks the number of API calls per tenant.
38
+ ` BarCustomer ` class presents the clients of the ` Bartender ` API. ` CallsCount ` tracks the number of
39
+ calls per ` BarCustomer ` .
37
40
38
41
``` java
39
- public class Tenant {
40
-
41
- private final String name;
42
- private final int allowedCallsPerSecond;
43
-
44
- public Tenant (String name , int allowedCallsPerSecond , CallsCount callsCount ) {
45
- if (allowedCallsPerSecond < 0 ) {
46
- throw new InvalidParameterException (" Number of calls less than 0 not allowed" );
42
+ public class BarCustomer {
43
+
44
+ @Getter
45
+ private final String name;
46
+ @Getter
47
+ private final int allowedCallsPerSecond;
48
+
49
+ public BarCustomer (String name , int allowedCallsPerSecond , CallsCount callsCount ) {
50
+ if (allowedCallsPerSecond < 0 ) {
51
+ throw new InvalidParameterException (" Number of calls less than 0 not allowed" );
52
+ }
53
+ this . name = name;
54
+ this . allowedCallsPerSecond = allowedCallsPerSecond;
55
+ callsCount. addTenant(name);
47
56
}
48
- this . name = name;
49
- this . allowedCallsPerSecond = allowedCallsPerSecond;
50
- callsCount. addTenant(name);
51
- }
52
-
53
- public String getName () {
54
- return name;
55
- }
56
-
57
- public int getAllowedCallsPerSecond () {
58
- return allowedCallsPerSecond;
59
- }
60
57
}
61
58
62
59
@Slf4j
@@ -76,14 +73,14 @@ public final class CallsCount {
76
73
}
77
74
78
75
public void reset () {
79
- LOGGER . debug(" Resetting the map." );
80
76
tenantCallsCount. replaceAll((k, v) - > new AtomicLong (0 ));
77
+ LOGGER . info(" reset counters" );
81
78
}
82
79
}
83
80
```
84
81
85
- Next we introduce the service that the tenants are calling. To track the call count we use the
86
- throttler timer.
82
+ Next, the service that the tenants are calling is introduced . To track the call count, a throttler
83
+ timer is used .
87
84
88
85
``` java
89
86
public interface Throttler {
@@ -111,71 +108,103 @@ public class ThrottleTimerImpl implements Throttler {
111
108
}, 0 , throttlePeriod);
112
109
}
113
110
}
111
+ ```
114
112
115
- class B2BService {
113
+ ` Bartender ` offers the ` orderDrink ` service to the ` BarCustomer ` s. The customers probably don't
114
+ know that the beer serving rate is limited by their appearances.
116
115
117
- private static final Logger LOGGER = LoggerFactory . getLogger( B2BService . class);
118
- private final CallsCount callsCount;
116
+ ``` java
117
+ class Bartender {
119
118
120
- public B2BService (Throttler timer , CallsCount callsCount ) {
121
- this . callsCount = callsCount;
122
- timer. start();
123
- }
119
+ private static final Logger LOGGER = LoggerFactory . getLogger(Bartender . class);
120
+ private final CallsCount callsCount;
124
121
125
- public int dummyCustomerApi (Tenant tenant ) {
126
- var tenantName = tenant. getName();
127
- var count = callsCount. getCount(tenantName);
128
- LOGGER . debug(" Counter for {} : {} " , tenant. getName(), count);
129
- if (count >= tenant. getAllowedCallsPerSecond()) {
130
- LOGGER . error(" API access per second limit reached for: {}" , tenantName);
131
- return - 1 ;
122
+ public Bartender (Throttler timer , CallsCount callsCount ) {
123
+ this . callsCount = callsCount;
124
+ timer. start();
132
125
}
133
- callsCount. incrementCount(tenantName);
134
- return getRandomCustomerId();
135
- }
136
126
137
- private int getRandomCustomerId () {
138
- return ThreadLocalRandom . current(). nextInt(1 , 10000 );
139
- }
127
+ public int orderDrink (BarCustomer barCustomer ) {
128
+ var tenantName = barCustomer. getName();
129
+ var count = callsCount. getCount(tenantName);
130
+ if (count >= barCustomer. getAllowedCallsPerSecond()) {
131
+ LOGGER . error(" I'm sorry {}, you've had enough for today!" , tenantName);
132
+ return - 1 ;
133
+ }
134
+ callsCount. incrementCount(tenantName);
135
+ LOGGER . debug(" Serving beer to {} : [{} consumed] " , barCustomer. getName(), count+ 1 );
136
+ return getRandomCustomerId();
137
+ }
138
+
139
+ private int getRandomCustomerId () {
140
+ return ThreadLocalRandom . current(). nextInt(1 , 10000 );
141
+ }
140
142
}
141
143
```
142
144
143
- Now we are ready to see the full example in action. Tenant Adidas is rate-limited to 5 calls per
144
- second and Nike to 6 .
145
+ Now it is possible to see the full example in action. ` BarCustomer ` young human is rate-limited to 2
146
+ calls per second and the old dwarf to 4 .
145
147
146
148
``` java
147
- public static void main(String [] args) {
149
+ public static void main(String [] args) {
148
150
var callsCount = new CallsCount ();
149
- var adidas = new Tenant ( " Adidas " , 5 , callsCount);
150
- var nike = new Tenant ( " Nike " , 6 , callsCount);
151
+ var human = new BarCustomer ( " young human " , 2 , callsCount);
152
+ var dwarf = new BarCustomer ( " dwarf soldier " , 4 , callsCount);
151
153
152
154
var executorService = Executors . newFixedThreadPool(2 );
153
- executorService. execute(() - > makeServiceCalls(adidas, callsCount));
154
- executorService. execute(() - > makeServiceCalls(nike, callsCount));
155
+
156
+ executorService. execute(() - > makeServiceCalls(human, callsCount));
157
+ executorService. execute(() - > makeServiceCalls(dwarf, callsCount));
158
+
155
159
executorService. shutdown();
156
-
157
160
try {
158
- executorService. awaitTermination(10 , TimeUnit . SECONDS );
161
+ executorService. awaitTermination(10 , TimeUnit . SECONDS );
159
162
} catch (InterruptedException e) {
160
- LOGGER . error(" Executor Service terminated: {}" , e. getMessage());
163
+ LOGGER . error(" Executor service terminated: {}" , e. getMessage());
161
164
}
162
- }
165
+ }
163
166
164
- private static void makeServiceCalls(Tenant tenant , CallsCount callsCount) {
165
- var timer = new ThrottleTimerImpl (10 , callsCount);
166
- var service = new B2BService (timer, callsCount);
167
+ private static void makeServiceCalls(BarCustomer barCustomer , CallsCount callsCount) {
168
+ var timer = new ThrottleTimerImpl (1000 , callsCount);
169
+ var service = new Bartender (timer, callsCount);
167
170
// Sleep is introduced to keep the output in check and easy to view and analyze the results.
168
- IntStream . range(0 , 20 ). forEach(i - > {
169
- service. dummyCustomerApi(tenant );
170
- try {
171
- Thread . sleep(1 );
172
- } catch (InterruptedException e) {
173
- LOGGER . error(" Thread interrupted: {}" , e. getMessage());
174
- }
171
+ IntStream . range(0 , 50 ). forEach(i - > {
172
+ service. orderDrink(barCustomer );
173
+ try {
174
+ Thread . sleep(100 );
175
+ } catch (InterruptedException e) {
176
+ LOGGER . error(" Thread interrupted: {}" , e. getMessage());
177
+ }
175
178
});
176
- }
179
+ }
177
180
```
178
181
182
+ An excerpt from the example's console output:
183
+
184
+ ```
185
+ 18:46:36.218 [Timer-0] INFO com.iluwatar.throttling.CallsCount - reset counters
186
+ 18:46:36.218 [Timer-1] INFO com.iluwatar.throttling.CallsCount - reset counters
187
+ 18:46:36.242 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [1 consumed]
188
+ 18:46:36.242 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [1 consumed]
189
+ 18:46:36.342 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [2 consumed]
190
+ 18:46:36.342 [pool-1-thread-1] DEBUG com.iluwatar.throttling.Bartender - Serving beer to young human : [2 consumed]
191
+ 18:46:36.443 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
192
+ 18:46:36.443 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [3 consumed]
193
+ 18:46:36.544 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
194
+ 18:46:36.544 [pool-1-thread-2] DEBUG com.iluwatar.throttling.Bartender - Serving beer to dwarf soldier : [4 consumed]
195
+ 18:46:36.645 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
196
+ 18:46:36.645 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
197
+ 18:46:36.745 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
198
+ 18:46:36.745 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
199
+ 18:46:36.846 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
200
+ 18:46:36.846 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
201
+ 18:46:36.947 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
202
+ 18:46:36.947 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
203
+ 18:46:37.048 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
204
+ 18:46:37.048 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
205
+ 18:46:37.148 [pool-1-thread-1] ERROR com.iluwatar.throttling.Bartender - I'm sorry young human, you've had enough for today!
206
+ 18:46:37.148 [pool-1-thread-2] ERROR com.iluwatar.throttling.Bartender - I'm sorry dwarf soldier, you've had enough for today!
207
+ ```
179
208
180
209
## Class diagram
181
210
@@ -185,7 +214,7 @@ second and Nike to 6.
185
214
186
215
The Throttling pattern should be used:
187
216
188
- * When a service access needs to be restricted to not have high impacts on the performance of the service.
217
+ * When service access needs to be restricted not to have high impact on the performance of the service.
189
218
* When multiple clients are consuming the same service resources and restriction has to be made according to the usage per client.
190
219
191
220
## Credits
0 commit comments