Edited at

SIer育ちがDDDレイヤー化アーキテクチャのオシャレな概念を業務的側面で理解した軌跡


背景

Mix Leap Study 特別編 - レガシーをぶっつぶせ。現場でDDD! コラボカンファレンスにてほげさん(@suzuki_hoge)さんの"抽象的な教えを試行錯誤しながら解釈した DDD の実践レポート"を聴講して"全く意味不明な時期あるよね"と懐かしくなったので, 自分の理解の経緯を改めて整理してみた.

ちなみに自分もエヴァンス本をほぼ読んでいない. 読めていないだけで"読む必要ない"とは思ってないよ. この記事はレイヤー化まで. モデリングとかドメインモデルを育てたりする処までは言及していない.


スタート地点


一年ほど前のSIer時代案件の技術スタック


  • 分散開発体制で構成管理マンへコードとバイナリをメール送信.

  • チーム内Staging環境へのデプロイは, ファイル名.拡張子_YYYYMMDDとFTP転送なOldSchool.

  • Java6?, 素JS, COBOL. *.cobを開くと大体5000行超え.,,

  • 全コンパイルは複数回しましょう. 循環参照? 案件内用語で"馴染む".


直前の社内SEでの技術スタック


  • Redmineでチケット駆動をぶっ込む.

  • Redmine Backlogsでスクラムをぶっ込む. スクラムマスターやった.

  • GitLabでソースレビュー文化をぶっ込む.

  • JenkinsでCI/CD文化をぶっ込む.

  • 開発体制で思いつくことは手当たり次第やった. 満足した.

  • Zabbixで死活監視・リソース監視をぶっ込む.

  • Elasticsearch, Kibana, fluentdでログ収集をぶっ込む.

  • Dockerでミドルウェア周りをぶっ壊しても大丈夫なお勉強環境をぶっ込む.

  • Infrastructure as a codeをぶっ込む.

  • オンプレインフラで思いつくことは手当たり次第やった. 満足した.

  • 技術に餓えていた. 好き勝手やった. 後悔はしていない.

  • でも, プログラミング全然してない.,,


DDD実装を始めた案件での技術スタック

"王様達のヴァイキング"を読んでたらScalaが出てきた. マイナーっぽくて変態っぽくて楽しそう.


  • Scalaを始めた. ミックスイン. 高階関数. 型パラメータ. 型クラス. モナド. うん, さっぱり分からん.

  • PlayFrameworkでWebAPI. play-scala-slick-example. Guice. Inject. DIコンテナ. sbt. うん, さっぱり分からん.

  • Slick. FRM. Future. Stream. Tupple. うん, さっぱり分からん.

XCodeのGUIが嫌. クロスプラットフォームしたい. VimVimしたい.


  • React Nativeを始めた. node.js. npm. 動的型付け. lambda式. Callback. async/await. うん, さっぱり分からん.

  • Redux. Action. Reducer. Store. うん, さっぱり分からん.

関数型わからん. 変態なFrontendしたい.


  • Elmを始めた. うん, 関数型分かった.

状態


  • 一人プロジェクト.

  • 技術的負債なトランザクションスクリプトを元気いっぱい製造. なんか知らんけど動いている.


DDDというキーワードを知る

独学でScalaを始めたのでキャッチアップのために以下を始めた.



  • Scala関西勉強会に参加する. 社外勉強会参加二回目でかなりドキドキした.


  • scalajp/publicをチェックする. 質疑応答の言葉の意味が分からん.

  • Scalaつよつよエンジニアをフォローする. オシャレ用語が飛び交う. うん, 分からん.


  • コップ本を読む. Scalaの言語機能は分かるけどアプリケーションの作り方が分からん. 積読へ.

うん, Scalaよく分からん, な日々が続く中, かとじゅん(@j5ik2o)さんがオシャレなDDD用語をひたすら呟く. 何かよく分からないが"最近はDDDで実装するのがイケてるらしい"と洗脳される.


取り敢えずPackageを分割してみる

DDDをググってみると, よく分からないが役割を決めてソースを分割する事らしい. "アーキテクチャ"というワードが連発される. まず, アーキテクチャというワードに"カッコつけ過ぎちゃう?"というくらいに親しみがない. SIer的な要件定義, 基本設計, 詳細設計のカテゴライズは分かるが実装よりの構造化の経験がない. MVCのMとCの分割もちょっとアヤシイ状態. それっぽいDDD用語で画像検索すると大きく分けて2系統の画像が見つかる.

