Posted at

Python で DDD するなら Inject がオススメ

More than 3 years have passed since last update.


概要

やや古い記事だけど、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行程度の小さなライブラリですが、とても便利です。