Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

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

Why do not you register as a user and use Qiita more conveniently?
  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
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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