Slide 1

Slide 1 text

ドメイン駆動設計 - 実践企業が語る Before/After - Vol.2 製造業の会計システムを DDDで開発した話

Slide 2

Slide 2 text

自己紹介 ● Yo Matsumoto ○ 兵庫県在住 ○ 基本リモートワークですが、月に 1〜2回ほど東京のオフィスに行っています ○ 趣味:スノーボード、ボードゲーム、猫 2匹 ● キャディ株式会社では ○ 生産管理システムのバックエンドエンジニアを 1年 ○ 図面データ活用SaaSのバックエンドエンジニアを 1年6ヶ月 2

Slide 3

Slide 3 text

© CADDi Inc. Mission モノづくり産業のポテンシャルを解放する Unleash the potential of manufacturing モノづくりに携わるすべての⼈が、 本来持っている⼒を最⼤限に発揮できる社会を実現する。 そのために私たちは、産業の常識を変える「新たな仕組み」をつくります。 現在モノづくり産業では、⾮常に多くの⼒が埋もれたままになっています。 ⾒積業務や管理業務に忙殺される、 営業⼒が⾜りない、情報やネットワークが乏しい。 あらゆる理由によってがんじがらめにされ、 本来の開発⼒や技術⼒を発揮しきれていません。 こうした縛りをほどくことで、各企業のポテンシャルを解放。 産業全体に⼤きな⼒を⽣み出し、豊かにすることが私たちの使命です。 ⼩さな町⼯場も、歴史ある⼤規模メーカーも、創⽴まもないベンチャーも。 すべてのモノづくり企業が強みを活かして輝き、新たな価値がたくさん⽣まれる。 そんな未来を切り拓くために、私たちは挑み続けます。 3

Slide 4

Slide 4 text

● 背景 ○ 約 2 年前に構築した社内向けプロダクトで現在はサービス終了している ■ 当時は受発注プラットフォームを運営しものづくりも行っていた ■ なお、現在はナレッジを集約し 製造業AIデータプラットフォーム を構築している ○ 当時、ビジネスが拡大し扱う製品、在庫の量や種類が増え、会計オペレーションの複雑さが限界を迎えつつ あった ● 目的 ○ 拡大する会計オペレーションを持続可能かつ再現可能にする ● システム概要 ○ バッチ処理として最低月に1回稼働し、締めた会計仕訳を保存する ○ 会計仕訳は、受発注や在庫管理を行う上流システムのイベントから生成される ○ 会計仕訳の正当性について検証レポートを生成する ○ 生成された会計仕訳は全社会計基盤にそのまま Importされる 会計システムとは 4

Slide 5

Slide 5 text

DDDの利用について考慮したこと 5

Slide 6

Slide 6 text

● ドメイン駆動設計のエッセンス エリック・エヴァンスのドメイン駆動設計: ソフトウェアの核心にある複雑さに立ち向かう ○ コアドメインに集中する ○ 明示的な境界づけられたコンテキストの内部で、ユビキタス言語を語る ○ ドメインの実践者とソフトウェアの実践者による創造的な共同作業を通じて、モデルを探求 ● 会計システムでは ○ コアドメイン : 上流システムのイベントを会計仕訳に変換する部分 ○ ドメインの実践者 : 経理担当者 ● プロジェクト開始時の状況 ○ 会計仕訳への変換処理の要件は決まっていない ○ 事業のゴールから逆算した開発期間は 6ヶ月 DDDの利用について考慮したこと 6

Slide 7

Slide 7 text

● キャディ では DDD が普通に利用されており、共通言語となっていた ○ 生産管理システムは開発済みで、 DDD を採用して開発 ○ より実践的な、 実践ドメイン駆動設計 や 関数型ドメインモデリング といった書籍も普及 ○ 新規システムは当然 DDD でやるよね、という雰囲気は醸成されていた ● しかし、DDD に馴染みがない中途採用者もいる ○ 育成期間はある程度かかる ○ 後でチームが拡大することがわかっており、 他のシステムと構造や考え方が揃っていることによるメリットが 上回ると判断した DDDの利用について考慮したこと 7

Slide 8

Slide 8 text

● システムの状況とDDD ○ イベントの種類や仕訳の種類で分類する ○ 要件が定まっていないことから、徐々に実施範囲を広 げながらドメインを洗練させる方針 ● バッチ処理とDDD ○ バッチ処理はUIがない = 検証ロジックやエラーハンド リングがない ○ ドメインロジックがクリーンに保ちやすいのではという 仮説があった ● 会計処理とDDD ○ 会計処理はかなり純粋な計算ロジックになる ○ ドメインをカプセル化して Testable にしやすい DDDの利用について考慮したこと 8 実施範囲を決める図

