⚠️

Pythonで実装されたモジュラモノリスで依存違反を検知する

に公開

🎯 目的

Python で実装されたモジュラモノリスのアプリ内で以下のような依存違反がないかどうか検知できるようにする

  • ⚠️ アーキテクチャの依存違反がないかどうか検知する
  • ⚠️ モジュール間で依存違反がないかどうか検知する

👤 自己紹介

2018年から2025年までヤフーやBASE株式会社などで、システムアーキテクチャ/アプリケーションアーキテクチャ設計、開発、保守運用を行いました。システム設計のご相談も募集していますので、下記の Wantedly や LinkedIn , X まで気軽にご連絡ください!もしこの記事が良いと思ったら、いいねとコメントをお願いします!!

企業 活動内容
ヤフー
2018/4~2022/5
・ヤフーショッピングのマーケティングシステムの設計/開発/保守運用を行いました。
・マイクロサービスアーキテクチャの設計/開発を行いました
・ドメイン駆動設計を導入したアプリケーションの設計を行い、開発効率を向上させました
・15,000rpsのシステムの負荷対策や開発、保守運用を1人で行いました
🛍️ BASE
2022/6~2025/1
・モジュラモノリスアーキテクチャのシステム設計/開発を行いました
・テックリードとしてドメイン駆動設計を用いた設計を行い、開発効率を向上させました
🚀 起業
2025/1~現在
・フリーランスのアーキテクトとして独立
・マイクロサービス,モジュラモノリスなどのシステムアーキテクチャの設計コンサル
・ドメイン駆動設計の導入コンサル

https://www.wantedly.com/id/taitamur

https://www.linkedin.com/in/taitamur/

https://x.com/tm_taiyo

💡 前提

🗺️ Pythonアプリケーションの構成

Python のアプリは、以下の例のようにモジュラモノリス構成のシステムを想定します。モジュラモノリスについての説明は以下を参照してください👍

https://zenn.dev/taiyou/articles/2c4a644935ddb3

上図の構成図はあくまで例ですが、イメージしやすいように各モジュールを紹介します。

モジュール 説明
🔌 API Gateway 全てのモジュールの前段にあるモジュール。主に外部から送信されたアクセストークンを認証/認可モジュールで検証し、後段のモジュールへ JWT の内部通信トークンと共にリクエストを送信する。
🔐 認証/認可 認証 / 認可を扱うモジュールです。 認証と認可を実現するために必要となる機能を提供します。
🏦 課金/請求 ユーザーが契約しているプランやサービスに基づいて発生する課金や支払いを管理するモジュール

そして、各モジュールでは以下のような観点でドメイン駆動設計を導入するか、どのアプリケーションアーキテクチャ(MVC / レイヤードアーキテクチャ / ヘキサゴナルアーキテクチャ / ...)を自由に決定します。

  • ビジネス価値」: そのモジュールで実装される機能が、サービスとしての他社との競争優位性となるくらい重要なモジュールか
  • ドメインロジックの複雑さ」: そのモジュールで実装する仕様が複雑なものか、もしくは単純なCRUD処理を行う程度なのか
  • 連携システムの種類」: そのモジュールで API の他にメッセージングシステムやバッチ、WebSocketなどの連携するシステムが多いかどうか
ヘキサゴナル MVC レイヤード

各アプリケーションアーキテクチャの詳細は以下の通りです。

アーキテクチャ 概要 適した状況
ヘキサゴナルアーキテクチャ ビジネスロジック(ドメイン)を中心に置き、外部との通信は「ポート」と「アダプター」を通じて行う設計。依存の方向が外から内へ一方向になる 複雑なビジネスロジックがあり、APIやDB, Message, Batch など複数の外部インターフェースがある場合
MVC Model(データ)、View(表示)、Controller(制御)に分離する伝統的な設計 シンプルな CRUD 処理を実装する場合
レイヤードアーキテクチャ プレゼンテーション層→アプリケーション層→ドメイン層→インフラ層という層状の設計 複雑なビジネスロジックがあるが、APIとDBのみのような外部インターフェースが少ない場合

※ ヘキサゴナルアーキテクチャは「ポートとアダプターアーキテクチャ」とも呼ばれ、ドメインロジックを外部の技術的関心事から隔離する設計パターンです。

🏁 課題: アーキテクチャの依存(参照)違反が検知できない

このような「モジュラモノリス」×「アプリケーションアーキテクチャ」の Python アプリで以下の依存違反がないかどうか検知できるようにしたいです。

  • ⚠️ モジュール内部のアーキテクチャの依存(参照)違反
  • ⚠️ モジュール間の依存(参照)違反