答えのアーキテクチャ?が何か分からないが, 少なくとも自分が作ってるコードが汚いことは痛感していたので, 取り敢えずScalaWebAPIのPackageを切ってみる. どうせ一人ブロジェクトだし.

この心意気w

MVC
Package
備考

View
-
Json的な何か

Controller
Controller
ロジックがいっぱい

Model
Domain
何か部品を置いてみた

Model
Infrastructure
SlickFRMのObjectを置いてみた

何が嬉しいかさっぱり分からなかった. 😇


業務フロー

折りに触れてDDD用語をググっているとScalaのDDD実装サンプルに出会う.

DDDの各用語の深い意味は未だにチンプンカンプンだけどPackage分割の目星はついた. 取り敢えず倣ってPackageを切ってみる. DDDの意味が分からないので頭空っぽにして実装サンプルを真似てコードを分割していく. するとApplicationService層はロジックの流れだけが残った. "うん? 見た事あるぞコレ! 業務フローちゃうん?"という事でApplicationServiceは業務フロー的に理解できると分かった.

業務理解
レイヤ
備考

-
Adapter.Interface.WebAPI.Controller
Json変換

業務フロー
Application Service
Domain部品を使ってロジックの流れを作る

?
Domain
何かロジック部品を置いてみた

-
Adapter.Infrastructure.Datastore
SlickFRMのObjectを置いてみた


依存, Dependency, import, DI, Inject, DIコンテナの違い

ddd-on-scalaのComponent.scalaを読んだら下記オシャレ用語の違いを理解できた.


  • 依存・Dependency・import: 他ソースを参照すること. build.sbtのビルドツールで他ライブラリを参照すること.

  • DI・Inject: 抽象クラス・インターフェイス・トレイトを継承・ミックスインして抽象定義に実ロジックを与えてオブジェクト・インスタンスを作ること.

  • DIコンテナ: 依存の関連性を管理している所. 本番・テストのモードによって継承・ミックスインする実ロジックを切替えること.

後にこのDIパターンをCakePatternと知った. PlayFramework・SlickのInjectはGuiceを理解しないと使いこなせないことを知る.

注意: いくらなんでもimportは分かってるよ #include <stdio.h>

注意: アセンブラのメモリアドレス, Cのポインタ, JavaのInterface, Abstractは分かるからアドレス参照的な概念は分かってるよ


関数型しか許されない

Scalaは手続型でも書けるからついつい手続型コーディングへ逃げてしまう.,, Elmやった. 関数型しか許されなかった. let式で少しノラリクラリ逃げた. 関数型分かった. ScalaとJavaScriptの関数型を理解できた. lambda式もCallbackも即時関数も関数型引数もFunction22も中置記法もバッチリ大丈夫!! ついでにReduxも分かった. 多分, 知らんけど.


依存方向を制限する

ddd-on-scalaの実装サンプルをちゃんと読んでいないことが露呈する. ScalaWebAPIの各依存関係をCakePatternで実装し各レイヤをサブプロジェクトに分割しsbtで依存方向を制限した. 但し, Adapter.InfrastructureのSlickFRMObjectはDIコンテナを使っていたので保留とした.


依存関係逆転

一刻も早くGuiceDIコンテナと決別したかったのでSlickFRM関連の実装を全て修正した. Adapter.Infrastructure層からDomain層へ依存を逆転させた. Adapter.Infrastructure層もサブプロジェクトに分割して依存方向を制限した.

業務理解
レイヤ
DDD
備考

-
Adapter.Interface.WebAPI.Controller
-
Json変換

業務フロー
Application Service
-
Domain部品を使ってロジックの流れを作る

?
Domain
-
何かロジック部品を置いてみた

?
Domain
Repository
データ永続化Interface

-
Adapter.Infrastructure.Datastore
Impl
データ永続化実装


型の有り難みを知る

格好良さそうだったのでReactNativeのJavaScriptをTypeScriptに置き換えた. いい加減VSCodeを導入した. VimKeybind付き. "type! interface! 読める! 読めるぞ! 引数の意味が読めるぞ!"と改めて型の有り難みを知る.

構造体@C, Recoerds@Elm, copy句@COBOL case class@scalaなどのデータ構造を表現するコードは書いてきたつもりだったけど, 同シンタックスで動的型付け言語@JavaScriptから静的型付け言語@TypeScriptに移行することで有り難みを染み染み感じた. 型, ありがとう. 🙇🏻


