radioc@?

レディオキャットハテナ

【勉強会メモ】関西Javaエンジニアの会(関ジャバ) '17 10月度

kanjava.connpass.com

日時:2017/10/21(土) 13:30 〜 17:30

場所:エムオーテックス新大阪ビル 2F

今回のテーマは「現場で役立つシステム設計の原則」の著者である増田さんをゲストに迎えてのDDD特集。関ジャバですがScalaの話が多くてJavaの話は少なめ。Scala Kansai Summitのトートバッグを頂きました。

f:id:radiocat:20171021192400p:plain:w500

以前投稿済みだが「現場で役立つシステム設計の原則」は読んだものの、「理想はそうだがうまくいくのか?」という懐疑的な思いも若干あったが、パネルディスカッションを聞いてみると結局のところ、理論も大事だがどれだけ本気で向き合ってチームづくり、文化づくりができるかも重要だと感じた。その上で、DDDに向き合うためにしっかり書籍を読むなどで知識を得て自分の中に定着させておく事ももちろん重要。理論や仕組みや標準の言語仕様に依存するのが一番まずくて、それらを活用して何年もメンテして付き合っていくシステムを作るための状態を作ることが本質であると改めて感じた。

DDD失敗談を発表して学んだこと

@aa7th さん

とあるプロジェクトでDDDをやってみたがうまく行かなかった話

speakerdeck.com

もらった意見とわかったこと

そもそもきれいにモデリングしたかったのかクリーンアーキテクチャで作りたかったのか?

⇒ごっちゃにしていた

モデリングには知識の噛み砕きのフェーズが必要

⇒クライアントとの話の詰め方が圧倒的に甘かった

ICONIXプロセスというものもある

www.shoeisha.co.jp

DDDは使用とコードじゃなくて頭の中のコードと一致させる

次に機会があれば実践したいこと

  • DDDが向いているかどうか検討する
  • DDDを実践する場合、本質を見誤らないように
  • きれいな設計で作るためにDDDでやる
  • 最初からきれいなものを作ることにこだわりすぎない

実践ScalaでDDD

@crossroad0201 さん

speakerdeck.com

DDDとは

オブジェクト指向で変更が容易なソフトウェアを開発するための体系化された原則集

ドメインの知識をドメインの言葉でコードに落とし込む

参考書籍

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

実践ドメイン駆動設計

実践ドメイン駆動設計

DDDのコンポーネント

※CQRSパターンを使う場合は下記も

  • クエリモデル
  • イベントサブスクライバ
  • クエリプロセッサ

qiita.com

ScalaとDDDは相性がいい

Scalaの実装スタイル

  • イミュータブルに作る
    • 再代入不可にする
    • 値を書き換えず、別インスタンスを作って返す
  • 副作用を起こさない(局所化する)
    • 副作用…戻り値として戻らないもの
  • for内包式を使いこなす⇒ Scalaのfor文について考える - Qiita
  • implicitパラメタを活用する
    • ドメインの関心事ではないパラメタを暗黙的に渡すことができる
    • ドメインロジックからノイズを排除する
  • case classのcopy()は外から使わない
  • 変数はむやみに略さない
  • Option型の変数はmaybeを付ける
    • 値が無いかもしれない変数はmaybeを付けるとわかりやすい
    • Scalaでは型推論で型を省略することが多いので変数名に気を使う
  • 自動コードフォーマッタを利用

アーキテクチャ

レイヤ構成

  • ドメイン
  • アプリケーション
  • インフラストラクチャ
  • インターフェース

レイヤ化アーキテクチャ

ドメイン中心アーキテクチャ

  • ドメインから副作用排除
  • アプリケーションはインフラストラクチャに依存したまま

オニオンアーキテクチャ

  • ドメインもアプリケーションもインフラストラクチャに依存しない
  • インターフェースでアプリケーションとドメインにインフラストラクチャをDIして使う

※参考

qiita.com

エラー処理

Scalaの値をラップする型

  • Option
    • Some or Noneのいずれか
  • Either
    • Right or Left⇒予測可能なエラー
  • Try
    • Success or Failure⇒予測不能なエラー

