ドメイン駆動設計でトラッキングIDをどうするか
マイクロサービスっぽくサービスを分解していると、ログを串刺しでみたり分析したりするためにトラッキングIDを付与したいことがあるが、DDDの文脈からいうと、トラッキングIDはドメインの処理とは関係ないため、どうやって引回すか悩む。
例
↓のデータソースに渡す、TrackingIdをどうやって取得するか、という話です。
class SomeService { someEntityRepository: SomeEntityRepository someService(id: Id): Unit } class SomeEntity { id: Id someFunction(): SomeEvent } class SomeEvent { id: Id } interface SomeEntityRepository { findBy(id: Id): SomeEntity save(event: SomeEvent): Unit } class SomeEntityRepositoryImpl { entityDataSource: EntityDataSource eventDataSource: EventDataSource } class EntityDataSource { selectEntity(id: Id): (TrackingId, SomeEntity) postEvent(trackingId: TrackingId, event: SomeEvent): Unit } SomeService *-down-> SomeEntityRepository SomeService -down-> SomeEntity SomeEntity -down-> SomeEvent SomeEntityRepository <|-down- SomeEntityRepositoryImpl SomeEntityRepositoryImpl *-down-> EntityDataSource
object SomeService { def someService(id: Id): Unit { val entity = someRepository.findBy(id) val event = entity.someFunction someRepository.save(event) } }
案1:Entityに属性を追加して引回す
case class SomeEntity(id: Id, trackingId: TrackingId) { .... }
サービス層は変更ないですが、ドメイン層がTrackingIdに汚染される。ぶっちゃけTrackingIdだったらまぁって気がしてますが、EventDataSourceに引回す項目が増えてくると大変。
案2:サービス層で保存する
Repositoryを以下のようにして、サービス層で引回す
trait SomeEntityRepository { def findBy(id: Id): (SomeEntity, TrackingId) def save(event: SomeEvent, TrackingId): Unit }
Entityは汚染を防げるが、Repositoryは汚染される。EntityがクリーンならテストしやすいのでRepositoryが汚れてもいいかーという割り切りがありならあり。複数のエンティティを操作する場合に、TrackingIdが入れ替わらないように注意が必要。
案3:EntityDataSourceへの参照をキャッシュしてRepositoryで再度参照
これが一番良さそう。キャッシュの有効期限とか、キャッシュの排他制御が大変。 超雑に書くと↓、スレッドセーフではない。
object SomeEntityRepositoryImpl { val entityCache: TrieMap = TrieMap[Id, TrackingId] val entityDataSource: EntityDataSource = EntityDataSource val eventDataSource: EventDataSource = EventDataSource def findBy(id: Id): SomeEntity = { val res = entityDataSource.selectEntity(id) entityCache.put(res._1.id, res._2) res._2 } def save(event: SomeEvent): Unit = { val trackingId = entityCache.get(id)._2 eventDataSource.postEvent(event, trackingId) } }
SpringFrameworkなら、RequestScopeやJobScopeのオブジェクトが使えそう。