fc2ブログ

makopi23のブログ

makopi23が日々の生活で感じたことを気ままに綴るブログです。

「JSUG勉強会 2019その2 Spring BootベースのDDDサンプル徹底解説!」に参加しました

2019/2/18(月) 「JSUG勉強会 2019その2 Spring BootベースのDDDサンプル徹底解説!」に参加してきました。

Doorkeeper
https://jsug.doorkeeper.jp/events/86655

Togetter
https://togetter.com/li/1320727

参加の経緯


これまで「エリック・エヴァンスのドメイン駆動設計」「実践ドメイン駆動設計」の読書会に参加したりして一通り書籍は読んでいるものの、コードのイメージが中々掴めないままでモヤモヤしてたので、このイベントはホントありがたい~

事前にGitHubのリポジトリからDDDサンプルをcloneしてEclipseにインポートし、手元でアプリを動かしてみたり、コードをいくつか眺めてみたりして、勉強会中にもすぐ見て触れる状態にしてから参加しました。

聴講メモ


DDD sample code explained in Java from 亨 増田


はじめに

始める前に (P.2)
  • 「面白かったらブログ書いてみようかな、と思ってる方、挙手!」
  • 「ブログを書いてくれる方は「現場で役立つシステム設計の原則」の書籍をお渡ししようと思います。」 By 増田さん




なぜ作ったか? (P.3)
  • まずV1としてシンプルなDDDサンプルを開発した。
  • V1は「どこがDDDなんだ?」という感じで具体性が無かった。なのでV2として大幅にバージョンアップして公開し直した。
  • 言葉でDDDについてやりとりしても具体性がない。コードが一番具体的に伝えられる。
  • DDDサンプルを作ってから現場で実際に話す機会があって、質問が具体的にできるようになった。
  • 今日はQAタイムでいろいろ話してみたい。


何の具体例か? (P.5)
  • キーワードは3つ
  • 1.ビジネスルールが複雑さの原因
    • DDD本の副題に「ソフトウェアの核心にある複雑さに立ち向かう」とあるが、ビジネスルールが単純ならば複雑にならない。
    • 複雑さはビジネスルールに依存する。
  • 2.計算をモデリング
    • オブジェクト指向でソフトウェアを作るのに、データモデリングはぜんぜん関係がない世界。
    • ビジネスルールを計算モデルで表現する。
  • 3.型指向でプログラミング
    • どう実装するのか、徹底的に型指向でプログラミングする。
  • 3/22の[DevLOVE Premium第3回]ドメイン駆動設計 本格入門のイベントで、なぜそう考えるのか、3点がどういう位置づけにあるのか、を詳しく説明する。


関心の分離 (P.6)
  • 「計算(ビジネスルール)を実行するモジュール群」と「データを入出力するモジュール群 」を、モジュールとして明確に分ける。



モジュール構造の選択 (P.7)
  • 業務アプリはトランザクションスクリプトで書かれているいることが多い。
  • そうでなく、型と型を組み合わせてドメインモデルを作る。
  • ドメインモデルはビジネスルールが中核。計算に登場する値の種類に注目して、型で定義する。


サンプルの概要 (P.8)
  • 時給ベースの給与計算モデルは、日付計算、時間計算、金額計算などのネタがある。
  • 契約や法律などのルールもある。給与は書面に署名して、契約して単価が決まる。背景に雇用契約がある。
  • 単価×数量、だけじゃなく、残業割増とか深夜割増とか、いろいろな要素がある。労働基準法でも決まっている。
  • なのでサンプルのテーマに選んだ。
  • 契約 + 法規 + どのくらい働いたか、等をぶつけると、給与の支払い額になる。
  • 給与計算ルールを62種類の型で記述している。
  • 今日は給与(Payroll)型をどう使ってるかを具体的に説明する。


この後の段取り (P.9)
  • まずドメイン層(ビジネスルール層)を固める。
  • 次に、ドメイン層を使って機能を実現するアプリケーション層を実装する。
  • 次にデータソース層、最後にプレゼンテーション層を実装しにいく。
  • 設計・実装の観点で考えるとこの順序になるので、今日の説明のこの順序で行く。
  • 最後に6番目でビジネスドキュメントをコードから出力するツールについて説明する。


給与計算 (P.10)
  • サンプルを動かして実物を見せながら説明する。
  • 「給与の一覧」画面がある。布川さんの2月の支払額は86,450円。
