Pythonでレイヤードアーキテクチャするときはimport-linterが便利だった


概要

Pythonで開発する場合でもレイヤードアーキテクチャーをベースとした設計をすることがあります。
その時、依存の方向性をチェックしたくなる時があります。

project_root/
  ├ infrastructure/
  │ └ db
  ├ interface/
  │ └ controllers
  ├ application/
  │ └ service
  └ domain/
    └ entity

例として上記の様なパッケージ構成を考えます。
依存の方向性をinfrastructure -> interface -> application -> domainと言った形で定義しているとします。
開発時にルールを破っているインポートを自動でチェックするにはimport-linterを使うと簡単に解決できます。

サンプルのリポジトリを用意しました。ソースコード全体はこちらを参照してください。

使い方

$ pip install import-linter

setup.cfg.importlinterでレイヤーの定義と依存の方向性を定義します。
まずは[importlinter]のセクションでレイヤーの定義をします。

[importlinter]
root_packages =
    infrastructure
    interface
    application
    domain

type = layersはレイヤードアーキテクチャ用のルール設定です。
依存の方向性をレイヤーの順序で表現しています。

[importlinter:contract:1]
name = layered architecture contract
type = layers
layers =
    infrastructure
    interface
    application
    domain

様々なオプションがあり柔軟なルールを構成することができます。
例えばflaskのパッケージのインポートを特定のレイヤーに限定したい場合などは、include_external_packagesを有効にして下記のようにtype = forbiddenで定義することができます。

[importlinter]
root_packages =
    infrastructure
    interface
    application
    domain
include_external_packages = True

[importlinter:contract:2]
name = Do not use `flask` outside the interface layer.
type = forbidden
source_modules =
    infrastructure
    application
    domain
forbidden_modules =
    flask

チェックする

ルールの設定が完了したら以下のコマンドでチェックを行います。

$ lint-imports

pipenv run lintfix                                                                                                                            ✘ 1 main ✚ ✱ ◼
Skipped 2 files
=============
Import Linter
=============

---------
Contracts
---------

Analyzed 14 files, 15 dependencies.
-----------------------------------

Layered architecture contract BROKEN
Do not use `flask` outside the interface layer. BROKEN

Contracts: 0 kept, 2 broken.


----------------
Broken contracts
----------------

Layered architecture contract
-----------------------------

domain is not allowed to import application:

- domain.entity -> application.repository (l.5)


Do not use `flask` outside the interface layer.
-----------------------------------------------

infrastructure is not allowed to import flask:

-   infrastructure.db -> domain.entity (l.4)
    domain.entity -> flask (l.3)


application is not allowed to import flask:

-   application.repository -> domain.entity (l.4)
    domain.entity -> flask (l.3)

-   application.service -> domain.entity (l.6)
    domain.entity -> flask (l.3)


domain is not allowed to import flask:

-   domain.entity -> flask (l.3)

ルール違反を行なっているコードが存在する場合はエラーを表示してくれます。

CIツールに定義してPR時にチェックしてもらいましょう。