BIGLOBEの「はたらく人」と「トガッた技術」

Checkstyleでクリーンアーキテクチャをチェックする

こんにちは、なおしむです。 私はシステム企画部でシステム全体のアーキテクトとレガシーシステムの改善開発をしています。

弊社ではドメイン駆動設計を使って開発をしています。 ドメイン駆動設計ではクリーンアーキテクチャのようなレイヤー構造でシステムを作ります。このレイヤー構造に従って設計・コーディングをするのですが、コードレビュー時に正しいレイヤー構造で作れているかをチェックするのが地味にめんどくさいです。。 現在のプロジェクトで、この地味で面倒なレイヤー構造のチェックをCheckstyleを使って自動化しているのでその方法を紹介します。

クリーンアーキテクチャのコードレビューはめんどくさい

クリーンアーキテクチャとは、図のような円形のレイヤー構造のアーキテクチャです。

f:id:biglobe-style:20200213144730p:plain

The Clean Architecture を参考に筆者が作成

クリーンアーキテクチャにはレイヤー構造の依存関係にルールがあります。 この依存関係のルールでは、外側から内側への依存は許可しますが、内側から外側への依存は禁止です。コードレビューではこのルールに違反してないかをチェックします。具体的には各クラスのimport文を見ながらひとつひとつ確認します。たとえば、ドメイン層のクラスのレビューであればimport文にデータソース層がないかをチェックします。 このレビューが地味にめんどくさくてツライ。。 これをCheckstyleを使って自動化することが今回の本題です。

ドメイン層からデータソース層に依存している例

f:id:biglobe-style:20200213141648p:plain

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.xmlimport-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を実行してみます。

f:id:biglobe-style:20200213141608p:plain

お!見事にドメイン層からデータソース層への依存を検知してくれました。

まとめ

今回はCheckstyleを使って、クリーンアーキテクチャのレイヤー構造に従っているかを自動検知する仕組みを作りました。 これで面倒なコードレビューから解放される〜。

それではまたー。