OTOBANK Engineering Blog

オトバンクはコンテンツが大好きなエンジニアを募集しています!

レイヤー間の依存関係の静的解析 - PHP deptrac ~ 実践編

前回の記事 レイヤー間の依存関係の静的解析 - PHP deptrac ~ 導入編 からの続きです。

「ユニットテストとは事情が違うし、そうそう違反は起きないよ」と思った方がいらっしゃるかも知れません。いえ、レイヤーの依存関係違反は割と発生します。

起こりうる違反例

前回のコードを少し変更して、Userエンティテイにおいて getType() メソッドで属性を取得できるようにしてみましょう。

<?php
namespace Foo\Entity;

class User
{
    public function getType() : string
    {
        // 何かしらのロジック

        return \Foo\Repository\UserRepository::TYPE_ADMIN;
    }
}
<?php
namespace Foo\Repository;
use Foo\Entity\User;

class UserRepository
{
    public const TYPE_ADMIN = 'admin';

    public function findOneById(int $id) : User
    {}
}

Entityまでのレイヤールールを行うと・・・・

paths:
  - ./src
exclude_files:
layers:
  - name: Action
    collectors:
      - type: implements
        implements: Psr\Http\Server\RequestHandlerInterface
  - name: Repository
    collectors:
      - type: className
        regex: Foo\\Repository\\.*Repository
  - name: Entity
    collectors:
      - type: className
        regex: Foo\\Entity\\.*

ruleset:
  Action:
    - Repository
    - Entity
  Repository:
    - Entity
$ deptrac analyse action-repository-entity.yaml
 8/8 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ----------- -------------------------------------------------------------------------------
  Reason      Entity
 ----------- -------------------------------------------------------------------------------
  Violation   Foo\Entity\User must not depend on Foo\Repository\UserRepository (Repository)
              /mnt/d/dev/sandbox-deptrac/src/Entity/User.php:10
 ----------- -------------------------------------------------------------------------------


 -------------------- -----
  Report
 -------------------- -----
  Violations           1
  Skipped violations   0
  Uncovered            6
  Allowed              5
  Warnings             0
  Errors               0
 -------------------- -----

と、Entityは他に参照するレイヤーはあるべきでないのに、UserRepositoryの定数を利用していたことが判明します。 実際のプロジェクトでも数は少なくはありながらも、「サービスクラスの定数を Entityクラス内で用いてしまっていた。」などがありました。

ほかの違反例

さきほどの例では、レイヤー外の定数を挙げましたが、ほかには以下のような違反を発見しました。

  • Entityでのメソッドで、サービスレイヤーとして定義した層の例外クラスの使用
    • これは、定数の例と同様にサービスクラス側に設けた、サービスの例外をエンティテイの例外に利用してしまったというミスです。

現行のエラーをひとまずレポートしないようにする。~ baselineの利用

実際に、既存のプロジェクトに対してレイヤールールをある程度行い、analyseを実行してみると上記で述べたような違反が検出されてくることになります。その場合、deptrac を導入したくても既存の違反分の修正を行わなくては、と思われるかも知れません。ご安心ください。deptracには、phpstanやpsalmのものと同様に既存の違反分を許容するベースラインサポートがあります。

baselineの作成

--formatter=baseline でbaselineを作成します。

$ deptrac analyse --formatter=baseline --baseline-dump=baseline.yaml action-repository-entity.yaml
 8/8 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

Baseline dumped to /mnt/d/dev/sandbox-deptrac/baseline.yaml

以下のような既存の違反に対して skip_violationsが作成されます。

$ cat baseline.yaml
skip_violations:
  Foo\Entity\User:
    - Foo\Repository\UserRepository

baselineのインクルード

baseline: 行を追加します。

paths:
  - ./src
exclude_files:

baseline: baseline.yaml

baselineを含んだ上での再実行

baselineを含ませて改めて実行してみます。

$ deptrac analyse action-repository-entity.yaml
 8/8 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%


 -------------------- -----
  Report
 -------------------- -----
  Violations           0
  Skipped violations   1
  Uncovered            6
  Allowed              5
  Warnings             0
  Errors               0
 -------------------- -----

Skipped violations の対象となり、exitコードも0です。

Github Actions でのアノテート

CIでの運用の場合、deptracはさらにフォーマッターとしてgithub-actions が用意されています。 弊社では、deptrac analyse action-repository-entity.yaml --no-progress --formatter=github-actions といったワークフローを設定しています。 なお、補足として、0.11.1以降ではGithub Actionsのフォーマッターでのスキップ対応 を含めスキップ分のレポートはデフォルトでは除外されるようになり、Github Actionsでのアノテートなどでの導入が行いやすくなりました。

継続的なアーキテクチャの維持・向上に向けて

上述のbaselineのサポートもあり、もし既存のコードに対してアーキテクチャを失敗していた!ということが見つかっても、恐れることなく即レイヤー設定の追加・アップデートが行えるようになりました。自身が担当した改修部分に限らず、コードレビューにおいて見つかった点をフィードバックする形で deptrac.yaml に追加し継続的にアーキテクチャテストを行っていきたいと思います。