@@ -415,8 +415,8 @@ To make a distributed transaction atomic, we can split it into multiple
415
415
individually atomic transactions with the [ Saga] ( https://microservices.io/patterns/data/saga.html ) pattern.
416
416
Read more about the Saga pattern:
417
417
418
- - < https://learn.microsoft.com/en-us/azure/architecture/reference-architectures/saga/saga >
419
418
- < https://microservices.io/patterns/data/saga.html >
419
+ - < https://learn.microsoft.com/en-us/azure/architecture/reference-architectures/saga/saga >
420
420
421
421
To make individual transactions that compose a Saga atomic, we can use Saga's Semantic Locks with two other patterns:
422
422
[ Unit of Work] ( https://martinfowler.com/eaaCatalog/unitOfWork.html ) and
@@ -431,14 +431,23 @@ The Transactional Outbox ensures messages/events are reliably published to a mes
431
431
Using the Transactional Outbox, the message is first stored in the database as part of the Unit of Work's transaction,
432
432
and a separate process reads saved messages from the database and sends them to the message broker.
433
433
434
+ You can find a complete example of how to apply these patterns in another of my projects:
435
+ < https://github.com/filipsnastins/transactional-messaging-patterns-with-aws-dynamodb-streams-sns-sqs-lambda >
436
+
434
437
#### Applying Optimistic Locking to the Payments System Example
435
438
436
- ... TODO we're distributing the 'charge PaymentIntent' into two steps -
437
- request the charge (set the semantic lock) and perform the actual charge request.
439
+ To apply optimistic concurrency control to the Payments System,
440
+ we'll create the "charge ` PaymentIntent ` " Saga consisting of two separate atomic operations:
441
+
442
+ 1 . Request the charge by applying the semantic lock that sets the ` PaymentIntent ` to the ` CHARGE_REQUESTED ` state
443
+ and publish the ` PaymentIntentChargeRequested ` event as part of the Unit of Work transaction.
444
+
445
+ 2 . Listen to the ` PaymentIntentChargeRequested ` event, send the charge request to the Payment Gateway,
446
+ and set the ` PaymentIntent ` state to either ` CHARGED ` or ` CHARGE_FAILED ` .
438
447
439
448
``` mermaid
440
449
---
441
- title: PaymentIntent states updated with semantic lock
450
+ title: Updated PaymentIntent states with semantic lock
442
451
---
443
452
stateDiagram-v2
444
453
CHARGE_REQUESTED: CHARGE_REQUESTED (semantic lock)
@@ -453,6 +462,23 @@ stateDiagram-v2
453
462
CHARGE_FAILED --> [*]
454
463
```
455
464
465
+ The sequence diagram below simulates how optimistic concurrency control and semantic lock prevent concurrency anomalies
466
+ and enforce ` PaymentIntent ` object's invariants.
467
+
468
+ First, the user creates a new ` PaymentIntent ` and then sends the charge request. The charge request queries
469
+ the created ` PaymentIntent ` , checks that it's in the ` CREATED ` state, applies the semantic lock by
470
+ changing the state to ` CHARGE_REQUESTED ` , and writes the ` PaymentIntentChargeRequested ` event to the database.
471
+ The database writes are wrapped in an atomic DynamoDB transaction.
472
+
473
+ While the DynamoDB transaction was committing, the user sent a new request to change the ` PaymentIntent ` amount.
474
+ Since the previous transaction hasn't been committed yet, the ` PaymentIntent ` is still in the ` CHARGED ` state,
475
+ which allows changing the amount. While the application issued another DynamoDB transaction to change the ` PaymentIntent ` amount,
476
+ the first transaction that requested the charge was committed, incrementing optimistic lock's version number by one.
477
+ Therefore, the second transaction that changes the amount fails because its version number (` 0 ` )
478
+ doesn't match with the current version stored in the database (` 1 ` ).
479
+ The user can retry changing the amount, but the application will reject the request because
480
+ the ` PaymentIntent ` is now in the ` CHARGE_REQUESTED ` state that prevents changing the amount.
481
+
456
482
``` mermaid
457
483
sequenceDiagram
458
484
actor User
@@ -508,15 +534,6 @@ sequenceDiagram
508
534
deactivate PaymentIntent
509
535
```
510
536
511
- TODO
512
-
513
- - [ ] Incrementing version number
514
- - [ ] Semantic lock & transactional outbox for making charge request
515
- - [ ] When using optimistic locking, your business operation must be encapsulated in the unit of work
516
- - [ ] When using optimistic locking, the request must not issue any destructive operations outside of the
517
- transaction (unit of work), for example, making the charge request to the external Payment Gateway
518
- - [ ] Provides monotonic updates
519
-
520
537
#### Optimistic Locking with DynamoDB
521
538
522
539
TODO
0 commit comments