レイヤごとに発生するエラーの特性がある

ユーザー向けにはユーザーフレンドリーなエラーを返したいが、ドメインやインフラストラクチャでユーザーインターフェースを意識すべきではない

⇒上位レイヤでユーザーフレンドリーなエラーに変換する必要がある

コンポーネントの実装

アプリケーションサービスのコードが仕様書(ユースケース記述)になることを目指す

アプリケーション

  • ドメインレイヤを使ってユースケースを実現することが責務
  • ドメインの知識がアプリケーションサービスに漏れ出さないようにする
  • インフラストラクチャレイヤに依存しない
  • メソッドの処理の流れをfor内包式でつなげる

エンティティ

  • 集約の中でエンティティは1つ
  • プリミティブな型は使わない
  • ドメインの振る舞いはエンティティのメソッドとして実装

バリューオブジェクト

  • タイプエイリアスは型安全にならないのでちゃんと型を作る
  • オブジェクトの集合はファーストクラスコレクションを作る(ListやSq,Mapをコレクション型のまま扱わない)

ロールオブジェクト

  • 集約をまたいだエンティティとエンティティの関連をモデル化
  • implicit classで定義することでエンティティを自動的にロールオブジェクトに変換
  • エンティティの肥大化を防ぐ

ファクトリ

  • エンティティの生成は2パターン
    • 単独で生成⇒コンパニオンオブジェクトにファクトリメソッドを実装
    • 他の集約のエンティティから生成⇒ロールオブジェクトにファクトリメソッドを実装

DDDの実践

さらにこの先にあるもの

  • イベントソーシング
    • ドメインオブジェクトを永続化してデータをUPDATEするのではなくイベントを永続化し取得時に順番に再適用することでドメインオブジェクトの状態を復元する
  • アクターモデル⇒Akka Streamを使う

qiita.com

  • DDDに正解はない
    • MSAならコンテキストごとにサービスを分割して開発するので新しい学びを段階的に導入しやすい

各レイヤーからのエラーについて考える

@yoshiyoshifujii さん

speakerdeck.com

エラーの分類

  • ユーザーの操作起因
  • システムの障害起因

ユーザーの操作

  • 契約に違反するような操作
  • 必須、長さ、型、重複
  • 画面側で確認
  • APIを直接呼び出すことを想定

システムの障害

  • IO例外
  • Network、System Down
  • バグ
  • ぬるぽ

HTTPステータスコード

  • ユーザー操作…400番台
  • システム障害…500番台

ドメイン駆動設計


ドメイン層のエラー

  • 契約違反
  • 必須
  • 長さ、短さ
  • 重複
  • 型チェック
  • DomainError型

リポジトリドメインと同じ?⇒リポジトリの責務を考える

  • ドメインの永続化
  • 再生成
  • I/O例外をよく扱う
  • 楽観的排他とNotFoundを型で表明

ファクトリはドメインと同じ

イベントパブリッシャ

  • ドメインイベントのパブリッシュ
  • 契約違反の検証は終わった状態
  • I/O例外のみの考慮で良い

ドメインサービス

ドメイン層のエラー型

  • DomainError型
  • RepositoryError型
  • 例外(Try)

インフラストラクチャ層

イベントパブリッシャ実装

  • 例外をそのまま扱う
  • 特別に型を定義して扱わない

インフラストラクチャサービス

  • ドメインで表現する必要のないインフラ操作
  • ファイルアップロードをテンポラリーに一時保存
  • 暗号化/復号化

インフラストラクチャ層のエラー型

  • RepositoryError型
  • 例外

アプリケーション層

アプリケーションサービス

Convertを整理

  • DomainError⇒ApplicationError

エラー型を表明したことでConvertが大変 全てApplicationErrorにする Convertをうまく実装しないと辛い


インターフェース

Application ErrorをConvertする

HTTPステータスに変換

HTTP以外のインターフェース

  • Subscriber
  • Reciever
  • Command Line Interface

インターフェースに依存する

  • インターフェースがHTTPならHTTPにする
  • Sub/RecieverならSkipかRetry

