DRESS CODE TECH BLOG
📅

「DevicePeople」か「PeopleDevice」かで悩んだ話。そして、中間テーブルの「解」を求めた話。

に公開

Dress Code Advent Calendar 2025、17日目の記事です🎉

本記事では、9月頃にリリースした機能の一部「デバイスの貸与状況の管理」の設計を簡単に振り返りつつ、実はもっと良い解があったなといった思いを残したいと思います。

https://www.dress-code.com/ja/news/it-force-devices-update-2025/ja

前提となるDRESS CODEのアーキテクチャの話を軽く載せます

DRESS CODEは「HR Force」、「IT Force」、「GA Force」といったシリーズと、各シリーズの配下にある「メンバー管理」や「デバイス管理」などのプロダクト群からなるSaaSです。


www.dress-code.com のプロダクト全体像より

バックエンドのディレクトリ構成においても、「HR Force」、「IT Force」、「GA Force」といったシリーズ単位で区切られており、それぞれのディレクトリ内でクリーンアーキテクチャっぽくサブディレクトリが切られています。
また、シリーズを跨いでデータを参照する際には、テーブルの直参照や結合はせずに、各シリーズ毎に用意されたadapterモジュールを経由するルールになっています。


バックエンドのディレクトリ構成は "XX Force" 毎に分かれていて、参照方法に制限がある

今回の話はメンバーとデバイスに関する話で、「HR Force」と「IT Force」の境界を意識しての設計が必要な内容になります。

リリースしたものは「デバイスに使用者を設定する」という字面通りのAPI

それでは、「デバイスの貸与状況の管理」の設計の話をしていきます。
要件としては、「デバイスに250項目近くの項目を増やして欲しい」というもので、その中の1項目に「デバイスの使用者を設定できるようにする」という要件がありました。

元々のドメインモデルのイメージは以下です。


元々のドメインモデル図

安直ですが要件の字面通り「デバイスに」「使用者を」ぶら下げる形でデバイス使用者を表現しました。(してしまいました。)


変更後のドメインモデル図

そして次のようなDeviceクラスや所有者を設定するCommandクラスができあがりました。

デバイス関連のコードのイメージ
class Device {
  deviceId: DeviceId
  // 現在の使用者(過去の所有者は一旦無視)
  deviceUser: {
    from: Date
    to?: Date
    peopleId: PeopleId
  } | null

  assign(peopleId: PeopleId): Device {
    const d = new Device()
    d.deviceId = this.deviceId
    d.deviceUser = {
      from: new Date(),
      peopleId
    }
    return d
  }
}

// デバイスに使用者を設定するためのコマンド
class AssignPeopleCommand {
  device: Device
  assignPeopleId: PeopleId
}

// コマンドのハンドラ
class AssignPeopleCommandHandler {
  repo: DeviceRepository
  async handle({device, assignPeopleId}: AssignPeopleCommand) {
    // デバイスを介して使用者を設定する
    const assignedDevice = device.assign(assignPeopleId)
    // 永続化する
    this.repo.save(assignedDevice)
  }
}

リリース後しばらくしてから何か引っかかる

誰かが入社するシナリオを考えました。
入社するメンバーにはパソコンやモニターや携帯電話など、複数のデバイスを貸与するはずです。

先ほどの実装を使うことも可能ですが、デバイス集約の集約ルートはもちろんデバイスなので、貸与するデバイス数だけDeviceインスタンスを用意して、ループ処理もしくはmap処理でアサインを実行しないといけなくなってしまい、非効率です。

このタイミングで、「デバイスに使用者を設定する」が果たして正しかったのか引っかかり始めました。
このシナリオに沿って設計するとすれば「メンバーがデバイスを持てる」の方が良さそうだったなと思いました。

考慮不足だったかもしれない「メンバー」目線での考え

「メンバーがデバイスを持てる」とするならば、下記のようになりそうです。


メンバー側に寄せたドメインモデル図

しかし、集約ルートとなるメンバーを介して使用デバイスの追加をするとして、そのデバイスが追加可能かはデバイス側の知識であるはずなので、この構造にも違和感を感じました。

今回のケースでは「貸与」という集約を作ることが解な気がした

「デバイスに使用者を設定する」と「メンバーの使用デバイスを追加する」は共存するので、どちらも正しくないのだろうなと思いました。


貸与集約として切り出したドメインモデル図

デバイス貸与という集約であれば、IT Force領域にあることは違和感ないです。また、複数のデバイスを貸与する場合にデバイス貸与エンティティを複数作成することも当然のように思えます。貸与中かについてもデバイス貸与集約の知識として持っていて違和感はありません。

定義されそうなコードのイメージ
// デバイス貸与エンティティ
class DeviceProvidedEntity {
  deviceId: DeviceId
  peopleId: PeopleId
  from: Date
  to?: Date
}

// デバイス貸与を複数作成するためのコマンド
class DeviceProvidedCreateManyCommand {
  entities: DeviceProvidedEntity[]
}

// コマンドのハンドラ
class DeviceProvidedCreateManyCommandHandler {
  repo: DeviceProvidedRepository
  handle(command: DeviceProvidedCreateManyCommand) {
    // デバイス貸与リポジトリを使用してデバイス貸与を複数作成する
    this.repo.saveMany(command.entities)
  }
}

// デバイス貸与のクエリサービス
class DeviceProvidedQueryService {
  // deviceIdに関するtargetDate時点の貸与情報を返す関数
  // 貸与情報がなければnullを返す
  findDeviceProvided(
    deviceId: DeviceId,
    targetDate: Date
  ): DeviceProvidedEntity | null {
  }
}

🎄残り1週間もDRESS CODEアドカレをよろしくお願いします🎅

余談

  • Dress Codeの今年度のテーマが「 \mathsf{\textcolor{#ffcc00}{The Answer}} 」だったので、それに因んで解に関する事を記事にしました。
  • DRESS CODEのようなコンパウンドなプロダクトでは、主語が入れ替わっても成り立つような設計や業務の想定を心がけた方が良いなと思いました。
  • DDDについては、かわうそさんも記事にしております〜

https://zenn.dev/dress_code/articles/dda0ca62219d92#ddd-における集約の難しさ

おまけ

  • 貸与と賞与、結構見間違えます。時期的にもね。


🍌 nano banana

DRESS CODE TECH BLOG
DRESS CODE TECH BLOG

Discussion