20190218_01.jpg


  • 「勤務時間の一覧」のリンクを押すと、勤務時間の一覧が表示される。いつ、何時から何時まで働いたか。
  • 勤務時間を計算すると、2/1~2/18までで91時間。
20190218_02.jpg

  • 「従業員の一覧」画面を見ると、布川さんの現在の時給は12/21から950円。
  • ずっと働いてると時給が上がることもある。今は契約として、時給が950円と決まっている。
20190218_03.jpg
  • 布川さんの2月の支払額、86,450円をどう計算しているか。
  • PayrollクラスのtotalPaymentメソッドが86450円を計算して返している。

  • ContractWageから契約給与を取ってきて、PaymentAmount(支払い金額)にaddしていく。
  • 契約条件(Contract)という事実と、どれだけ働いたかという勤怠(Attendance)の事実から計算する。
  • 細かい計算式はPayrollにはない。それぞれの種類のクラスが持っている。
  • 責任をクラスに割り当てていくと、給与計算ルールのクラス数が60くらいになった。

  • WageConditionクラスが賃金条件を決める。
  • ベースがいくらで、割増率がいくらで、といった事実は、WageConditionクラスのフィールドの各クラスが持っている。

  • これらをPayrollクラスのtotalPaymentメソッドで合算して86,450円を算出している。


ドメイン層(ビジネスルール層) (P.11)
  • ビジネスルールに基づいた計算が重要。その置き場がドメイン層。
  • modelパッケージとtypeパッケージの2つに分けている。
  • モデルそのものと、それを使う部品パッケージを分ける。
  • 最初からハッキリ分かれるわけではなく、行ったり来たりの試行錯誤を相当やっている。
  • 型指向のプログラミングについて、Wikiに設計ガイドラインを書いている。相当こだわって書いた。

  • Bean Validationで有効範囲を記述している。自己文書化してる。

  • Plain Old Java
    • 「可読性 over Java」については設計ガイドラインを見てほしい。
    • 例えば、イミュータブルなフィールドはprivate finalにすべき、という意見があるが、でもビジネスルールってどうなの?
    • privateとかfinalはこのDDDサンプルでは書いてない。 → ガイド該当箇所
    • そこらへんは実験してる最中。コードを読んだとき、ルールとして読みやすいこと重視してる。
    • Javaの構文のお作法にはこだわってない。
    • setter/setterはビジネスルールの表現手段としては全然意味が無い。Direct Field Accessを使う。
    • LombokやJPAのアノテーションも使わない。
    • ビジネスルールをシンプルに書くとき、それらのアノテーションはほとんど意味が無い。考え方として、それらは有り得ない。

  • Payrollクラス
    • Contract型(契約条件)とAttendance型(勤務実績)という2つの事実を知っている。
    • 戻り値としてBigDecimalとかは返したくないので、PaymentAmount型(支払金額)のクラスを用意している。


