54
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

Organization

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

概要

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
54
Help us understand the problem. What are the problem?