isolating-the-domainの紹介

@haljik さん

speakerdeck.com

Isolating the domainとは

エリック・エヴァンスドメイン駆動設計における第2部第4章のタイトル

ドメインを隔離する」をプロジェクトにしたもの

github.com

  • 実際のプロジェクトの雛形として利用
  • チーム内での共通認識を持つための道具でもある
  • 名前の通りドメインを隔離するアーキテクチャ

読むべきところ

設計ガイドのwiki

Home · system-sekkei/isolating-the-domain Wiki · GitHub

骨格となるパッケージに配置されたpackage-info.javaの記述

⇒いかにしてドメインを隔離しているか

骨格となるパッケージに配置されたpackage-info.javaの記述

いかにしてドメインを隔離しているのか

ドメインで定義したインターフェースの実装をインフラストラクチャ層で実装しています

実際のプロジェクトではある程度の規模になるとほとんどの場合はマルチプロジェクト構成になる

参考資料

speakerdeck.com

speakerdeck.com

設計のスタイル、開発のスタイル -ドメイン駆動設計を実践するために、知っておきたい基礎知識

@masuda220 さん

本日のキーワード:型

  • 言語に用意された型…boolean,int...
  • Java標準ライブラリに用意された型…String,BigDecimal,...
  • 独自に型を定義する仕組み…class,interface,enum,package

値の集合+操作の集合

  • boolean型
    • true,falseの2つだけ
    • 可能な操作は論理演算だけ(Javaの場合)
  • LocalDate型
    • 正しい日付
    • 日付演算

プログラミングの2つのスタイル

  • 型の利用者
    • 言語の組み込み型を使う
    • 標準ライブラリの型を使う
    • 誰かが作ってくれたユーザ定義型を使う
  • 型の生産者
    • 基本データ型を使って独自の型を定義する
    • 独自の型を組み合わせてさらに型を定義する

オブジェクト指向アンチパターン

  • 基本データ型への執着⇒諸悪の根源
  • 長いメソッド
  • 大きなクラス
  • たくさんの引数
  • コード重複
  • 変更の分散

リファクタリング 不吉な臭いより

オブジェクト指向良い習慣

  • 基本型をラップして独自の型を定義する
  • コレクションをラップして独自の型を定義する

基本データ型より独自の型が良い理由

  • 正しさ
  • 表現力
  • Find Usage

正しさ

  • 基本データ型
    • 値の範囲が広い
    • 可能な操作が汎用的
    • 特定の目的のためには、間違った範囲、間違った操作を含んでいる
  • ユーザー定義型
    • 値の範囲を用途に合わせて制限する
    • 可能な操作を用途に合わせて限定する
    • 間違いが減る
    • 安全・安心

表現力

  • 基本データ型
    • いろいろな用途に同じ型を使う
    • 10種類ぐらいの単語(型)だけでなんでもかんでも説明されても意味がわからない
  • ユーザー定義型
    • 用途が具体的で明確な型が増える
    • 引数の型、メソッドの型、シンプルなメソッド名
    • 型をレビューすればコードの内容を推測できる(わかっているか、勘違いしているか)

intelliJのdiagramで確認できるのは便利

Find Usage

intellijリファクタリングでプレビューする

ポリモーフィズム

