The Fantasy Bookstore is a tiny SpringBoot application outlining usage of different MongoDB Data Models for atomic operations. The Bookstore refers to and extends the Model Data for Atomic Operations example.
Use Application Profiles to activate the different approaches.
Profile | Description |
---|---|
sa | Synchronous Atomic Operations with denormalized Data Model |
stxn | Synchronous Multi Document Transactions using just the native MongoClient |
stx | Synchronous Spring managed Multi Document Transactions |
rtx | Reactive Multi Document Transactions |
rcs | Active this profile along with one of the transactional (stx, rtx) ones to subscribe to changes on the order collection. |
retry | Activate this profile to retry failed transactions via Spring Retry. |
reset | Reset the initial set of collections and pre fill it with test data |
Web Endpoints
URL | Sample | Description |
---|---|---|
GET :8080/books | http :8080/books |
List all books. |
GET :8080/book/{book} | http :8080/book/bb4e114f |
A single Book. |
POST :8080/book/{book}/order?customer= | http POST :8080/book/bb4e114f/order?customer=christoph |
Place an order for a book. |
The denormalized Data Model keeps all required data in one place, the Document. Hence all updates of the one document are atomic.
Each and every Order
is tracked by checkout
within the Book
itself.
{
"_id" : "bb4e114f",
"title" : "The Painted Man",
"author" : [ "Peter V. Brett" ],
"published_date" : "2009-08-01",
"pages" : 544,
"language" : "English",
"publisher_id" : "Harper Collins Publishers",
"available" : 3,
"checkout" : [ { "by": "cstrobl", "date" : "2018-08-27T10:11:59.853Z" } ]
}
Spring Profile: sa
MongoDB Collections: books
Components: AtomicOrderService, SyncBookstoreHandler
The transactional approach splits data between Book
and Order
whereas the Order
references the Book
via a DBRef
.
Still the number of available copies is kept within the books
collection.
{
"_id" : "bb4e114f",
"title" : "The Painted Man",
"author" : [ "Peter V. Brett" ],
"published_date" : "2009-08-01",
"pages" : 544,
"language" : "English",
"publisher_id" : "Harper Collins Publishers",
"available" : 3
}
{
"by" : "cstrobl",
"date" : "2018-08-27T10:11:59.853Z",
"books" : [ { "$ref" : "books", "$id" : "bb4e114f" } ]
}
Spring Profile: stx
MongoDB Collections: books, order
Components: TransactionalOrderService, SyncBookstoreHandler
Just as in the sample above data is split between Book
and Order
whereas the Order
references the Book
via a DBRef
.
However, here we're using the native operations of MongoClient
and ClientSession
to run the transaction.
Spring Profile: stxn
MongoDB Collections: books, order
Components: NativeMongoTransactionalOrderService, SyncBookstoreHandler
Just as the synchronous transactional approach data is split between Book
and Order
, this time using a reactive API
for processing the checkout inside a transaction.
{
"_id" : "bb4e114f",
"title" : "The Painted Man",
"author" : [ "Peter V. Brett" ],
"published_date" : "2009-08-01",
"pages" : 544,
"language" : "English",
"publisher_id" : "Harper Collins Publishers",
"available" : 3
}
{
"by" : "cstrobl",
"date" : "2018-08-27T10:11:59.853Z",
"books" : [ { "$ref" : "books", "$id" : "bb4e114f" } ]
}
Spring Profile: rtx
MongoDB Collections: books, order
Components: ReactiveOrderService, ReactiveBookstoreHandler
It may happen that multiple transactions try to alter the very same document which leads to write conflicts and therefore the abortion of the transaction.
This scenario can be provoked by:
- Start the application
- Open Mongo Shell and execute
rs0:PRIMARY> session = db.getMongo().startSession({ "mode" : "primary" });
rs0:PRIMARY> session.startTransaction();
rs0:PRIMARY> session.getDatabase("fantasy-bookstore").books.update({ "_id" : "f430cb49", "available" : { "$gt" : 0 } }, { "$inc" : { "available" : -1 } });
- Call the order endpoint
~ $ http POST :8080/book/f430cb49/order?customer=cstrobl
- The Application will retry the operation for 3 times with 5 seconds delay in between.
- Switch to Mongo Shell again and commit the transaction within time to have the other one succeed as well.
rs0:PRIMARY> session.commitTransaction();
Spring Profile: stx,retry
MongoDB Collections: books, order
Components: TransactionalOrderService, SyncBookstoreHandler, RetryTemplate
The application requires Java 8 (or better).
The Application requires MongoDB 4.0 or better running as Replica Set.
Some of the code uses Project Lombok. Make sure to have it.