データ更新周りをゴニョゴニョしたら集約概念が分かった

新規プロジェクト(これも一人プロジェクト)が並行して立ちあがったのでScalaWebAPI側でScalikeJDBCを使ってみた. ddd-on-scala実装サンプルのInfrastructureAware.scalaを読む. UnitOfWork, CakePattern, Currying?でApplicationService層でトランザクション管理している事が分かった. Elmで関数型を仕込まれたので結構すんなり理解できた. 併せてAtomicityから技術的側面の集約概念が分かった. 業務的に集約単位をどのように設計するかは別の話.

業務理解
レイヤ
DDD
備考

-
Adapter.Interface.WebAPI.Controller
-
Json変換

業務フロー
Application Service
集約
Domain部品を使ってロジックの流れを作る

?
Domain
-
何かロジック部品を置いてみた

?
Domain
Repository
データ永続化Interface

-
Adapter.Infrastructure.Datastore
Impl
データ永続化実装


業務都合と技術都合

ReactNativeのReduxのMiddlewareで業務フロー処理と同時にStore, Realm, WebAPIへ保存処理を行っていた. リアルタイム表示やオフライン表示のためにOnMemory, LocalStorage, Storageとデータ更新先がそれぞれあったが技術的都合なのでAdapterへ閉じ込めた. 業務都合と技術都合でRepositoryとRepositoryImplを分ける理由を実践で理解できた. 業務視点で設計を考えてDomain層にAdapter層が依存する理由も実践で理解できた.

業務理解
レイヤ
DDD
業務 or 技術
備考

-
Adapter.Interface.WebAPI.Controller
-
技術
Json変換

業務フロー
Application Service
集約
業務技術
Domain部品を使ってロジックの流れを作る

?
Domain
-
?
何かロジック部品を置いてみた

?
Domain
Repository
業務
データ永続化Interface

-
Adapter.Infrastructure.Datastore
Impl
技術
データ永続化実装


受発注手段

今までは自前構築したWebAPI, Datastoreと通信してたが, 新しく外のWebAPIを叩く機会を得た. するとWebAPIエンドポイントの新しい側面が見えた. Frontend, MobileClientから依頼を受けるエンドポイント, 自前WebAPIサーバから依頼を受ける外のWebAPIエンドポイント. 自分への依頼を遂行すために外に依頼を出す. パートナーへの外出発注? Adapter層はシステム外との受発注手段的に理解できると分かった. 時代が変われば手段も変わる. 給与明細を紙配布しようが電子化しようが業務内容が意味する処は変わらない.

業務理解
レイヤ
DDD
業務 or 技術
備考

受注手段
Adapter.Interface
-
技術
受注書, 納品書, 窓口, 郵便受, etc.

業務フロー
Application Service
集約
業務技術
Domain部品を使ってロジックの流れを作る

?
Domain
?
?
何かロジックを置いた

記録保存業務
Domain
Repository
業務
簿記, 入出庫のInterface

社外連携業務
Domain
Interface
業務
発注, 検品のInterface

記録保存手段
Adapter.Infrastructure
Impl
技術
帳簿, 倉庫, etc

発注手段
Adapter.Infrastructure
Impl
技術
注文書, 手紙, 電話, FAX, EMail, etc


業務細則

Domain層の独立が終わったのでDomain層のモデリングに注力できるようになった. 決算周りの日付管理を整理しようとValueObjectに計算を閉じ込めてみた. 決算時期は? 年度? 半期? 四半期? タイムゾーンは? とValueObjectは企業独自の事情が含まれることが分かった.

業務理解
レイヤ
DDD
業務 or 技術
備考

受注手段
Adapter.Interface
-
技術
受注書, 納品書, 窓口, 郵便受, etc.

業務フロー
Application Service
集約
業務技術
Domain部品を使ってロジックの流れを作る

?
Domain
?
?
何かロジックを置いた

業務細則
Domain
Value Object
業務
規程で定義するほどでない細則

記録保存業務
Domain
Repository
業務
簿記, 入出庫のInterface

社外連携業務
Domain
Interface
業務
発注, 検品のInterface

記録保存手段
Adapter.Infrastructure
Impl
技術
帳簿, 倉庫, etc

