doilux’s tech blog

ITに関する備忘録。 DDP : http://doiluxng.hatenablog.com/entry/2018/01/01/195409

ドメイン駆動設計でトラッキング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のオブジェクトが使えそう。