良い単純化、良い部品化に役立つ仕組み(型を使ったポリモーフィズム

ポリモーフィズムが何かは基本的にはあまり重要ではない

-(暗黙的な)型変換 - long+int, "value:" + int - int<->Integer - オーバーロード - BigDecimal(int), BigDecimal(String),BigDecimal(BigInteger),... - 型変換の責任を使う側から使われるクラスに移動する - 型のパラメータ化(ジェネリクス) - List - さまざまな型に利用可能な原型を提供する - 部分型(subtyping) - interface宣言、enum宣言 - 型のグルーピング:同じグループの型を、同一の型として利用する仕組み

型の使い方について

  • どの仕組みもとても便利
  • しかし誤った使い方は事故の元
  • 上手に使うのが設計の腕の見せどころ

設計のスタイル

  • モジュール化
    • 型(データ+操作)に分解するか
    • 手続き(サブルーチン)に分解するか
  • 独自の型
    • 積極的に独自の型を定義するかできるだけ基本データ型でがんばるか
  • 型の記述
    • 明示的に書くか、型推論に頼るか、暗黙化か(型を書かない・型を書けない)
  • 型の検査
    • 設計中(IDE)、実行前(静的)、実行時(動的)

ドメイン駆動設計の開発スタイル

  • ドメイン(問題領域)に特化した型の探求活動
  • 良い型は最初からは見つからない
  • 実験的に探求する

良い型は最初からは見つからない

  • 問題領域の知識が不足している
  • 問題領域を深く分析できない
  • 型でビジネスロジックを表現する設計パターンの知識不足/経験不足
  • 初期の型の使い勝手の悪さ⇒ソフトウエアはだいたいそう
  • 型の成長性(変更容易性)⇒使っていく中で成長させる

実験的に探求し進化させる

  • 型のアイデアの発見
    • 雑談、ラフスケッチ⇒これがきっかけになることが多い
  • 型のアイデアをコードで書いてみて試してみる⇒考えただけでは仕方ない
    • だめなら捨てる
    • いけそうなら改善を続ける
  • もやもやしたら別のアイデアを試してみる⇒何が正解かはわからないけどよくなさそうだと感じることは多い
    • 新たな発見 and/or 元のアイデアの再評価
  • 小さなステップの繰り返し
    • イデア出し、実験、改善にdoneはない
    • タイムボックスで先に進む⇒自分で時間を決める、延長しない(あと少し考えてみたらできそうというのは罠)

⇒最初から見積もりやここまでできそうという話をするならある程度実験してみる作業を見込んでおく。

ドメイン(問題領域)に特化したユーザー定義型の見つけ方

ドメインに特化したユーザー定義型

  • 汎用の基本型の用途を限定
    • 値の範囲
    • メソッドの型
    • メソッドの名前

↑これが一番最初にやることで最も重要

型の振る舞いの設計パターン

  • 判定(同値、大小、最大/最小)
  • 加工(型変換、短縮形、合成)
  • 計算(四則演算)

↑どういう振る舞いがあるか実験してみる

既存のアイデアの流用/カスタマイズ

  • 分析パターン⇒これはずっと後
  • 過去に経験したシステムの設計内容
  • データモデリングのカタログ

↑有名な本、モデリングなどを使う

ユビキタス言語

  • 業務の用語
  • 画面
  • 業務マニュアル

ユビキタス言語を使えば良い設計ができるわけではない

参考

Java SE Specifications 4.2と2.10

型システム入門

⇒1章の無料サンプルがとても良い

オブジェクト指向入門 by バートランドメイヤー

オブジェクト指向入門 第2版 原則・コンセプト (IT Architect’Archive クラシックモダン・コンピューティング)

オブジェクト指向入門 第2版 原則・コンセプト (IT Architect’Archive クラシックモダン・コンピューティング)

Simula - Wikipedia

パネルディスカッション

speakerdeck.com

Q: 知識がない人がいる中でどうやって広めるか

  • システムを作る目的はあるはず⇒そこから想像する
  • 具体的に想像できるようになると話がしやすいかも
  • 口頭だけで伝えるとわからなくなる
    • 書いてもらうと伝わるようになる
  • 言葉が実際の動きと合ってないこともある(クライアントがうまく言語化できていない)
    • 実際に動いているものを見てもらう
  • 法律が絡む場合は法律を知る(制約があるケース)
  • 紙に図を書いてみる。
  • 設計だけ先にしないといけない場合は辛いかも
    • 概念レベルのクラス図、ユースケース図ぐらいなら作れるかも
    • 繰り返しの発見のプロセスがあるので気づいたことをお互いに言える環境が重要

Q:どういうふうにDDDにたどり着いたか

  • 大炎上プロジェクトに入った
    • 3000行のSQL
    • マルチクライアントで複数の会社に提供
    • A社の画面がB社に見える
  • アーキテクチャやモデルを参考に
  • 効果があったのはリファクタリング⇒即効性があった
  • 手続き型に戻ることはなかった
  • 炎上していたので何をしても自由だった
  • 設計を変えていくのが楽しかった
  • 技術力やパフォーマンスの発揮で解決できる面もある
  • 画面ドリブンで設計してはダメ
    • モデルから設計したほうがいいと思ってたまたまDDD本を読んだ

Q:どういうところに取り入れるか

  • インフラストラクチャ寄り
  • パフォーマンスを要求されていないところ
  • バックオフィスを考えると複雑になるケースはある
  • 問題領域を区別してプログラムに反映するという意味では向いていない領域はない
    • 実装上は向かないことはあるかも

Q:DDDと負債化

  • ボーイスカウトルールしかないかも
  • Scalaで置き換えたが10年戦える仕組みを手に入れたかというと不安
    • 2,3年なら思想や文化を知っているメンバーがいる
    • 思想や文化は重要
    • 自分が抜けた時にどうなるかは不明で不安がある
  • 納品した後でお客さんが触り始めるとぐちゃぐちゃになるかも
  • 10年の長いスパンで考えたときにソースコード自体よりもソースコードから読み取れる知識が重要
  • Scalaって10年後にコミュニティ大丈夫か?
    • コミュニティ含めて支えられると判断する
    • チームづくり⇒文化が残るようなチームにすることを重要視している
    • 自分が抜けるにはどうしたらいいか考える
  • スキルが無い人に渡してもうまくいくというのは無理がある⇒渡せるチームづくりが先

Q:テーブルの再設計ができない場合はどうするか

  • DDDで言うとインフラストラクチャが複雑になる
  • ドメインモデルへのConvertが複雑になる
  • テーブルとの結合度が複雑なフレームワークを使わなくする
  • リード用のテーブルを用意する
  • 名前がflagなのに区分になっている⇒テーブルを変えるの大変なのでモデリングでなんとかする
  • リポジトリパターンの中に隠す
  • データベースのリファクタリングも考える
    • ロギングのような形で今のテーブル設計とは別で操作を記録する新しい設計のテーブルに入れる
    • テーブルをビューで名前を変える
    • 最終的にテーブルを変えれない、しょうがない事はあるかもしれないがマインド的にはリファクタリングする方向で動く
  • 頑張ろうとした形跡はあるけどメンバーが変わって途中で止まる⇒ 負債ではなく財産ととらえる

Q:短期的な視点と長期的な視点のバランスについて

  • モデルの構造を変えざるを得ないことも起こる
  • ある程度長期的な視点で設計しておかないと破綻することがある
  • バリューオブジェクトの負債化
    • DDDじゃなくても時間がかかるかも
    • DDDのほうが発見しやすくなる
  • 100個中100個が全てだめになるというようなことは経験上ない
    • 組み換え可能な設計にしておく
    • 独立したものをどれだけ小さくできるかが重要

Q:DDDの失敗談

  • きれいにやろうとしすぎた
  • お客さんがちゃんと見てくれていなかった
    • 開発後半になって色々言われた
    • ある程度作るまでフィードバックがもらえなかったのはなぜ?
      • アナログなツールに頼ったほうがよかったかも
      • もっと一緒にやる姿勢を見せる
  • DDDも設計の外側のコミュニケーションが重要
    • どうやって話をするか認識を揃えるか
  • 1回作ったコードは二度と触るなみたいな文化もダメ
  • 日本語も英語も両方使えるようにする

Q:メンバーがDDDへの関心が薄い場合はどうするか

  • ドメインに興味を持っている持っていないという問題ではない
  • 役割や責任が軽すぎると関心がなくなる
  • DDDならではの不安
    • 自分の思考がコードに現れる
    • 設計のブレが現れやすくなる
  • 保守を経験すると違うかも
  • システムのライフサイクルを一通り経験しないと難しい
  • もっと良くしたいという状況を経験すると変わる
  • 自分が変わる、レベルアップする
  • ドメインに興味が無い人は切る
  • チームの文化が変わって主流になれば仲間が増える
  • 圧倒的なパフォーマンスを見せる