これらの違反を検知できないと以下のような問題が発生します。

  • 💥 開発効率の低下: 依存関係が複雑化すると変更の影響範囲が予測しづらくなります
  • 💥 バグの発生: 想定外の依存関係によって予期しない副作用が生じます

⚠️ モジュール内部のアーキテクチャの依存(参照)違反

例えば、あるモジュールで採用しているヘキサゴナルアーキテクチャを採用している場合、ポート・アダプター層➡️アプリケーション層➡️ドメイン層のような依存関係があります。


https://fintan.jp/page/397/ より引用

しかし、Pythonでは技術的には以下のようにアーキテクチャの依存関係を無視(違反)して、実装することが可能です。

❌ドメイン層からポート・アダプター層を呼び出す
# at ドメイン層の Python ファイル

from port.adapter.service.fuga import HogeService

⚠️ モジュール間の依存(参照)違反

システムによっては、あるモジュールから別のモジュールへの依存(参照)を禁止にしたいと言ったことがあります。これはモジュラモノリスの依存関係を制御し、思わぬバグを生むのを防いだりするためです。

具体的には、以下のようなケースです。

❌ 認証/認可モジュールから API Gateway モジュールを参照する
# at authority/port/adapter/auth_service.py

from apigateway.port.adapter.resource import hoge_resource
❌ API Gateway モジュールから認証/認可モジュールのドメイン層を直接参照する
# at apigateway/port/adapter/auth_service.py
from authority.domain.model.user import User
モジュール間の依存 外部モジュールから参照可能な層(レイヤ)を指定

他モジュールから API Gateway モジュールへ参照するのを禁止

外部モジュールからプレゼンテーション層への参照は許可し、ドメイン層への参照は禁止

🚧 import-linter による依存違反検知

上記のような課題は import-linter を利用して、違反検知できます。

https://import-linter.readthedocs.io/en/stable/readme.html

pip install import-linter

# 設定ファイル作成
# .importlinter ファイルをプロジェクトルートに作成

# キャッシュなしで実行
lint-imports --no-cache

出力例

=============
Import Linter
=============

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

Analyzed 242 files, 412 dependencies.
-------------------------------------

[APIGateway] ヘキサゴナルアーキテクチャの依存違反を禁止 KEPT
[APIGateway] 外部モジュールから API Gateway のドメイン層、アプリケーション層への参照を禁止 KEPT
[Authority] ヘキサゴナルアーキテクチャの依存違反を禁止 KEPT
[Authority] 外部モジュールから Authority のドメイン層、アプリケーション層への参照を禁止 KEPT
Common モジュールから各モジュールからへの依存を禁止 KEPT

Contracts: 5 kept, 0 broken.

🚧 1.モジュール内部のアーキテクチャの依存(参照)違反を検知する

以下のようにモジュール内部のアーキテクチャの依存(参照)関係を定義します。

./src/apigateway
├── __init__.py
├── application  # アプリケーション層
├── core.py      # API Gateway モジュールの定義ファイル
├── domain
│   └── model    # ドメイン層
└── port
    └── adapter  # ポート・アダプター層
./src/.importlinter
[importlinter]
root_packages =
    apigateway

[importlinter:contract:apigateway-architecture]
name = [API Gateway] ヘキサゴナルアーキテクチャの依存違反を禁止
type = layers
layers =
    port
    application
    domain
containers =
    apigateway
exhaustive = true
exhaustive_ignores =
    core

🚧 2.モジュール間の依存(参照)違反を検知する

ヘキサゴナルアーキテクチャの Authority で外部モジュールからドメイン層、アプリケーション層への参照を禁止します🚨

./src/authority
├── __init__.py
├── application  # アプリケーション層
├── core.py      # Authority (認証/認可)モジュールの定義ファイル
├── domain
│   └── model    # ドメイン層
└── port
    └── adapter  # ポート・アダプター層
./src/.importlinter
[importlinter:contract:authority-forbidden]
name = [認証/認可モジュール] 外部モジュールから認証/認可モジュールのドメイン層、アプリケーション層への参照を禁止
type = forbidden
source_modules =
    apigateway
    // ...モジュールを指定

forbidden_modules =
    authority.application
    authority.domain
allow_indirect_imports = True

📚 参考文献

https://qiita.com/taguchi_taguchi_taguchi/items/36b85a1ec967935921ef

https://import-linter.readthedocs.io/en/stable/contract_types.html

https://zenn.dev/mottyzzz/articles/20250101111916

Discussion