QAタイム (P.12)
  • Q1.
    • Payrollクラスの37行目、value()メソッドで値を取り出してるが、どこでvalue()で取り出すのかのガイドラインはあるか?
  • A1.
    • このvalue()メソッドは、無くしたい。このサンプルは完璧な完成系ではない。
    • ただ、timeRecord.workDate().value()と、ドット連結はイヤだな・・・
    • 言い訳をすると、パッケージ間の相互依存を無くすために、あえて型でなくプリミティブで値を使った。。。
    • パッケージ関係の依存関係を無くしたかった。パッケージ間の依存関係は重要で、依存関係を減らしたい。
    • プリミティブは徹底的につぶしにいく。


  • Q2.
    • PayrollクラスのtotalPaymentメソッドでreduceを使っていないのは何故か?
  • A2.
    • reduceよりもforの方が分かりやすいから。
    • Stream APIを増田さんが嫌っている、というのはある。 By irofさん
    • Stream APIはシビアに見極めないと、業務の表現として貧弱なところがある。
    • その言葉を避けようとするとforで書いたほうがいい感じになることがある。
    • Streamという言葉が出てくること自体を避けたい。
    • Haskellとか関数型言語は大好きだけど、Collection操作をそれでやってしまうと、Javaだと自然に書けない。。。
    • Groovyだとさくっと書けるけど、Javaだと素直に書けない。。。
    • Stream APIを主要なところで使うと、ノイズに見える。
    • どこまで読みやすくするかのバランスで決める。


  • Q3.
    • MailAddressクラスに文字数の上限のバリデーションが入ってない。
    • 一方、DBテーブルのメールアドレスのカラムはVARCHAR(255)で定義されており、255文字までという暗黙の制限があるはず。
    • ちなみにPhoneNumberクラスは文字数のバリデーションチェックがちゃんと入っている。
    • この差は何か?
  • A3.
    • サンプルの品質がばらついてるだけ。ただ、255文字という制限は微妙。。。
    • DBの制約はかけるだけかけたほうがいい。
    • ドメインオブジェクトも、それはそれで制約を書くべき。


  • Q4.
    • Payrollクラスについて。
      • ①getterは使わないという話だったが、37行目のvalue()メソッドはgetterにしか見えない。
      • ②TimeRecordsという複数形のドメインオブジェクトではなく、List<timerecord>というリスト型を使っているのは何故か?
  • A4.
    • ①について
      • getterを無くしたい、という意図は、値を持っているドメインオブジェクト自身で計算処理をさせたいから。
      • でも、複数のドメインオブジェクトを組み合わせて計算させるとき、value()を使うかの選択の余地はある。
      • できたらvalue()やgetterは無くしたい。意味がないgetメソッドとかは書きたくない。
      • パッケージに閉じてるなら、フィールド直接アクセスもありなんじゃないか。
    • ②について
      • List<timerecord>じゃなくてTimeRecordsに修正します。
      • 言い訳をすると、このコード部分はもっと処理を持っていたが、どんどん消して、今はこうなってる。
      • AvailableTimeRecord型を戻すようにすべき。


  • Q5.
    • ドメイン層のvalidationとController層のvalidationの違いをどう考えるか?
    • 例えば、管理者権限の場合はこのチェックをするとか、場合分けがある場合とか。
  • A5.
    • SpringのBean ValidationのGroupを使うとかで対処できるのでは。
    • あとは、権限の違いで処理を分けるというのはそもそも複雑なので、別アプリにしちゃうという考え方もある。



アプリケーション層 (P.13)
  • アプリケーション層は、計算モデルのPayrollクラスを使う側。
  • 基本的にはPayrollを生成する指示を出す。それが責務。
  • ① Query サービス : 計算結果を返す
  • ② Operation サービス:計算結果の記録/通知を指示する。
  • アプリケーション層の役割はこのどちらかになる。QueryとCommand、と表現されることもある。
  • (1) serviceクラス: repositoryにautowireしてin/outする。単機能。
    • 例: ContractQueryService、AttendanceQueryService
  • (2) coordinatorクラス: repositoryには一切autowireかけない。サービスとサービスを複合する。
    • 例: PayrollQueryCoordinator は ContractQueryService と AttendanceQueryService を複合してPayroll型を1個1個作る。それらをforでループしてaddして返す。


  • DBとやりとりをする単機能の要素が(1)のServiceクラス。
  • それらを組み合わせてなにか仕事してるのが(2)のCoordinatorクラス。
  • リポジトリは入出力の関心事が入ってるので、アプリケーション層に移動しようとしてる。
  • ロジックとか分岐とかは基本的にifで分岐しない。serviceに持たせない。
  • アプリケーション層は計算をかき集めて、オブジェクトにして返す。


QAタイム (P.14)
  • Q1.
    • joinでいっぺんに取ってこれるところもこういう分け方をするのか?
  • A1.
    • 計算モデルを作るところに振り切る。
    • こういう入出力で解決できるんじゃないか、はあるが、あえてやってない。
    • 計算モデルを実行するにはどうすればいいか、という組み立てをしてる。



データソース層 (P.15)
  • 増田さんはMyBatis一択。irofさんはDomaを採用したいと思ってる。
  • SQLを生でちゃんと書くという発想を大事に。
  • アプリケーション層から見ると、selectの実行を指示するとオブジェクトを生成してくれるのがデータソース層。Javaからみたらそういう世界。
  • DBとしてどういうデータ構造を持つのかは、計算モデルとは独立だと思ってる。
  • テーブル設計は、独立して良いテーブル設計を追及したい。
  • データモデルと計算モデルは、理屈では繋がってるが、実装でいうと、関心事が一致しない。
  • SQLという道具があるんだから、それで繋ごう。
  • ContractDataSourceクラスはmapperを定義して、mapperから取ってくるだけ。



