最近、社内で Hexagonal Architecture ベースのバックエンドを書いていて、わりとうまくいっている(つもり)なので、基本となるエッセンスを超簡単に書いてみます。
TagService の実装例
ドメイン層
from zope.interface import Interface
from .base import Entity, ValueObject
class Post(Entity):
u""" Post Model """
...
class Tag(ValueObject):
u""" Tag Model """
...
class ITagService(Interface): # specification
u""" Tag management domain service """
def add_tag(post, tag):
u""" add tag to a post """
def remove_tag(post, tag):
u""" remove tag from a post """
ValueObject や zope.interface については下記の記事を参照してください。
インフラストラクチャー層
from zope.interface import implementer
from .domain.model import ITagService
@implementer(ITagService) # implement TagService as described in ITagService
class TagService(object):
u""" TagService using xxx """
def add_tag(self, post, tag):
u""" add tag to a post """
# do some actual staff
def remove_tag(self, post, tag):
u""" remove tag from a post """
# do some actual staff
@implementer(ITagService)
class DummyTagService(object): # i.e. memory based implementation
u""" TagService for tests """
def add_tag(self, post, tag):
u""" add tag to a post """
# do some actual staff
def remove_tag(self, post, tag):
u""" remove tag from a post """
# do some actual staff
アプリケーション層
from .infrastructure import TagService
def add_tag(post, tag):
srv = TagService()
srv.add_tag(post, tag) # use TagService as described in ITagService
テスト
- DummyTagService と TagService がドメイン層で期待されている通りに振る舞うことを保証する。
- TagService を利用するオブジェクトのテストにおいては DummyTagService を利用する。
ドメイン層の役割について
社内で「ITagService って必要なの?」という質問があったのですが、ドメイン層の役割は、アプリ層、インフラ層それぞれに対して「仕様」を提示ですることではないか、と思っています。ドメイン層があることで、インフラ層はそのインタフェースを提供するように実装をすればよいし、アプリ層はそのインタフェースに従ってモデルを利用すれば良いことになります。
ECMAScript 仕様で例えると、
- ECMAScript の仕様がドメイン層で、
- Google Chrome の開発者は ECMAScript の仕様に従ってブラウザを実装し、
- アプリ開発者は ECMAScript の仕様に従って JavaScript のアプリ開発を行う
ドメイン層が間に入ることで、機能の提供者も、利用者も、それぞれが恩恵を受けられるわけです。