発注手段
Adapter.Infrastructure
Impl
技術
注文書, 手紙, 電話, FAX, EMail, etc


業務分掌, 所管規程

閲覧権限管理は人事部門管掌?監査部門管掌?法務部門管掌?とPackageの切り方や機能をどこに寄せるかを悩むようになってきた. やっとDomain駆動設計w

業務理解
レイヤ
DDD
業務 or 技術
備考

受注手段
Adapter.Interface
-
技術
受注書, 納品書, 窓口, 郵便受, etc.

業務フロー
Application Service
集約
業務技術
各部門手続を連結させて業務フローを作る

業務分掌, 所管規程
Domain
Domain Object
業務
各部門所管規程

業務細則
Domain
Value Object
業務
規程で定義するほどでない細則

記録保存業務
Domain
Repository
業務
簿記, 入出庫のInterface

社外連携業務
Domain
Interface
業務
発注, 検品のInterface

記録保存手段
Adapter.Infrastructure
Impl
技術
帳簿, 倉庫, etc

発注手段
Adapter.Infrastructure
Impl
技術
注文書, 手紙, 電話, FAX, EMail, etc


業務が進むCommand, 業務が進まないQuery

DDDとCQRSとEventSourcingってワンセットなの?って事でCQRSとEventSourcingを調べていると下記文献と遭遇する.

CQRSの仕組みをDatastoreに限定してよくよく考えると”DatabaseのTriggerと同じことやってる?イベント要らん?Viewでも良い?"と理解が進み, 心理的ハードルが下がった. Event管理がちょっと大変だったので, Domain層のRepositoryとは別にQuery層とDAO, DTOを新たに増設するに留めた.

Query層はDTOをゴニョゴニョするのがメインで業務的な要素が少なかった. Domain層は業務を進める処理, Query層は業務が進まない処理, 的に理解できることが分かった.

業務理解
レイヤ
DDD
業務 or 技術
備考

受注手段
Adapter.Interface
-
技術
受注書, 窓口, 郵便受, etc.

業務フロー
Application Service
集約
業務技術
各部門手続を連結させて業務フローを作る

業務分掌, 所管規程
Domain
Domain Object
業務
各部門所管規程

業務細則
Domain
Value Object
業務
規程で定義するほどでない細則

記録保存業務
Domain
Repository
業務
簿記, 入出庫のInterface

社外連携業務
Domain
Interface
業務
発注, 検品のInterface

照会業務
Query
DAO, DTO
業務
納品書, 帳簿, 倉庫のInterface

記録保存手段
Adapter.Infrastructure
Impl
技術
帳簿, 倉庫, etc

発注手段
Adapter.Infrastructure
Impl
技術
注文書, 手紙, 電話, FAX, EMail, etc

照会手段
Adapter.Infrastructure
Impl
技術
帳簿, 倉庫, etc


業務フローの可読性を高める

ScalaWebAPIの各レイヤの戻り値が下記の通りとなっておりAdapter.Infrastructureのパターンが増えると更にゴチャゴチャしそうで嫌だった. 技術コンテキストを排除して業務コンテキストに統一したかった. コンテキストを統一した.


  • Domain: Either

  • Domain Repository: Future

  • Query: Future

Either[A, B], Future[T], Future[Option[A]]をEitherT[Future, A, B]へ統一する


仕様書

ApplicationService層のテストが仕様書っぽくてなかなか良い感じになった.


まとめ

DDDアーキテクチャを業務的側面で解釈した内容をCleanArchitecture風の図にするとこんな感じ. まんま企業ドメインの構造ですね. ここまで自社都合で殿様商売できたら嬉しいですねw

BusinessDDD.jpeg


所感

独学でもアセンブラ的低レイヤー知識と実装サンプルがあれば, 時間をかけてデータの流れを追うことで大まかな概念は理解できた. システム全体を自分事で考える必要性, 機会があれば理解が進むと思う. 輪郭を整えてからディテールを描き込む絵を描くスタイルなのでレイヤ構造と依存方向を整えてからDomain層を作り込む流れになったけど, 理解は人それぞれなのでどこから取り組んでも良いと思う.

業務的側面でシステムを理解していれば事業のあり方にも意識が向くしエンジニア以外とのコミュニケーションもスームズに進むと思う. 多分, ドメインを育てるってそういう事なんだろう. 多分, あってる.,, エヴァンス本読んでないけど.


参考文献