You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit cd7ff04
Browse filesBrowse the repository at this point in the historyBrowse files
fix: reserve stock before charging and validate order quantity
The order flow is now reserve, charge, release on failure. Inventory rejects non-positive quantities and its errors are surfaced, the access policy runs before the registry lookup, the statelessness and interoperability wording is corrected, and confirmations carry the customer id. The class diagram is a rendered PNG.
Copy file name to clipboardExpand all lines: service-oriented-architecture/README.md
+51-25Lines changed: 51 additions & 25 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -49,7 +49,9 @@ flowchart LR
49
49
Order -.->|orchestrates via bus| Bus
50
50
```
51
51
52
-
Because every service is stateless between calls, each one can be scaled independently. In the example the in-memory maps stand in for each service's own datastore, and the bus stands in for the network protocol (SOAP, REST or messaging) that would carry the messages in a deployed system.
52
+
Because no service keeps per-conversation session state, each one can be scaled independently; the state a service does own is shared between all its callers and guarded for concurrent access. In the example the in-memory maps stand in for each service's own datastore, and the bus stands in for the network protocol (SOAP, REST or messaging) that would carry the messages in a deployed system.
53
+
54
+

53
55
54
56
## Programmatic Example of Service-Oriented Architecture Pattern in Java
55
57
@@ -64,7 +66,7 @@ public interface Service {
64
66
}
65
67
```
66
68
67
-
Messages are coarse-grained and self-describing. The payload is plain data, which is what makes the contract interoperable: the same request could travel as SOAP, JSON or any other wire format.
69
+
Messages are coarse-grained and self-describing. The request payload is plain data, which is what makes the contract interoperable: the same request could travel as SOAP, JSON or any other wire format. Responses in this demo carry typed Java objects for brevity; a deployed system would describe them as plain data too.
68
70
69
71
```java
70
72
public record ServiceRequest(
@@ -102,7 +104,7 @@ public class ServiceRegistry {
102
104
}
103
105
```
104
106
105
-
The bus is the communication backbone. It resolves the target through the registry, applies cross-cutting concerns in a single place (access control, tracing, timing)and turns provider failures into error responses so that consumers never see provider exceptions.
107
+
The bus is the communication backbone. It applies cross-cutting concerns in a single place (access control, tracing, timing), resolves the target through the registry and turns provider failures into error responses so that consumers never see provider exceptions. The access check runs before the lookup, so a caller that may not reach a service learns nothing about what the registry holds.
@@ -160,7 +162,7 @@ var denied = bus.send(new ServiceRequest("payment", "charge",
160
162
// denied = ServiceResponse[success=false, body=null, message=Access denied to payment]
161
163
```
162
164
163
-
Each enterprise service owns exactly one business capability and is stateless between calls. Operations are dispatched with a switch expression on the operation name.
165
+
Each enterprise service owns exactly one business capability and keeps no per-conversation session state; the state it does own is private to it and guarded for concurrent access. Operations are dispatched with a switch expression on the operation name.
164
166
165
167
```java
166
168
publicclassCustomerServiceimplementsService {
@@ -177,10 +179,12 @@ public class CustomerService implements Service {
177
179
}
178
180
```
179
181
180
-
`InventoryService` answers `checkStock`and `reserve`, and `PaymentService` answers `charge` in the same style.
182
+
`InventoryService` answers `checkStock`, `reserve`and `release`, and `PaymentService` answers `charge` in the same style. Both reject a non-positive quantity or amount, because a service validates its own input rather than trusting its callers.
181
183
182
184
The composite `OrderService` is where SOA shows its strength. It implements the same contract as the others, but delivers a higher level capability by orchestrating the lower level services through the bus. It depends only on service names and message contracts, never on the provider classes, and it forwards the caller's credential so the bus can authorise every downstream call.
183
185
186
+
No transaction spans the services, so the order service compensates instead: it reserves the stock before it charges the customer, and releases the reservation again when the charge fails. Doing it the other way round would leave a customer charged for an order that was never reserved.
187
+
184
188
```java
185
189
@RequiredArgsConstructor
186
190
publicclassOrderServiceimplementsService {
@@ -199,22 +203,30 @@ public class OrderService implements Service {
199
203
}
200
204
var stock = bus.send(newServiceRequest(INVENTORY_SERVICE, "checkStock",
The application wires everything together behind a bus that protects the payment service and sends four requests: an order with a valid credential that succeeds, an order that fails because of stock, a request for a service nobody registered, and an anonymous order that the bus stops at the payment step.
229
+
The application wires everything together behind a bus that protects the payment service and sends four requests: an order with a valid credential that succeeds, an order that fails because of stock, a request for a service nobody registered, and an anonymous order that the bus stops at the payment step, after which the order service releases the stock it had already reserved.
218
230
219
231
```java
220
232
var registry =newServiceRegistry();
@@ -239,36 +251,50 @@ var denied = bus.send(new ServiceRequest(OrderService.NAME, "placeOrder",
239
251
Running the program produces output similar to this:
240
252
241
253
```
254
+
Bootstrapping the service registry and the service bus
0 commit comments