Webアプリケーションサーバーのアーキテクチャとテスト設計
Web アプリケーションサーバーでは3層アーキテクチャ[1]を前提とした Web アプリケーションフレームワークの構成に従うことで, ほとんど悩まずにアプリケーションを構築できます. しかしこれらの構成には実質的に制限がなく, 毀損してしまうような事態が生じ得ます. そこでこの記事では3層アーキテクチャよりも厳しい制限を課したアーキテクチャを考えます. これによってアーキテクチャの違反を機械的に検知するだけでなく, テスト容易性を獲得します.
このアーキテクチャでは非同期処理などを考慮していませんが, 構成が変わっても原則は適用可能です.
アーキテクチャの要素
今回考えるアーキテクチャは3層アーキテクチャを改良したものですが, 関数の作用・副作用を中心に考えます. 副作用(隠れた入出力)を特別なものとして扱い, 副作用を持つ処理をレイヤーとして分離します. これによりレイヤーごとにテスト容易性を考えることができます.
このアーキテクチャはプレゼンテーション層, オーケストレーション層, ロジック層, データ層を持ちます. 隠れた入出力を持つのはHTTPリクエストを受け持つプレゼンテーション層とデータベースアクセスと外部APIアクセスを持つデータ層だけです.
要するに関数型プログラミングによるヘキサゴナルアーキテクチャです.
プレゼンテーション層
プレゼンテーション層はHTTPリクエストの処理に責務を持ちます. いきなりですがこのレイヤーではほとんどコードを書きません. のちに続くオーケストレーション層にわたすデータを整えたり, HTTPレスポンスを生成したりするのが責務です.
コードを書かないようにするためにはどうするかというと, コード生成を使います. 一般的なHTTPリクエストを扱う場合は OpenAPI Generator[7] が利用できます. OpenAPI による定義をきちんと行うことでHTTPリクエストに関するハンドリングロジックのほとんどが書かなくてよくなります.
テストはサーバーを立ち上げて行う Web API テストで担保します. なぜこのレイヤーではコードを書かないことに注力しているかというと単体テストを書か なくてすむようにしたいからです.
オーケストレーション層
オーケストレーション層はロジック層とデータ層を利用してエンドポイントをまとめ上げる役割です. ロジック層とデータ層の関数が上から下へと手続的に流れていくようなコードを目指します. ロジック層やデータ層の関数の実行結果によって条件分岐ができる場合もありますが, このレイヤーも基本的にはロジックを書かないようにします.
リクエストパラメーターをデータベースにクエリするだけのようなエンドポイントではロジック層がなくデータ層だけという場合もあります.
逆にロジック層を複数種類・複数回実行する必要がある場合もあります. このような場合は関数の合成などを利用します. Chain of Responsibility, Visitor, Strategy パターンなどを利用します. ロジック層とデータ層とのやりとりが複雑に絡み合っているような場合は高階関数などを使って原則を守るようにします.
どうしてもこれらの原則を守れない場合は守れない部分だけを隔離しましょう. この違反部分の凝集はテスト容易性のために必要な凝集で, テストが書けるかどうかは変更容易性の直結します. 違反部分は違反部分だけでテストを書きましょう.
このレイヤーのテストは medium test として実装します. エンドポイントの処理全体に関わるテストになるため, いわゆる統合テストとなります. データベースや外部APIなどデータ層の隠れた入出力のためのテストダブルは全てフェイクを利用します. スタブやモックは利用しません.
ロジック層
ロジック層は主に必要なデータの変換を行うレイヤーです. データの変換のみに限定しているのはロジック層は隠れた入出力を持たず関数の引数と戻り値だけ(つまり純粋関数)で責務を扱うためです.
ロジック層からロジック層への接続(依存関係)は許されますが, ロジック層がデータ層を呼び出すことは許されません. データベースの値を変換したい場合も必ず引数と戻り値でやりとりします.
このレイヤーのテストは全て small test となります. 関数の引数と戻り値に関する検証となるため非常にシンプルにテストできるはずです.
データ層
データ層はデータベースの操作や外部APIへのアクセスなどを行います. 異常系などのハンドリングは行いますが, それ以外のデータの変換はロジック層に任せます.
このレイヤーに対して単体テストは行いません, としたいところですが, そうはいかない場合があります. データベースのクエリが複雑だったり外部APIのリクエストが複雑な場合はテストを書くべきでしょう. このあたりの個別の判断はチームで合意を取りましょう.
テストを書く場合はオーケストレーション層と同様に medium test として実装します. この場合もスタブやモックを利用せず, フェイクを使います.
認証・認可がある場合
エンドポイントに認証・認可がある場合, エンドポイントの責務となるロジックとは別に認証・認可のロジックが必要です. 一般的な Web アプリケーションフレームワークでは認証・認可ロジックをエンドポイントから隔離することができますが, そういったことができない場合やエンドポイントに個別の認証・認可が必要な場合もあるでしょう. このような場合は認証・認可のロジックに対してオーケストレーション層, ロジック層, データ層を用意します. 認証・認可に関するオーケストレーション層を通ってから, エンドポイント固有の処理に関するオーケストレーション層で処理を行うことになります.
この記事では隠れた入出力に注目して3層アーキテクチャを見直すことで, より厳格なアーキテクチャを考えました. その結果レイヤーの特性に合わせたテスト設計ができ, テスト容易性の高いアーキテクチャとなっています. 現代のソフトウェア開発では自動テストは非常に重要な地位を占めています. このアーキテクチャはテスト設計を含めたソフトウェア開発全体に関わる設計をもたらします.
-
エンタープライズアプリケーションアーキテクチャパターン, Martin Fowler, 長瀬 嘉秀, 翔泳社, 2005 https://www.shoeisha.co.jp/book/detail/9784798105536 ↩︎
-
関数型ドメインモデリング, Scott Wlaschin, 猪股 健太郎, アスキードワンゴ, 2024, https://asciidwango.jp/post/754242099814268928/関数型ドメインモデリング ↩︎
-
13章 テストダブル, Googleのソフトウェアエンジニアリング―持続可能なプログラミングを支える技術、文化、プロセス, Titus Winters, Tom Manshreck, Hyrum Wright, 竹辺 靖昭, 久富木 隆一, オライリー・ジャパン, 2021 https://www.oreilly.co.jp/books/9784873119656/ ↩︎
-
単体テストの考え方/使い方 プロジェクトの持続可能な成長を実現するための戦略, Vladimir Khorikov, 須田 智之, マイナビ出版, 2022 http://book.mynavi.jp/ec/products/detail/id=134252 ↩︎
Discussion