Slide 9

Slide 9 text

ドメインモデリングを合意する 9

Slide 10

Slide 10 text

● ドメインモデリングとデータモデリングを 並行して行う ○ ドメインモデリングに不慣れなメンバーもい て、データモデルをあわせて考えないと実 感が湧かなかった ● 当時のチーム3人が、それぞれモデル をおこし、ディスカッションしながら洗練 させていく ○ 図があるので空中戦にならない ○ モデル作成1日、ディスカッション1日でかな り高速に決定できた ドメインモデリングを合意する 10

Slide 11

Slide 11 text

ドメインモデリングを合意する 11 境界づけられたコンテキストの内部をクリーンにデザインする ● AggregateRootを決定する ● 上流システムとの連携部分は全て腐敗防止層 (ACL)に分離する ● コアドメインの用語にこだわる

Slide 12

Slide 12 text

ドメインの用語を整理する ● ユビキタス辞書 ○ システム固有の概念を整理する ○ ググればよいことは書かない ● 会計用語集 ○ 金融庁の EDINETタクソノミ が公開している 勘定科目リストを利用 ○ https://www.fsa.go.jp/search/20241112.html ドメインモデリングを合意する 12

Slide 13

Slide 13 text

● 複式簿記で複雑な概念の認識をあわせる ○ ドメインモデルだけでは複雑な計算ロジックは表現できない ○ 会計ドメインの共通言語である複式簿記で認識を合わせる ○ 結果として精度高く認識を合わせることができた ○ ドメインを正確に表現でき、検証可能なツールであれば何でも良い ドメインモデリングを合意する 13

Slide 14

Slide 14 text

● DFDの利用 ○ 上流のイベントと バウンディッドコン テキスト内の仕訳の変換が複雑 ○ どのように変換できるかを把握できる ようにした ● 運用フェーズで利用 ○ 経理部門でも長く利用する資料に なったとのこと ドメインモデリングを合意する 14

Slide 15

Slide 15 text

具体的なドメイン実装の紹介 15

Slide 16

Slide 16 text

● Domain はどこにも依存しない構造にする ○ ロジックを集約する Domain から複雑さを排除する ○ Domain 単体で Testable にする 具体的なドメイン実装の紹介 16 引用元: The Clean Code Blog

Slide 17

Slide 17 text

● 1例として、Repository は… ○ 依存関係逆転の原則 (DIP)を使う ■ trait(Rust における interface のようなもの) は domain 層に配置 ■ 実装は infrastructure層に配置 ○ mockall crateを使ってmockを自動実装させて usecase 層のテストを書きやすく ○ 具体的なドメイン実装の紹介 17 // domain/accounting/src/repository/accounting_journal_repository.rs #[cfg_attr(any(test, feature = "mock"), mockall::automock)] #[async_trait::async_trait] pub trait AccountingJournalRepository { async fn find_by_id (&self, id: &JournalId) -> anyhow:: Result; async fn insert(&self, accounting_journal: AccountingJournal) -> anyhow:: Result<()>; } // infra/repository_impl/repository_impl_pg/src/repository_impl_pg/accounting_journal_repository_pg.rs #[async_trait::async_trait] impl AccountingJournalRepository for AccountingJournalRepositoryPg { async fn find_by_id (&self, id: &JournalId) -> anyhow:: Result { // ... } async fn insert(&self, accounting_journal: AccountingJournal) -> anyhow:: Result<()> { // ... } }

Slide 18

Slide 18 text

● 意味の違う数値を異なる型として表現 (Value Object) ○ 総額(TotalAmount)、単価(UnitAmount)、在庫数量(InventryAmount)... etc ○ ソースコードにドメインの計算ロジックを直接記述できる ○ 誤って異なる型を使用した場合、ビルド時にエラーになる ○ コード修正の際の矛盾 = ビジネスロジックの矛盾 となり活発な議論を醸成する下地に 具体的なドメイン実装の紹介 18 総額 = 単価 × 在庫数量の計算 pub struct AccountingAmount { value: Decimal, amount_type: PhantomData, } pub type TotalAmount = AccountingAmount; pub type UnitAmount = AccountingAmount; pub type InventoryQuantity = TaggedQuantity; // pub type quantity_type::Inventory impl UnitAmount { pub fn multiply_with (&self, quantity: InventoryQuantity) -> anyhow:: Result { // ... } }

