JJUG ccc 2019 spring の感想、まとめ
今回も行きました。
最近は仕事で Java を触る機会が増えていたことに加え、 Clean Architecture を読んだり、開発リーダーをやったりしたことで 「設計やQAも含めて、どう進めていくのが良いのか?」を考えることも増えたので、 アーキテクチャやQAなどの話を中心にブログを書こうと思います。
セッション一覧
- Java で クリーンアーキテクチャ 〜 ゼロから始める先行開発 - @nrslib(GMOインターネット)さん
- テストエンジニアが教える JUnitを書き始める前に考えるべきテスト - ブロッコリー(風間祐也)さん
- マイクロサービス:4つの分割アプローチの比較 - 増田 亨(ギルドワークス)さん
Java で クリーンアーキテクチャ 〜 ゼロから始める先行開発 - @nrslib(GMOインターネット)さん
想定しているケース
以下のような「後から関連モジュールが開発/変更される」ケースを想定しています。
(もちろん、「理想」の開発でも今回のような開発の進め方は有効です。)
- 「理想」とは違う順序で開発しなきゃいけない
- API開発より前にフロントを開発しなきゃいけない
- ※BFF(Backend For Frontend)の文脈での『フロント』なのでサーバーサイド開発も含む
- DBの選定が終わっていない
- API開発より前にフロントを開発しなきゃいけない
- UI やデザインなど、試行錯誤による修正が予測されている
- プロトタイプを早くから開発したい
ヘキサゴナルアーキテクチャとクリーンアーキテクチャ
「関連モジュールの開発/変更をクリティカルパスとせずに先行開発をしたい」というモチベーションに対して、 クリーンアーキテクチャを採用しましたが、ベースとなるアイデアとして「ヘキサゴナルアーキテクチャ」があります。
ヘキサゴナルアーキテクチャは、ビジネスルール(Application)を中心に、WebやDBなどの関連モジュールを「Port & Adapter」で接続するアーキテクチャです。
「Port & Adapter」でビジネスルールを呼び出すアーキテクチャにすることで、関連モジュールの変更がビジネスルールのロジックに影響を与えないように出来ます。
クリーンアーキテクチャは、この「Port & Adapter」の具体的な実装方針を示したアーキテクチャです。
DIP(依存関係逆転の法則)を適用しながら、Device/Web/DB -> Controller/Gateway -> Use Cases -> Entities と内側に向かって依存するように実装します。
具体的な実装例、嬉しさと辛さ
実装例はスライドがめちゃめちゃ分かりやすいので、そっちを見てください。
- 嬉しさ
- Input/Output Boundary にDIPを適用したことで、Mock/Stubによる差し替えが可能
- 「予測されないエラー」をWebに返すためのStubを作れる
- Data Access Interface にDIPを適用したことで、Data Store の差し替えが可能
- Input/Output Boundary にDIPを適用したことで、Mock/Stubによる差し替えが可能
- 辛さ
- フレームワーク(Play Framework) で、返り値を返さないと Presenter(Output Data -> View Modelの変換役)が動かない
- 対策: Presenter を捨て、Controller で Output Data を扱うようにすることで、「ビジネスルールの実装を守る」という目的を果たす
- Inject Hell
- 対策: Message Bus パターンを利用して、Input Data => Use Case のハンドリングを実装する
- 退屈(になりがち)なコードを大量に書く必要があるフレームワーク
- 対策: 自作ツール(NORIO)を利用して Scaffold する
- フレームワーク(Play Framework) で、返り値を返さないと Presenter(Output Data -> View Modelの変換役)が動かない
そもそもアーキテクチャの役目とは?
- 実装のためのレールを用意する
- 一定のアーキテクチャに従うことで「探索のコスト」を減らすことが出来る
感想
- 最近、Clean Architecture を読んだのもあり、非常に分かりやすくためになるセッションでした。 ヘキサゴナルアーキテクチャとの対比で捉えるというのも分かりやすかったです。
Scaffold を始めとした開発効率を上げる工夫とセットでアーキテクチャを提供するというアイデアは 「分かっていてもジュニアには出来ない」部分で、シニアエンジニアのバリューを発揮できるところだなぁと思いました。
懇親会でもお話を聞かせて頂きました。他のモジュールや部署の影響を受けやすい中でも 「アーキテクチャを守りきる」というお話が非常に印象的で、勉強になると同時に刺激をもらえました。
テストエンジニアが教える JUnitを書き始める前に考えるべきテスト - ブロッコリー(風間祐也)さん
テストの目的とは?
- 欠陥の検出
- 品質が充分(≠ 完璧)であることを確認する
- 意思決定のためのインプット(リリース出来る?して良い?)
- 欠陥の作り込みの防止
また、 仕様誤りの修正コストは要件定義時に1だったものが、リリース後には200になります。
「何をテストするのか」「どうテストするのか」という観点から、 「実装前に」仕様を確認することで開発の総コストを下げることが出来ます。
ワーク - パスワード認証画面のテストを考える
- 4~12 文字の英数字を許容する
- 3分以内に4回以上パスワードを間違えると5分間ロックする
- 「許容する」とは?エラーメッセージ?エラー画面?
- 仕様の認識齟齬によるバグが発生するリスク -> 追加コストに繋がる
実装する前に仕様を確認することで追加コストを抑えることが出来ます。
また、良いドキュメントを設計段階で作成することで、コストを抑えることが出来ます。
テストケースの作り方
- 全ケースをテストすることは不可能 -> テスト設計技法を用いる
- 合理的に少なくする
- バグは偏在する
- 境界値分析
- ゼロ値、null 値
QAチームの役割、開発者の役割
- QAチームはシステムテストレベルのテストをしたい
- 単体テスト、結合テストレベルは開発者が担保すべき領分
- Checking と Testing
- Checking: 意図通り動くか確認する -> 開発者が担保すべき
- Testing: なんとかして壊そうとする -> QAチームが担保する
感想
内容としては難しくないものの、非常にためになる話でした。
仕事でも「事前にテストケースを見ておけばなぁ」と思うようなバグが発生することが多かったため、 実装/レビューの前にテスト項目を大まかに持っておくことでトータルコストを抑えられるということは強く賛成できます。
QAチームとの役割分担についても、直近のプロジェクトでは開発チームで打鍵も含めた結合テストを事前に実施しましたが、 コミュニケーションコストが低い状態で「バグ検出 -> 修正 -> 確認」を行えたため、全体のコストという面では非常に良かったと感じています。
「開発の進め方」という意味で、非常にためになるセッションでした。
マイクロサービス:4つの分割アプローチの比較 - 増田 亨(ギルドワークス)さん
マイクロサービスへの移行
- マイクロサービスを技術的に支えるのはクラウド、コンテナ、設計スキル
- クラウド、コンテナはあくまで必要条件。設計スキルと合わせることで十分条件となる。
- マイクロサービスへ移行するモチベーションは負荷分散、リスク分散、データ分散、機能分散
- 前の3つはあくまで非機能に関わる部分。機能分散を考えるにはサービス群を分けるための業務理解/設計スキルが重要
- 基本的なフローは、まずモノリスとして作り、分散の検討、実施を部分的に進めていく
- 分解の検討は基本的な設計と同様に考える
- 関心の分離、高凝集、疎結合、モジュール化
- ビジネスルールを中心に据えて、inbound, outbound, DataStore へのインターフェースを考える
- インターフェースは同期的な req/resp よりも非同期な pub/sub, queue の利用が好まれる
サービス分割の考え方
- どこまで小さく「出来る」か
- 理論的にはAPI単位だが、実践的には設計/インフラ/運用の習熟度が限界を決める
- どこまで小さく「する」か
- 論理的なモジュール分割は積極的にやるべき
- 物理的な配置と運用の分割は慎重にやるべき
- 通信が多くなることへのコスト/リスクを軽視しない
4つの分割アプローチ
大きく以下の4つのアプローチがあります。
- ビジネスファンクション: 業務機能
- ユースケース: 動詞
- リソース: 名詞
- 境界づけられたコンテキスト
これらを組み合わせて分割の設計をしていきます。
ビジネスファンクション
- Good: 業務の活動単位をシステムの単位に反映できる
- Bad: 実際のビジネスの複雑さがシステムの複雑さに反映されてしまう
- 企業サイズによって、分割の単位が変わってくる
- 大企業はより細分化している -> 大企業病がシステム開発/運用に持ち込まれる
- 小企業はそれほど細分化していない -> 業務機能をまたぐ要求が出てくる
- 削除/キャンセル時のデータの一貫性の保持が課題となってくる
- 上流でのキャンセルを下流に伝搬させる、下流データの削除を上流に伝搬させる
ユースケース: 動詞
- Good: 機能要求を定義しやすい
- Good: 開発範囲を定義しやすい
- Bad: トランザクション単位のマイクロサービスはレガシーになりやすい
- サービス間のロジック重複、断片化
- 重複/断片化を処理するためのバッチ処理が重くなりがち
- ロジックの分割、共通化の設計が課題となってくる
リソース: 名詞
- Good: エンティティ + CRUD という伝統的なデータストアの構造を利用できる
- Good: データ更新に責任を持つサービスを明確にできる
- Bad: 異なる関心事が分離できず、肥大化しやすい
- Join を出来ないことが重複/肥大化に拍車をかける
- 関心事に応じたリソースの再設計が課題となってくる
境界づけられたコンテキスト
- そもそも、どういうことか分からない...
- エヴァンスとバーノンで、どうも「コンテキスト」と「サブドメイン」の定義が違う
- エヴァンス: コンテキスト > モデル > サブドメイン
- バーノン: ドメイン > サブドメイン > コンテキスト
- エヴァンス流の定義の方がマイクロサービスの設計のセオリーに合っていそう
- エヴァンス流の定義
- コンテキスト: 一つのモデルを一貫して適用できる範囲
- 境界の実体はチームやDBのスキーマの変更可能範囲となる
- サブドメイン: コンテキストの中の凝集性が高い範囲
- コンテキスト、サブドメインともに分割の単位となりうる
- 実践的には、サブドメインは設計の中で見直されることが多い
- サブドメインを分割の単位にしないほうが設計の継続的な改善を進めやすい
- コンテキストが「一つのモデルを一貫して適用できる」状態を目指す
- ビジネスルールの複雑さをシンプルに説明できるモデルを探す
- ビジネスルールを中心に据えて、インバウンド、アウトバウンド、データソースを司るサービスを作っていく
- この設計の中でサービス分割の検討を進めていく
感想
増田さん自身も「まだ模索中」とおっしゃっているように、難しい問題だと感じました。 ですが、考え方のエッセンスの部分で非常に勉強になるセッションでした。
個人的に刺さったのは「どこまで分割できるか」と「どこまで分割するか」を分けて考えていたことです。 「設計の改善」という意味ではモノリスの方が取り回しが良いケースが多いことを考えると、 物理的な移行はかなり不可逆なもので有ることを意識すべきだということが分かりました。
トランザクションなどの非機能的な観点も考えると技術的・運用的な観点での難易度も高くなるので、 開発効率との見合いをどのように評価するか、も大きな課題だと感じました。
終わりに
足元で自分が悩んでいる/悩んでいた問題と、今後チャレンジしてみたい問題の両方について面白い話を聞くことが出来ました。 前回よりも共感/納得をしながらセッションを聞くことが出来たので、より面白く感じました。 また、懇親会で色々な方の現場事情やキャリアの話を聞くことが出来ました。(転職経験者の多さ!) そういった意味でも普段は聞けない話を聞くことが出来て、刺激と知識をもらえた一日でした。