マーチン・ファウラー著のエンタープライズアーキテクチャーパターン(以下PofEOAA)に載っていた、ユニットオブワーク(UnitOfWork)というパターンを理解するために、Rubyで実装してみたという話です。
ユニットオブワークとは
PofEOAAでは次のように記載されています。
ビジネストランザクションの影響を受けるオブジェクトのリストを保持しつつ、変更点の書き込みと並行性の問題の解決を調整する。
引用: マーチン・ファウラー. エンタープライズアプリケーションアーキテクチャパターン (Japanese Edition)
ドメインロジックで、オブジェクトに複数の変更を加えたいとき、トランザクションをどの範囲で設定したらよいか悩むことがあると思いますが、そうした問題に対処できそう、ということでこのユニットオブワークの理解を深めることにしました。
実装方法
オブジェクトの変更を、それぞれ追加、変更、削除を記録しておくコレクションに追加しておき、コミットメソッドが呼ばれたら一気に変更を反映する、というやり方をします。
ソースコード
githubのこちらのリポジトリにも置いてあります。
require 'singleton'
# Mapperはサンプルなので何もしません
class SomeMapper
def self.insert(obj)
puts "追加処理をしました"
end
def self.update(obj)
puts "更新処理をしました"
end
def self.getSomeDomainObject()
return SomeDomeinObject.create
end
end
# これがユニットオブワークの実装です
class UnitOfWork
include Singleton
def initialize
@newObjects = []
@dirtyObjects = [] # 変更を保存しておく場所。Cleanな状態から変更されたのでDirtyです。
@removedObjects = []
end
def registerNew(domainObj)
@newObjects << domainObj
end
def registerDirty(domainObj)
@dirtyObjects << domainObj unless @dirtyObjects.include?(domainObj)
end
def registerRemoved(domainObj)
return if @newObjects.delete domainObj
@dirtyObjects.delete domainObj
@removedObjects << domainObj unless @removedObjects.include?(domainObj)
end
def registerClean(domainObj)
end
def commit()
insertNew()
updateDirty()
deleteRemoved()
end
def insertNew
@newObjects.each do |o|
SomeMapper.insert o
end
end
def updateDirty
@newObjects.each do |o|
SomeMapper.update o
end
end
def deleteRemoved
# 略
end
end
# ユニットオブワークに記録されるドメインオブジェクトの基底クラス
class DomainObject
def markNew
UnitOfWork.instance.registerNew self
end
def markClean
UnitOfWork.instance.registerClean self
end
def markDirty
UnitOfWork.instance.registerDirty self
end
def markRemoved
UnitOfWork.instance.registerRemoved self
end
end
# ドメインオブジェクトの実装
class SomeDomeinObject < DomainObject
def self.create
obj = SomeDomeinObject.new
obj.markNew # ここでユニットオブワークに追加を記録している
return obj
end
def setSomeValue(newValue)
@some_value = newValue
markDirty() # ここでユニットオブワークに変更を記録している
end
end
# ここからは実際にサンプルを実行するところ
dobj = SomeMapper.getSomeDomainObject()
dobj.setSomeValue("test")
UnitOfWork.instance.commit()
まとめ
PofEOAAに載っているサンプルは、ただ記録して、それを反映するだけのシンプルなものでした。
実際に並列性の問題を解消するためには、ロックの仕組みを入れていかなければなりませんが、これについては書かれていませんでした。
PofEOAAにはこの他に、軽ロックや重ロックなどのパターンが説明されているので、これらと組み合わせて並列性の問題を解決しましょう、ということなのかもしれません。
重ロックというとヘビーメタルみたいですね。