データベース (P.16)
  • イミュータブルデータモデル
    • データベースの方は、イミュータブルデータモデルという考え方を大事にする。
    • イミュータブルデータモデルについては川島さんのスライドがある。
    • まず履歴がある。履歴に最新レコードが積みあがっていく。Insertオンリー。
    • 起きた事はまずinsertする。それが積み重なって履歴になる。
    • それだけで最新状態を導出できる。それで済むならそれで終了。
    • でも最新状態を知りたいので、最新をDelete + Insertで入れておく。
    • Updateを使わない。最新状態すらもCreate + Add。
  • 型とか制約は徹底的に書くべき。
  • とことん日本語。

  • ContractMapper.xmlはテーブル名もカラム名も日本語。
  • 読んだときにドキュメントとして意味がある情報をどれだけ持ってるか。
  • コメントでなく、コードを間違えるとエラーになるようにする。




QAタイム (P.17)
  • Q1.
    • DBは日本語に全部したとのことだが、Javaのコードは日本語にしないのか?
  • A1.
    • 区分は100%日本語にしてる。メソッド名とかは踏み切れない。あとIDEの補完とかも厳しいことがある。
    • 日本語だと大文字と小文字の区別とかができない。
    • 日本語だと複数形も表現できない。例えばTimeRecordとTimeRecordsの使い分け、日本語で分けるの難しい。


  • Q2.
    • DBでイミュータブルモデルを採用するとデータ量が膨らんでしまう。それで反対とかされないか?
  • A2.
    • そういうスケール問題が発生するような規模のプロジェクトじゃない。
    • 今はメモリとかパーティショニングとか、インフラが良くなってきている。
    • Oracle 7の時代では許してもらえないが。。。
    • 無視はできないが、今ならそこそこいけるんじゃないかな。


  • Q3.
    • 外部APIとか連携する処理はデータソース層に実装する?
    • 外部APIの仕様がイケてないので変換ロジックとかどこで吸収するか?
  • A3.
    • 外部APIとかの詰め変えはトランスファー層で入れる。
    • コントローラーのプレゼンテーション層にインバウンドを入れる。
    • アプリケーション層には外部の都合は入れない。


  • Q4.
    • 日本語のテーブル設計だが、IntelliJ IDEAを使ってると、補完が2段階になって止めようと思った。。。
  • A4.
    • IDEで補完してくれるようになった時代より前から書いてるから、補完は気にならない。
    • IDEの補完は、開発の生産性にあんまり関係ないかなと思っている。


  • Q5.
    • イミュータブルデータモデルについて。CRUDは普通にやって、Triggerを使って履歴テーブルに入れていくほうが効率が良さそうだが?
  • A5.
    • Triggerは使いたくない。いろいろ嫌な目にあってる。
    • でも、事実を記録してから残高を更新する方が、業務の考え方として正しいんじゃないかな。
    • 事実の置き方の順番として、これがいいのかなと思っている。


  • Q6.
    • データソース層のエラーの扱いをどうやってるか。
    • エラーをモデルに詰めてドメイン層に返す?
  • A6.
    • データソース層でエラーが起きないようにドメイン層で頑張る。
    • データソース層でエラーが起きたときは、システムエラーくらいにしておく。そういう場合は例外を投げちゃう。


  • Q7.
    • イミュータブルデータモデルで最新をDelete + Insertするのは同一トランザクションでやってるのか。それとも結果整合性で遅延同期を許すのか?
  • A7.
    • DDDサンプルはトランザクションを意図的に組んでない。
    • 最新って複数持つことも在りなんじゃないか。レプリカとか。
    • なのでトランザクションをガチガチに組むことは今はやってない。



プレゼンテーション層 (P.18)
  • ドメインオブジェクトをそのままviewに表示させる。naked objectパターンに従う。いちいちDTOオブジェクトに詰め替えない。


