LoginSignup
15

More than 5 years have passed since last update.

Hexagonal Architecture の実践には zope.interface が便利

Last updated at Posted at 2015-01-17

追記: 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)

ドメイン駆動開発シリーズ

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15