Home > object oriented design > A pattern to "atomically" update a pair of objects

A pattern to "atomically" update a pair of objects

January 27Hits:1
Advertisement

Is there a standard pattern in OOP to kind of "atomically" update a pair of objects, like a pair of bank accounts on a transaction?

I would like to have somehow a single public method exposed that does it all at once, but can only come up with ways of doing it by making two calls of public methods to update objects separately.

I am currently experimenting with Racket, but i would appreciate examples in any language, or a description of a standard pattern.



Here is an example of what i have experimented in Ruby (with which i am more comfortable than with Racket). I am not concerned with locking here, just with class interface.

class Account   def initialize(total)     @total = total   end    def total     @total   end    def transfer_to(another, sum:)     change_total_by(-sum)     another.change_total_by(sum)   end    protected      def change_total_by(sum)       @total += sum     end  end  a = Account.new(10) b = Account.new(20) a.transfer_to(b, sum: 5) print "New a.total = #{ a.total },\nnew b.total = #{ b.total }" 

This works using protected instead of private.

Answers

You can abstract the transaction as an object in itself, which then provides a single interface to manage the transaction and its participants. First property of transactions, Atomicity satisfies your requirement of atomicity/single point.

I.e. something like this:

public class Transaction {
    ...
    private Account _source;
    private Account _destination;

    public void Commit() {
        lock( _globalAccountWriteLock )
        lock( _source )
        lock( _destination )
        lock( this ) {
            try {
                UpdateSource( _source );
                UpdateDestiantion( _destination );
            }
            catch( Exception e ) {
                // Rollback
            }
        }
    }

The important part is, you have to use respective locks for every access to these three objects for it to work.

Instead of a public addAmount(amount) method, your class Account could have a public addAmountFrom(otherAccount, amount) method which automatically checks the other account and performs the counter-operation on it.

When each balance-change of a account must specify another account, you also automatically enforce the "no entry without counter-entry" rule of double-entry bookkeeping.

The counter-operation could be done through a private or protected method subtractAmountTo(this, amount). Most OOP languages allow private and protected methods to be called from objects of the same class. It should be private because the method assumes that the counter-entry is taken care off by the caller.

I think i've found a satisfactory solution myself. Here is a Ruby version:

class TransactionService
  def initialize
    @accounts = {}
  end

  def register_account(account_id, deposit_proc:, withdrawal_proc:)
    @accounts.store(account_id,
                    :deposit_proc    => deposit_proc,
                    :withdrawal_proc => withdrawal_proc)
  end

  def forget_account(account_id)
    @accounts.delete(account_id)
  end

  def transfer(sender_id:, receiver_id:, sum:)
    @accounts[sender_id  ][:withdrawal_proc][sum]
    @accounts[receiver_id][:deposit_proc   ][sum]
  end
end

class Account
  def initialize(total, transaction_service:)
    @total = total

    transaction_service.register_account(self.public_id,
        deposit_proc:    proc do |sum| change_total_by(sum)  end,
        withdrawal_proc: proc do |sum| change_total_by(-sum) end)
  end

  def total
    @total
  end

  def public_id
    self.object_id  # just for simplicity
  end

  private

    def change_total_by(sum)
      @total += sum
    end

end

ts = TransactionService.new
a = Account.new(10, transaction_service: ts)
b = Account.new(20, transaction_service: ts)

print "Initial a.total = #{ a.total },\n" \
      "initial b.total = #{ b.total }.\n" \
      "---\n"

ts.transfer(sender_id:   a.public_id,
            receiver_id: b.public_id,
            sum:         5)

print "New a.total = #{ a.total },\n" \
      "new b.total = #{ b.total }.\n"

I do not care about locking, but it can be implemented easily inside TransactionService#transfer.

Related Articles

Copyright (C) 2017 ceus-now.com, All Rights Reserved. webmaster#ceus-now.com 14 q. 1.142 s.