概要
やや古い記事だけど、InfoQ のドメイン駆動設計・開発の実践には次のように書かれています。
ドメイン・クラスがData Access Object(DAO:データ・アクセス・オブジェクト)クラスに依存し、サービス・クラスがドメイン・クラスに依存するという設計上の依存関係がDDDによる実装に際してDIを"なくてはならない"ものにしています。
Python の場合、DI(依存性の注入)の実現には Inject が便利です。
利用例
ざっと100行程度でサンプルを書くと、このような形になります。
# -*- coding: utf-8 -*-
import uuid
from abc import ABCMeta, abstractmethod
import inject
def config(binder):
binder.bind(UserRepository, UserMemoryRepository())
class User(object):
def __init__(self, identity, name):
self.identity = identity
self.name = name
class UserRepository(object):
u""" Base class of User Repository"""
__metaclass__ = ABCMeta
@abstractmethod
def store(self, user):
raise NotImplementedError
@abstractmethod
def find_by_identity(self, identity):
raise NotImplementedError
class UserMemoryRepository(UserRepository):
u""" User Repository on memory"""
def __init__(self):
self._users = {}
def store(self, user):
if not isinstance(user, User):
raise TypeError
self._users[user.identity] = user
def find_by_identity(self, identity):
return self._users[identity]
class UserRedisRepository(UserRepository):
u""" User Repository on Redis """
def store(self, user):
# TODO: write code here
pass
def find_by_identity(self, identity):
# TODO: write code here
pass
class UserService(object):
u""" User Service on Application Layer"""
repo = inject.attr(UserRepository)
def create_user(self, name):
user = User(uuid.uuid4(), name)
self.repo.store(user)
return user
def find_by_identity(self, identity):
return self.repo.find_by_identity(identity)
if __name__ == "__main__":
# Call once on application start
inject.configure(config)
user_service = UserService()
created_user = user_service.create_user('foo')
stored_user = user_service.find_by_identity(created_user.identity)
assert created_user == stored_user
inject
を使わずに同様のことを実現するためには、UserService
の引数に repo
を渡す必要があり、依存関係が増えるたびに UserService
のコンストラクタが肥大化してしまいますが、inject
を利用するとスッキリと書けます。@inject.params
デコレータを利用すれば、コンストラクタのデフォルト引数に依存性を注入することもできます。
わずか300行程度の小さなライブラリですが、とても便利です。