Skip to content

Commit 5bd0fd4

Browse files
committed
feat: demonstrate a credit limit change flowing through the replica
The demo never exercised changeCreditLimit, so the claim that the replica is enough to enforce business rules was only shown as a rejection. Add a fifth step: the customer service comes back online, raises the credit limit, and the order rejected in step 4 is accepted from the replicated state alone. Add restart() to CustomerService so the outage can end, make CustomerReplica use a LinkedHashMap like CustomerService for deterministic log order, cover the new behaviour in CustomerServiceTest and OrderServiceTest, and refresh the README demo section, its program output, and the PlantUML diagram.
1 parent 14c9e5d commit 5bd0fd4

7 files changed

Lines changed: 60 additions & 5 deletions

File tree

‎event-carried-state-transfer/README.md‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ public Order placeOrder(String customerId, BigDecimal amount) {
172172

173173
6. **The demo**
174174

175-
`App` registers a customer and changes the address, takes the customer service offline and places an order from the replica, publishes a stale event that the replica ignores, and finally shows the replica enforcing the credit limit.
175+
`App` registers a customer and changes the address, takes the customer service offline and places an order from the replica, publishes a stale event that the replica ignores, and shows the replica enforcing the credit limit. It then brings the customer service back and raises that limit: the new limit travels inside the event, and the order that was just rejected is accepted from the replica alone, without a single call back to the producer.
176176

177177
```java
178178
var bus = new EventBus();
@@ -188,6 +188,10 @@ var order = orderService.placeOrder("C-1", new BigDecimal("120.00")); // succeed
188188

189189
bus.publish(new CustomerUpdatedEvent(99, Instant.now(), staleVersionOne)); // ignored
190190
tryToOrder(orderService, "C-1", new BigDecimal("900.00")); // rejected, above the replicated limit
191+
192+
customerService.restart();
193+
customerService.changeCreditLimit("C-1", new BigDecimal("1500.00")); // the event carries the new limit
194+
tryToOrder(orderService, "C-1", new BigDecimal("900.00")); // accepted now, still only from the replica
191195
```
192196

193197
Program output:
@@ -217,6 +221,15 @@ INFO App -- Order service replica: C-1 version 2 at '42 Ocean Avenue, Porto' wit
217221
INFO App -- --- Step 4: the replica is enough to enforce business rules ---
218222
WARN App -- Order rejected: Amount 900.00 exceeds credit limit 500.00 of C-1
219223
WARN App -- Order rejected: Unknown customer C-2 in replica
224+
INFO App -- --- Step 5: a new credit limit travels in the event and unblocks the rejected order ---
225+
INFO CustomerService -- Customer service is back online
226+
INFO CustomerService -- Customer C-1 gets a credit limit of 1500.00
227+
INFO CustomerService -- Publishing event 3 with the full state of C-1 (version 3)
228+
INFO EventBus -- Publishing CustomerUpdatedEvent to 1 subscriber(s)
229+
INFO CustomerReplica -- Replica updated from event 3: C-1 is now at version 3 with address '42 Ocean Avenue, Porto' and limit 1500.00
230+
INFO App -- Order service replica: C-1 version 3 at '42 Ocean Avenue, Porto' with limit 1500.00
231+
INFO OrderService -- Accepted ORD-2 for C-1 (900.00) shipping to '42 Ocean Avenue, Porto' using replica version 3
232+
INFO App -- Order ORD-2 accepted
220233
```
221234

222235
## Class Diagram

‎event-carried-state-transfer/etc/event-carried-state-transfer.urm.puml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ package com.iluwatar.eventcarriedstatetransfer {
4444
+ changeCreditLimit(customerId : String, newLimit : BigDecimal) : CustomerState
4545
+ findCustomer(customerId : String) : Optional<CustomerState>
4646
+ shutdown() : void
47+
+ restart() : void
4748
+ isOnline() : boolean
4849
}
4950
class CustomerReplica {

‎event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/App.java‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@
4141
*
4242
* <p>The demo registers a customer and changes the address, showing the replica following each
4343
* event. It then takes the customer service offline and places an order anyway, purely from the
44-
* replica. A stale event is published to show that the replica ignores it, and finally an order
45-
* above the replicated credit limit is rejected.
44+
* replica. A stale event is published to show that the replica ignores it, and an order above the
45+
* replicated credit limit is rejected. Finally the customer service comes back and raises that
46+
* credit limit: the new state travels in the event, and the order that was just rejected is
47+
* accepted from the replica alone.
4648
*/
4749
@Slf4j
4850
public class App {
@@ -86,6 +88,13 @@ public static void main(String[] args) {
8688
LOGGER.info("--- Step 4: the replica is enough to enforce business rules ---");
8789
tryToOrder(orderService, "C-1", new BigDecimal("900.00"));
8890
tryToOrder(orderService, "C-2", new BigDecimal("10.00"));
91+
92+
LOGGER.info(
93+
"--- Step 5: a new credit limit travels in the event and unblocks the rejected order ---");
94+
customerService.restart();
95+
customerService.changeCreditLimit("C-1", new BigDecimal("1500.00"));
96+
logReplica(orderService, "C-1");
97+
tryToOrder(orderService, "C-1", new BigDecimal("900.00"));
8998
}
9099

91100
/**

‎event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerReplica.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
*/
2525
package com.iluwatar.eventcarriedstatetransfer;
2626

27-
import java.util.HashMap;
27+
import java.util.LinkedHashMap;
2828
import java.util.Map;
2929
import java.util.Optional;
3030
import lombok.extern.slf4j.Slf4j;
@@ -41,7 +41,7 @@
4141
@Slf4j
4242
public class CustomerReplica {
4343

44-
private final Map<String, CustomerState> customers = new HashMap<>();
44+
private final Map<String, CustomerState> customers = new LinkedHashMap<>();
4545

4646
/**
4747
* Applies an event to the replica.

‎event-carried-state-transfer/src/main/java/com/iluwatar/eventcarriedstatetransfer/CustomerService.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ public void shutdown() {
119119
LOGGER.warn("Customer service is going offline");
120120
}
121121

122+
/** Ends the outage: direct queries are answered again. */
123+
public void restart() {
124+
online = true;
125+
LOGGER.info("Customer service is back online");
126+
}
127+
122128
/** Whether direct queries are currently answered. */
123129
public boolean isOnline() {
124130
return online;

‎event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/CustomerServiceTest.java‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,15 @@ void refusesDirectLookupsWhenOffline() {
100100
var thrown = assertThrows(IllegalStateException.class, () -> service.findCustomer("C-1"));
101101
assertEquals("customer service is offline", thrown.getMessage());
102102
}
103+
104+
@Test
105+
void answersDirectLookupsAgainAfterARestart() {
106+
service.register("C-1", "Alice", "Lisbon", new BigDecimal("500.00"));
107+
service.shutdown();
108+
109+
service.restart();
110+
111+
assertTrue(service.isOnline());
112+
assertEquals("Alice", service.findCustomer("C-1").orElseThrow().name());
113+
}
103114
}

‎event-carried-state-transfer/src/test/java/com/iluwatar/eventcarriedstatetransfer/OrderServiceTest.java‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,21 @@ void rejectsOrdersAboveTheReplicatedCreditLimit() {
9191
assertTrue(thrown.getMessage().contains("exceeds credit limit"));
9292
}
9393

94+
@Test
95+
void acceptsOrdersOnceARaisedCreditLimitHasBeenReplicated() {
96+
customerService.register("C-1", "Alice", "Lisbon", new BigDecimal("500.00"));
97+
assertThrows(
98+
OrderRejectedException.class,
99+
() -> orderService.placeOrder("C-1", new BigDecimal("900.00")));
100+
101+
customerService.changeCreditLimit("C-1", new BigDecimal("1500.00"));
102+
103+
var order = orderService.placeOrder("C-1", new BigDecimal("900.00"));
104+
assertEquals(new BigDecimal("900.00"), order.amount());
105+
assertEquals(
106+
new BigDecimal("1500.00"), orderService.replica().find("C-1").orElseThrow().creditLimit());
107+
}
108+
94109
@Test
95110
void acceptsOrdersExactlyAtTheCreditLimitAndNumbersThemSequentially() {
96111
customerService.register("C-1", "Alice", "Lisbon", new BigDecimal("500.00"));

0 commit comments

Comments
 (0)