追記: 2017-02-12
実は、自分のプロジェクトでは最近、この方法を利用していません。inject による紐付けが出来ていて、必要なメソッドが定義されていれば(duck typing)、必ずしもドメイン層のモデルとインフラ層の実装が継承関係になくても良い、というゆるい考えです。
また、最近の自分のプロジェクトでは、インフラ層を置く代わりに、port, adapter のモジュールを定義しています。adapter には、ドメイン層または port で定義されたインタフェースの実装を書く感じです。ドメインモデルにある概念はドメイン層に置き、外部シストムとの連携インタフェースは port に置く感じです。
時間のあるときにまた記事を書こうと思います。
概要
ドメイン駆動開発にHexagonal Architectureを適用した場合、リポジトリやドメインイベントのインタフェースをドメイン層におきますが、zope.interfaceを活用することで、多重継承を回避した分かりやすいコードを書くことができます。
ドメイン層
from abc import ABCMeta
from zope.interface import Interface
class Entity(object):
""" Base class for Entity """
__metaclass__ = ABCMeta
class IRepository(Interface):
""" Interafce for Repository """
class IDomainService(Interface):
""" Interface for Domain Service """
class Blog(Entity):
def __init__(self, identity, title):
self._identity = identity
self._title = title
class IBlogRepository(IRepository):
""" Blog repository Interface """
def add_entity(blog):
""" add blog """
def get_by(identity):
""" get blog by identity """
class IBlogProvisioningService(IDomainService):
""" Blog provisioning service interface """
def provision_blog(title):
""" provision blog with title. returns Blog object """
インフラ層
import uuid
from abc import ABCMeta
from zope.interface import implementer
from domain import Blog, IBlogRepository, IBlogProvisioningService
class MySQLRepository(object):
""" MySQL based repository """
__metaclass__ = ABCMeta
class MemoryRepository(object):
""" memory based repository """
__metaclass__ = ABCMeta
@implementer(IBlogRepository)
class BlogRepository(MySQLRepository):
def add_entity(self, blog):
# do some stuff
pass
def get_by(self, identity):
# do some stuff
return Blog(identity, "some stored title")
@implementer(IBlogRepository)
class BlogMemoryRepository(MemoryRepository):
def add_entity(self, blog):
# do some stuff
pass
def get_by(self, identity):
# do some stuff
return Blog(identity, "some stored title")
@implementer(IBlogProvisioningService)
class BlogProvisioningService(object):
def __init__(self, repo):
self._repo = repo
def provision_blog(self, title):
entity = Blog(uuid.uuid4().hex, title)
self._repo.add_entity(entity)
テスト
import pytest
from zope.interface.verify import verifyClass
from domain import Blog, IBlogRepository, IBlogProvisioningService
from infra import BlogRepository, BlogMemoryRepository, BlogProvisioningService
def test_class_interface():
assert verifyClass(IBlogRepository, BlogRepository)
assert verifyClass(IBlogRepository, BlogMemoryRepository)
assert verifyClass(IBlogProvisioningService, BlogProvisioningService)