依存性逆転の原則(DIP)とは
依存性逆転の原則(DIP)は、ソフトウェアシステム内の上位モジュールと下位モジュールの関係と依存関係に関するものです。DIPは次の2つの主要なポイントを提唱しています。
-
上位モジュールは下位モジュールに依存すべきではなく、両方は抽象化に依存するべき
上位モジュールはシステムのビジネスルールをカプセル化しており、ソフトウェアにその独自の価値を与える重要な複雑なロジックを表します。一方、下位モジュールはデータベースアクセス、ネットワーキング、I/O操作などの処理を担当します。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が実装されていない状況を見てみます。
class UserDataAccess:
def get_user(self, user_id):
# Implementation to fetch user from the database
pass
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に準拠するように修正してみます。
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
を実装したクラスのオブジェクトを渡します。
from domain_layer import IUserRepository
class UserDataAccess(IUserRepository):
def get_user(self, user_id):
# Implementation to fetch user from the database
pass
インフラストラクチャ層では、UserDataAccess
がIUserRepository
インターフェースを実装するようになりました。これにより、UserDataAccess
はUserService
が依存する抽象化に適合するようになります。
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
に依存していることがわかります。