こんにちは、なおしむです。 私はシステム企画部でシステム全体のアーキテクトとレガシーシステムの改善開発をしています。
弊社ではドメイン駆動設計を使って開発をしています。 ドメイン駆動設計ではクリーンアーキテクチャのようなレイヤー構造でシステムを作ります。このレイヤー構造に従って設計・コーディングをするのですが、コードレビュー時に正しいレイヤー構造で作れているかをチェックするのが地味にめんどくさいです。。 現在のプロジェクトで、この地味で面倒なレイヤー構造のチェックをCheckstyleを使って自動化しているのでその方法を紹介します。
クリーンアーキテクチャのコードレビューはめんどくさい
クリーンアーキテクチャとは、図のような円形のレイヤー構造のアーキテクチャです。
The Clean Architecture を参考に筆者が作成
クリーンアーキテクチャにはレイヤー構造の依存関係にルールがあります。 この依存関係のルールでは、外側から内側への依存は許可しますが、内側から外側への依存は禁止です。コードレビューではこのルールに違反してないかをチェックします。具体的には各クラスのimport文を見ながらひとつひとつ確認します。たとえば、ドメイン層のクラスのレビューであればimport文にデータソース層がないかをチェックします。 このレビューが地味にめんどくさくてツライ。。 これをCheckstyleを使って自動化することが今回の本題です。
ドメイン層からデータソース層に依存している例
Checkstyleとは
Checkstyleはコードのコーディング規約違反をチェックするツールです。
本家サイトのoverviewを翻訳したものが下記です。
CheckstyleはプログラマーがJavaコードを書くための開発ツールです。コーディング標準に準拠するように支援してくれます。Javaコードをチェックする作業を自動化してくれ、コーディング標準を強制したいプロジェクトに有効です。
クリーンアーキテクチャのチェックもまさにコレ。 今回はCheckstyleを使ってimport文をチェックし、レイヤー構造の依存関係におけるルール違反を検知します。
Checkstyleの導入
checkstyle.xml配置
checkstyleディレクトリを作成しその中にファイルを作成します。
checkstyle/checkstyle.xml
<?xml version="1.0"?> <!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "http://www.puppycrawl.com/dtds/configuration_1_3.dtd"> <module name="Checker"> <module name="TreeWalker"> <module name="ImportControl"> <!-- importチェックのファイルパスを指定する --> <property name="file" value="checkstyle/import-control.xml"/> </module> </module> </module>
checkstyle/import-control.xml
最終的にココに設定を追加していきます。
<?xml version="1.0"?> <!DOCTYPE import-control SYSTEM "checkstyle/import_control_1_4.dtd"> <!-- チェック対象のパッケージ --> <import-control pkg="jp.co.biglobe"> </import-control>
checkstyle/import_control_1_4.dtd
これをDLして配置してください。
import_control_1_4.dtd
build.gradle
2行追加します。
buildscript { repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.5.RELEASE") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'idea' apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' apply plugin: 'checkstyle' // ★追加 checkstyle.configFile = file('checkstyle/checkstyle.xml')// ★追加 ...
この状態で./gradlew build
を実行して「許可されていないインポート」というエラーがたくさん出ればセットアップは完了です。
クリーンアーキテクチャの規約を設定
ここから先の設定はimport-control.xml
のimport-control
タグ内に記述します。
今回のプロジェクトがこんなパッケージ構成になっている想定で説明します。
+ jp.co.biglobe.sample + api // API層: コントローラなど + datasource // データソース層: DBへのアクセスなど + service // サービス層 + domain // ドメイン層
使って良いパッケージを設定
まず最初にプロジェクト内で使って良いパッケージを指定します。 クリーンアーキテクチャの話ではないですが、これをしないと全てエラーになってしまうので。。この設定はプロジェクトによって違うので適宜設定してください。
<!-- 使って良いライブラリ --> <allow pkg="jp.co.biglobe.sample"/> <allow pkg="lombok"/> <allow pkg="org.springframework"/> <allow pkg="java"/> <allow pkg="javax"/> <allow pkg="org.mybatis"/> <allow pkg="org.apache.ibatis"/>
クリーンアーキテクチャの規約を設定
本丸です。 正規表現を使いながら設定します。
<!-- ドメイン層 --> <!-- API層/データソース層/サービス層への依存禁止 --> <subpackage name="domain"> <disallow class=".*\.api\..*" regex="true" /> <disallow class=".*\.datasource\..*" regex="true" /> <disallow class=".*\.service\..*" regex="true" /> <disallow class="org.springframework.stereotype.Service" /><!-- domain層に@Serviceは禁止 --> </subpackage> <!-- サービス層 --> <!-- API層/データソース層への依存禁止 --> <subpackage name="service"> <disallow class=".*\.api\..*" regex="true" /> <disallow class=".*\.datasource\..*" regex="true" /> </subpackage>
ドメイン層では各層の依存に加えて@Service
の利用も制限しています。
ドメインサービスとアプリケーションサービスを混同して使ってしまうことがあるので。
全体
import-control.xml
全体としてはこんな感じです。
<?xml version="1.0"?> <!DOCTYPE import-control SYSTEM "checkstyle/import_control_1_4.dtd"> <import-control pkg="jp.co.biglobe.sample"> <!-- 使って良いライブラリ --> <allow pkg="jp.co.biglobe.sample"/> <allow pkg="lombok"/> <allow pkg="org.springframework"/> <allow pkg="java"/> <allow pkg="javax"/> <allow pkg="org.mybatis"/> <allow pkg="org.apache.ibatis"/> <!-- ドメイン層 --> <subpackage name="domain"> <disallow class=".*\.api\..*" regex="true" /> <disallow class=".*\.datasource\..*" regex="true" /> <disallow class=".*\.service\..*" regex="true" /> <disallow class="org.springframework.stereotype.Service" /><!-- domain層に@Serviceは禁止 --> </subpackage> <!-- サービス層 --> <subpackage name="service"> <disallow class=".*\.api\..*" regex="true" /> <disallow class=".*\.datasource\..*" regex="true" /> </subpackage> </import-control>
以上設定完了!
実行してみる
設定が終わったので./gradlew build
を実行してみます。
お!見事にドメイン層からデータソース層への依存を検知してくれました。
まとめ
今回はCheckstyleを使って、クリーンアーキテクチャのレイヤー構造に従っているかを自動検知する仕組みを作りました。 これで面倒なコードレビューから解放される〜。
それではまたー。