3

I am trying out a lot of new ideas (DDD, Event Sourcing and CQRS) and evaluating RethinkDB as a potential data store for the Domain Events. In DDD, an aggregate is set of objects that work together to provide a specific behaviour. Each aggregate is a transactional/consistency boundary. The root of the aggregate is an object that provides an API and hides the internal implementation.

To persistent an aggregate it is usually recommended to use optimistic locking. The idea is to have a version number attribute in the aggregate and when the time comes to save an aggregate we check to make sure the version of the aggregate in the database matches the version of the aggregate that was read/updated in the application. This guarantees that nobody changed the aggregate in the meantime and prevents overwriting others changes.

Obviously this version checking can't just happen in the application layer (think multiple application servers scenario). The application needs support from data store for doing atomic updates that take this version number into consideration.

Here is a simple implementation using the RethinkDB Ruby API.

I created a table called 'applicants' with one record

"id":  "6b3b57a7-3ba8-4322-873e-1d6c8333daae" ,
"name":  "Homer Simpson" ,
"updated_at": Mon Dec 28 2015 12:05:40 GMT+05:30 ,
"version": 1

Here is the sample test code that I ran twice in parallel

require 'rethinkdb'
include RethinkDB::Shortcuts

conn = r.connect(:host => 'localhost',
             :port => 28015,
             :db => 'test')

def update_applicant(conn, current_version)
  result = r.table('applicants').get('6b3b57a7-3ba8-4322-873e-1d6c8333daae').update{ |applicant|
  r.branch(
      applicant['version'].eq(current_version),
      {updated_at: Time.now, version: current_version + 1},
      {}
  )
}.run(conn)

  fail 'optimistic locking failure' if result['unchanged'] == 1
rescue => e
 puts "optimistic locking failure: #{current_version}"
 current_version = r.table('applicants').get('6b3b57a7-3ba8-4322-873e-1d6c8333daae').run(conn)['version']
 retry
end

(1..100).each { |version| update_applicant(conn, version) }

conn.close

This seems to work but I want to make sure there will be no race conditions and other issues with this approach in a production environment. I am assuming that update is an atomic operation and using a branch in update still keeps it atomic.

I am looking for some validation and suggestions from RethinkDB devs/users. Thanks.

1 Answers1

4

update is always an atomic operation unless you pass the non_atomic: true flag (which is sometimes necessary if the update contains a nondeterministic operation), so that code looks safe to me.

mlucy
  • 5,249
  • 1
  • 17
  • 21