Traffine I/O

日本語

2022-05-19

依存性逆転の原則

依存性逆転の原則(DIP)とは

依存性逆転の原則(DIP)は、ソフトウェアシステム内の上位モジュールと下位モジュールの関係と依存関係に関するものです。DIPは以下の2つの主要なポイントを提唱しています。

  • 上位モジュールは下位モジュールに依存すべきではなく、両方は抽象化に依存するべき
    上位モジュールはシステムのビジネスルールをカプセル化しており、ソフトウェアにその独自の価値を与える重要な複雑なロジックを表します。一方、下位モジュールはデータベースアクセス、ネットワーキング、I/O操作などの処理を担当します。DIPでは、両種類のモジュールは抽象化(インタフェースや抽象クラスなど)を介して連携し、下位モジュールの詳細に対する変更が上位モジュールに影響を与えないようにします。これにより、コードが堅牢でメンテナンスしやすくなります。

  • 抽象化は詳細に依存すべきではなく、詳細は抽象化に依存すべき
    従来、抽象化(インタフェースや抽象クラス)は詳細(具体的な実装)に基づいて設計されていましたが、DIPではこの関係を逆転させます。具体的な実装は抽象化に基づいて構築されるべきです。このアプローチにより、既存のコードを変更することなく、新しい実装を作成するだけで新機能を導入できる柔軟なシステムが実現されます。

DIP
Dependency Inversion Principle(DIP)

DDDにおける依存性逆転

ドメイン駆動設計(DDD)は、技術的およびドメインの専門家との協力を強調したソフトウェア開発のアプローチであり、複雑なビジネスニーズを満たすために、技術的およびドメインの専門家との協力を重視するソフトウェア開発アプローチであるドメイン駆動設計(DDD)の中で、依存性逆転の原則(DIP)がこの分離と堅牢な保守性を確保する上で重要な役割を果たしています。

DDDにおけるドメイン層とインフラストラクチャ層の役割

DDDにおいてDIPを考える前に、ドメイン層とインフラストラクチャ層の役割を理解することが重要です。

  • ドメイン層
    この層はシステムのビジネスロジックとルールをカプセル化しています。これはソフトウェアの核であり、特定の技術やインフラストラクチャの問題とは完全に独立しています。

  • インフラストラクチャ層
    この層にはデータベース、UI、外部サービスなどの要素が含まれます。これらの要素は、ドメイン層の機能をサポートするための技術的な問題に責任を持っていますが、ビジネスロジック自体とは関係ありません。

DDDでは、これらの層を明確に分離することが重要です。これにより、1つの層の変更が他の層に影響を与えず、保守性と柔軟性のあるアーキテクチャが実現されます。

DDDでの依存性逆転の原則の適用

依存性逆転の原則は、ドメイン層とインフラストラクチャ層の分離を維持し、ドメイン層がインフラストラクチャの変更から隔離されるようにするための強力なツールとなります。

DIPに従い、上位のドメイン層は下位のインフラストラクチャ層に依存すべきではありません。代わりに、両方の層は抽象化に依存するべきです。以下では、これがどのように実現されるかを詳しく見ていきます。

  • 抽象化に対する依存
    DDDの文脈では、ドメイン層はインフラストラクチャ層から必要なサービス(例:ドメインオブジェクトの永続化のためのリポジトリ、メール送信のサービスなど)に対するインターフェース(抽象化)を定義します。

  • 依存関係の逆転
    インフラストラクチャ層はこれらのインターフェースに具体的な実装を提供しますが、重要なのは、ドメイン層がこれらの実装に対して無知のままであることです。つまり、依存関係の方向が逆転します。ドメイン層がインフラストラクチャ層に依存するのではなく、インフラストラクチャ層がドメイン層に依存するのです。

DDDにおけるDIPの実装

この章では、ドメイン駆動設計(DDD)のコンテキストで依存性逆転の原則(DIP)を実装するPythonの例を見ていきます。

デモンストレーションのため、ドメイン層のUserServiceはインフラストラクチャ層からデータベースからユーザー情報を取得するためのUserDataAccessが必要とされるシンプルなシステムを考えてみます。

依存性逆転が行われていない場合

まず、DIPが実装されていない状況を見てみます。

infrastructure_layer.py
class UserDataAccess:
    def get_user(self, user_id):
        # Implementation to fetch user from the database
        pass
domain_layer.py
from infrastructure_layer import UserDataAccess

class UserService:
    def __init__(self):
        self.user_data_access = UserDataAccess()

    def get_user_details(self, user_id):
        return self.user_data_access.get_user(user_id)

この例では、UserServiceクラスがインフラストラクチャ層のUserDataAccessクラスに直接依存しています。この設計はDIPに違反しており、上位モジュールであるUserServiceが下位モジュールであるUserDataAccessに直接依存しています。

依存性逆転の実装

では、上記のシステムをDIPに準拠するように修正してみます。

domain_layer.py
from abc import ABC, abstractmethod

class IUserRepository(ABC):
    @abstractmethod
    def get_user(self, user_id):
        pass

class UserService:
    def __init__(self, user_repository: IUserRepository):
        self.user_repository = user_repository

    def get_user_details(self, user_id):
        return self.user_repository.get_user(user_id)

このアップデートされた例では、ドメイン層でIUserRepositoryというインターフェースを定義しています。このインターフェースはUserServiceがユーザーデータを取得するために使用する抽象メソッドget_userを提供します。

UserServiceは、具体的なUserDataAccessクラスではなく、IUserRepositoryインターフェースに依存するようになりました。UserServiceオブジェクトを作成する際には、IUserRepositoryを実装したクラスのオブジェクトを渡します。

infrastructure_layer.py
from domain_layer import IUserRepository

class UserDataAccess(IUserRepository):
    def get_user(self, user_id):
        # Implementation to fetch user from the database
        pass

インフラストラクチャ層では、UserDataAccessIUserRepositoryインターフェースを実装するようになりました。これにより、UserDataAccessUserServiceが依存する抽象化に適合するようになります。

main.py
from domain_layer import UserService
from infrastructure_layer import UserDataAccess

def main():
    user_repository = UserDataAccess()
    user_service = UserService(user_repository)
    user_service.get_user_details("user_id")

if __name__ == "__main__":
    main()

アプリケーションのメイン部分では、UserDataAccessのインスタンスを作成し、それをUserServiceに渡します。この設計はDIPに沿っており、上位モジュールであるUserServiceは下位モジュールであるUserDataAccessに依存せず、両方が抽象化であるIUserRepositoryに依存していることがわかります。

Ryusei Kakujo

researchgatelinkedingithub

Focusing on data science for mobility

Bench Press 100kg!