1

We are creating system using CQRS. Our projections are in mongodb. We are facing some cases. We have an event say OrderCreated. We need to produce a sequential order_no for example #3, #4 etc. We could use a projection and keep a sequence in table then called upsert method. and get a new number. Post a new command : GenerateOrderNumber. now before this post accepted hardware failure occur. If we retry we will have another number. It is not good. How to solve such use case in cqrs.

Mikev
  • 2,012
  • 1
  • 15
  • 27
Rohit Bansal
  • 1,039
  • 2
  • 12
  • 21

2 Answers2

2

Our projections are in mongodb <...>
now before this post accepted hardware failure occur

Most likely that described issue is not about CQRS or EventSoucring itself, but related to projection storage, which is MongoDB in question above.

You are trying to perform potential atomic operation without transaction guarantees. Since hardware failure can be caused within random time, database should provide ability for rollbacking failed atomic operations in current transaction.

Best choice is native MongoDB transactions, which are available since 4.0 version - https://docs.mongodb.com/manual/core/transactions/ - and your code will look like this:

session.startTransaction( … );
try {
  const lastNo = await eventsCollection.findOne( ... )
  await eventsCollection.insertOne( …, lastNo +1 )
  session.commitTransaction()
} catch (error) {
  session.abortTransaction()
}

If you have to use older MongoDB versions, transactions still can be used. But instead of using intrinsic operator, you should manually write transaction log, and after reconnect to database perform monitoring for broken transactions and revert them manually via log.

Vladislav Ihost
  • 2,127
  • 11
  • 26
  • Thanks for explaining. It is related with mongodb. If we use new mongo version then it will not occur. but if we are using older version, then it will really happened. I did not understood "If you have to use older MongoDB versions, transactions still can be used. But instead of using intrinsic operator, you should manually write transaction log, and after reconnect to database perform monitoring for broken transactions and revert them manually via log. ". Here how we can monitor broken transactions. – Rohit Bansal Mar 01 '19 at 05:52
1

You should do all actions via events, even generating sequence no. In your case I suggest you using saga:

  • build a projection for generating order_no
  • fire new event OrderCreated (after this point you have Order Aggregate with some unique id)
  • saga, listening to this event, fire event GenerateOrderNo (get next free number from projection)

In that case, any time you ask new order_no after failure it'll be the same.

Correct me please if I understood you wrong.

A Ralkov
  • 1,046
  • 10
  • 19
  • In that case, any time you ask new order_no after failure it'll be the same. ? How it can be same. We are using mongodb and in mongodb there is no sequence number. We need to do it manually by using some sort of $inc operation. If it succeeds and after that failure got occur. If we tried again, it will generate a new number. – Rohit Bansal Mar 01 '19 at 05:50
  • I suggest you using event store as the only source of truth. As written before, to get order_no, I suggest using particular projection (map aggregate_id-order_no, OrderNo projection). So, after creating order, your saga catch OrderCreated event, find last order_no from OrderNo projection, and fire new event with new order_no for new order OrderNoGenerated event. – A Ralkov Mar 01 '19 at 07:32
  • I don't think you should use mongo for order_no generating on-the-fly, because it'll be hard to switch to another storage. Besides that, there are lots of logic that may be done after order creation, so I suggest you use cqrs way to do it all (event -> saga -> projection -> event). – A Ralkov Mar 01 '19 at 07:32