Slide 19

Slide 19 text

● 集約の単位を定義する (Aggregate) ○ Aggregate とは ■ Repository の読み込み/保存の単位で、1 Transaction になる ■ 1つの Aggregate Root を持ち、 Aggregate をまたがるときは id で参照を保持する ■ 一般的には大きな AggregateRoot は NGとされるが… ○ このシステムでは最大 1ヶ月分の仕訳を含む AggregateRoot: AccountingJournal を定義した ■ 1回のバッチの処理単位が最大 1ヶ月分であるため構造がシンプルに ■ Webアプリではなかったためレスポンスが遅い、などの問題はなかった 具体的なドメイン実装の紹介 19 // domain/accounting/src/aggregate/accounting_journal.rs pub struct AccountingJournal { id: JournalId, transactions: Vec, } // domain/accounting/src/aggregate/accounting_journal/accounting_transaction.rs pub struct AccountingTransaction { id: AccountingTransactionId, accounting_date: AccountingDate, entries: AccountingEntrySet, }

Slide 20

Slide 20 text

● バッチ処理の上位構造はいわゆるワークフローになっている 具体的なドメイン実装の紹介 20 pub struct CreationSetInitialized { id: JournalCreationSetId, } pub struct CreationSetInventoryCreated { id: JournalCreationSetId, inventory_id: InventoryId, } pub struct CreationSetJournalCreated { id: JournalCreationSetId, inventory_id: InventoryId, journal_id: JournalId, } pub struct CreationSetReportCreated { id: JournalCreationSetId, inventory_id: InventoryId, journal_id: JournalId, report_id: ReportId, } async fn create_journal ( &self, creation_set: CreationSetInventoryCreated, ) -> anyhow:: Result { // ... } ● Workflow パターンを利用 ○ 状態そのものを Entity にする ○ ある状態を受け取って次の状態を返す関数でシ ステムを構築する ○ 関数型ドメインモデリング に 記述されているパターン

Slide 21

Slide 21 text

上流イベントの結果とデータ上の在庫数の一致をレポート化 ● 前月末在庫数 + 今月入庫数 - 今月出庫数 = 今月末在庫数 ● 月次締め前に複数回チェックし異常を検知、修正する 具体的なドメイン実装の紹介 21

Slide 22

Slide 22 text

プロジェクトを振り返って 22

Slide 23

Slide 23 text

● 後で参加したメンバーのスムーズなキャッチアップ ○ ビジセスと実装の乖離が少ない ○ ドメインモデル、ユビキタス辞書などの資料 ● シンプルなビジネスロジックによる少ない実装起因バグ ○ ドメイン内のコードをユビキタス言語で表現する ○ バウンデッドコンテキストを用いた会計ドメインの保護 ○ 運用フェーズで、実装に起因するバグは数件件程度の発生 ● ビジネス上の制約を加味した開発スコープの適切なコントロール ○ 当初の開発期間は6ヶ月だったが、最終的に開発期間は 9ヶ月になった ○ 監査法人との会計処理確定タイミングに従って、 6ヶ月で必要十分な機能リリースができた DDDの実践で得られた効果 23

Slide 24

Slide 24 text

● ドメインエキスパートへのDDDの導入 ○ エンジニアはDDDを理解した状態だったが、経理部門はそうでもなかった ○ 最初にDDDの勉強会をしておけば、さらなる成果が得られたのでは ● ユビキタス辞書の導入の遅れ ○ 開発開始時にはユビキタス辞書がなく、開発後 3ヶ月が経過してから作成していた ○ 開発着手後にすぐ作成していれば、もっとドメインエキスパートとの知識の交換が進んだかも DDDの実践時の課題 24

Slide 25

Slide 25 text

● 現在、我々は製造業AIデータプラット フォームを構築しています ○ データプラットフォームでは抽象化された知識 を蓄えるので、 型を重視したドメインモデリン グだけではない抽象的な設計が必要になる ○ データプラットフォームの周辺システムではドメ インに寄り添うサービスが求められるため、今 後も型を重視したドメインモデリングが必要に なる データプラットフォームでの DDD 25

Slide 26

Slide 26 text

● キャディ はドメインモデリングをリードできるエンジニアを募集しています ● もっと詳しく聞きたいところをカジュアル面談でお話しすることもできます ご清聴ありがとうございました 26 カジュアルにお話しましょう! カジュアル⾯談ページ エンジニア採⽤ポータル CADDi Engineering