Naked Objectsパターン (Wikipedia抜粋
このパターンは、次の二つの仮定に基づいている。
・良質なドメインモデルがあれば、ユーザインターフェイスは、単純にドメインモデルを反映したものになる。
・ドメインモデルを直接反映したユーザインターフェイスが必要であれば、より良いドメインモデルの設計を余儀なくされる。


  • Spring MVCでは、デフォルトではgetter/setterを要求してくる。
  • なのでDirect Field Accessをtrueにして、getter/setter無しでもいけるようにしている。

  • CSSフレームワークとしてSemantic UIを使っている。
  • クラス名なんかを物理的構造よりも意味としてマークアップするという考え方が非常に気に入っている。

  • PayrollControllerクラスではPayrollクラスを作ってmodelオブジェクトに突っ込む。
  • modelオブジェクトに突っ込むのはPayrollのListではなく、Payrollsという複数型のドメインオブジェクト。
  • payrollの複数型のオブジェクトごとmodelから渡して、viewで取り出す。naked objectパターンに従う。
  • 計算モデルのオブジェクトの参照をプレゼンテーション層に渡す。
  • プレゼンテーション層の機能でレンダリングする。


JIGドキュメント (P.20)
  • https://github.com/dddjava/Jig
  • 冶具。コードで設計するためのツール。
  • コードやドキュメントを置き去りにせず、ループに参加させる。
  • ビジネスルールをドメイン層に独立してドキュメント化する。
  • 日本語はjavadocコメントとpackage-info.javaで書いてる
  • ソースに日本語を埋めてると一覧に出せる。
  • 出てきそうな用語がクラスに抽出されている。
  • JIGでドキュメントを出すと、怪しげな名前や歪みが分かるので修正する。
  • パッケージの依存関係も生成ドキュメントから分かる。
  • コードだけ見てると部分部分はよく書けて見えるけど、JIGをかけるといろいろわかってコード改善ができる。
  • Enumの使用関係も出せる。ビジネスが複雑になるのは区分が複雑になることが多い。
  • 型の依存関係を詳細レベルで出せる。ホットなノードがわかる。
  • 相互参照があると赤字になったりする。
  • どのserviceがどのrepositoryを呼んでるか、どのrepositoryがどのクラスをinsertしてる、select、deleteしてるかを出せる。
  • 可能な限りリストにしてる。一覧形式で見ると、あれっ、と思うところが見つかる。
  • identifierとかenumとかnumberとか、使用箇所はどこで何か所あるか、とか分かる。
  • 真偽値よりenum返したほうがいいんじゃないか、とか、注意メソッドシートに出してくれる。


最後の質疑応答
  • Q1.
    • このDDDサンプルができるのにどのくらいの時間、試行錯誤にかかったのか?
  • A1.
    • 朝から晩までやって1か月くらいかな。1~2週間のレベルではない。
    • コアをどこに置くかを決めるのにまず作って会話した。まず動かすモノを作ったの大きかった。


  • Q2.
    • DBの方はイミュータブルデータモデルでupdateを使わないという話があった。
    • 一方、Javaオブジェクトはprivate もfinalも使わない、という話があったが、そうすると、書き換えられてしまうのでは?
  • A2.
    • 教育するしかないのかな。。。


  • Q3.
    • 新人研修でこっちの世界にどう導けばいいか。。。?
  • A3.
    • Javaのプログラミングの基本をちゃんとする。
    • intとintを足すとオーバーフローするでしょ。オーバーフローしないようにちゃんと型を使う。
    • booleanとintの足し算はできない、というところをきちんと教育する。
    • 型は、何が許されているのかの知識。
    • クラスを使って独自の型を定義できるのがいいんだよ、に目覚めてもらう。


  • Q4.
    • 大規模プロジェクトでDDDを使おうとして、オフショアに出したいと考えている。
    • 基本的なところは少数でやって、以降はオフショアに出したい。
    • どういうふうにやればいいいか。
  • A4.
    • 構造の部分はコアメンバーで頑張る。
    • 構造が固まった範囲でモジュール分割して、オフショアに出す。
    • でも、可能な限り少人数で密にやったほうがいい。
    • 可能な限りオフショアじゃなくて日本人で頑張ってほしい。


  • Q5.
    • DDDより形式手法を当てはめた方が、より素直に作れるのでは?
  • A5.
    • 形式手法の考え方は、静的な型付け言語では既にそうなってると思ってる。
    • でも「形式手法」だけでは伝わらない。
    • Javaがダントツでいいと思ってる
    • 型を静的に作って形式的に整合性をとる。

感想


すごく濃い話の連続でした。。。

SpringとMyBatisは普段使ってるし、こーゆうサンプルがあるとホント助かります。
DDD本の題材になってる貨物輸送、サンプルとしてはちょと題材が特殊すぎて馴染み薄すぎるねん。。。
IDDD本のアジャイルツールのサンプルも、GUIのWebアプリじゃないからイメージ湧かないし。。。

Spring BootだとTomcatや簡易DBも内蔵されてるので、サンプルとして気軽に動作させられるのもいいですね。
DDD本、ちょと抽象的な要素が多いので、このサンプルを傍に置いて読むといいんじゃないでしょうか。

増田さんはじめ、勉強会の関係者の皆様、ありがとーございました。
関連記事

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://makopi23.blog.fc2.com/tb.php/302-086bfb01
この記事にトラックバックする(FC2ブログユーザー)

-->