概要説明
弊社にて運用している口コミサービス エキテンはサービス開始の2007年から14年間運用を続けております。
10年以上運用されているサービスというと技術的負債が溜まりに溜まりがちになるかと思いますが、エキテンも例に漏れずサービスの成長に比例して技術的負債が少しずつ積もっていきました。例えばエキテンは大きく分けてユーザー様が店舗を探すサービス、店舗の管理者様が自ページを管理するサービス、エキテン運用者がエキテンの管理をするサービスの3つから構成されており、これらのサービスが密結合しているためサービスへの改修が他のサービスへの改修に影響するといったことが日常茶飯事です。
そんな技術的負債を返済するということを目的の1つとしてリニューアルプロジェクトが発足しました。今回の記事ではリニューアルプロジェクトにおけるクリーンアーキテクチャの構成について紹介します。
クリーンアーキテクチャとは
クリーンアーキテクチャはRobert C. Martin(Uncle Bob)が2012年に提唱したアーキテクチャであり、説明にしばしば以下の図が用いられます。
同心円の部分はクラスを以下の4つの層に分け、それらの層の依存関係を説明したものとなります。右下の図は依存関係に則ったクラス図となっております。
クリーンアーキテクチャにおける4つの層
- Enterprise Business Rules(モデル層)
- システムでソフトウェア化したいモノ(ドメインモデル)に注目し、その制約や構造、ビジネス的な振る舞いを記述する層。他の層への依存は許可されない。
- Application Business Rules(サービス層)
- システムを利用するユーザーの動作(制御)を記述する層。「入力」「永続」「出力」を抽象的に定義し、それらとモデル層のモデルたちを組み合わせることで、ビジネス的に達成したい処理を記述する。後述するInterface Adapters、 Frameworks & Driversへの依存は許可されない。
- Interface Adapters(アダプター層)
- サービス層で抽象的に定義された「入力」「永続」「出力」をポートととらえ、アダプターとしてそれらを実装する層。後述するFrameworks & Driversへの依存は許可されない。
- Frameworks & Drivers(インフラ層)
- フレームワークやライブラリ、データストアなどシステムの外界に存在する要素が存在する層。
個人的な見解
一般的には上記のように説明されることが多いかと思うのですが、私としての見解はクリーンアーキテクチャはSOLID原則である「単一責任の原則」、「オープン・クローズドの原則」、「リスコフの置換原則」、「インターフェース分離の原則」、「依存性逆転の原則」に則り、依存性・凝集度・結合度が整然としたアーキテクチャのことを指すと考えます。
実際、『Clean Architecture 達人に学ぶソフトウェアの構造と設計』で以下のように述べられています。
図の円は概要を示したものである。したがって、この4つ以外は認めないというルールはない。
したがって、これから説明するリニューアルプロジェクトにおけるクリーンアーキテクチャの構成が必ずしも図の円に従ったものではないことを先に明言させていただきます。
リニューアルプロジェクトにおけるクリーンアーキテクチャの構成
クラス構成図
リニューアルプロジェクトにおけるクリーンアーキテクチャのクラス構成図がこちらになります。
これを「入力」「永続」「出力」の観点で分解し、個々のコンポーネントの紹介をします。
入力
Controller
Interface Adaptersに属します。
フレームワークにはLaravelを使っており、Laravelのサービスコンテナによってメソッドの引数で自動的かつ再起的にDIの解決を行ってくれるためConverterを引数に受け取れるようになっています。
Controllerの責務はConverterを引数にUsecaseを実行し、Usecaseから返されたUsecaseOutputを引数にPresenterを実行するだけなのでメソッドの処理は基本的には2行で完結します。
Converter
Interface Adaptersに属します。
HTTPリクエストにより受け取った入力をユースケースで扱うために値オブジェクトなどに変換するコンポーネントです。
Laravelの Illuminate\Http\Request
をインジェクトしています。そのためInterface AdaptersがFrameworks & Driversに依存しており、クリーンアーキテクチャの同心円の図に反していますが、その部分の抽象化を行うことによる恩恵が少ないと判断し、許容しております。
UsecaseInput
Application Business Rulesに属します。
Usecase(Application Business Rules)がConverter(Interface Adapters)に依存することを回避するために依存性逆転を目的としたインターフェースです。
永続
Usecase
Application Business Rulesに属します。Factoryから取得したデータを基にサービス固有の処理を行うコンポーネントです。
処理の結果はUsecaseOutputとしてControllerに返却します。
注)永続だけを担うコンポーネントではなくビジネスの中核となるコンポーネントなのですが説明の都合上、永続のコンポーネントの段落で記載させていただいてます。
Factory
Application Business Rulesに属します。
後述するQueryから返されたEntityを適切な粒度に集約しDTOとしてUsecaseに返すコンポーネントです。
DTO
Application Business Rulesに属します。
Factoryで集約したEntityをを保持するコンポーネントです。
Query
Application Business Rulesに属します。
後述するQueryGatewayのインターフェースです。Factory(Application Business Rules)がQueryGateway(Interface Adapters)に依存することを回避するために依存性逆転を目的としています。
一般的にRepositoryと呼ばれますが、ここではコマンドクエリ分離原則に従いそれぞれQueryとCommandと命名してあります。
QueryGateway
Interface Adaptersに属します。
永続を実際に行うコンポーネントです。GatewayではDaoとTransformerを利用してDBをはじめとするデータストアから取得したデータ(主にEloquent)をEntityとして返します。
Dao
Interface Adaptersに属します。
主にLaravelのEloquentやDatabaseManagerを利用して実際のSQLなどを実行するコンポーネントです。データストアから取得したデータはEloquentModelやCollection、stdClassとしてGatewayに返します。
Laravelの Illuminate\Database\Eloquent\Model
をインジェクトしているため、Converter同様にInterface AdaptersがFrameworks & Driversに依存しておりますが同様の理由から許容しております。
Transformer
Interface Adaptersに属します。Daoで取得したEloquentModelやCollection、stdClassとEntityのマッピングを行うコンポーネントです。
出力
UsecaseOutputImpl
Application Business Rulesに属します。
Usecaseの結果を集約するコンポーネントです。
UsecaseOutput
Application Business Rulesに属します。
UsecaseOutputImplのインターフェースです。
Presenter
Interface Adaptersに属します。
UsecaseOutputをViewModelへ加工し、主にJson形式でResponseを返却するコンポーネントです。
Laravelの Illuminate\Http\JsonResponse
をインジェクトしているため、ConverterやDao同様にInterface AdaptersがFrameworks & Driversに依存しておりますが同様の理由から許容しております。
ViewModel
Interface Adaptersに属します。Jsonの構造を定義するコンポーネントです。
クリーンアーキテクチャを導入した所感
最後にクリーンアーキテクチャの導入により感じたメリット・デメリットの記載で記事を締めたいと思います。
クリーンアーキテクチャのメリット
- コンポーネントの責務が明確である
- コンポーネント間の依存関係が整理され、また循環依存が発生しない
- テストが容易になる
- ビジネスロジックが明確になり、また特定のクラスがFatになるといったことが起こりにくくなる
- メンバー間でクラス設計の共通認識が取りやすい
クリーンアーキテクチャのデメリット
- クラス数が多くなるため名前空間の設計を誤ると悲惨になる
- Application Business RulesがFrameworks & Driversに依存しないため特にUsecaseにおいてCollectionなどフレームワークの強力な機能を利用できない
仲間を募集しております
募集中の職種については以下を御覧ください。