sifue's blog

プログラマな二児の父の日常 ポートフォリオは www.soichiro.org

エリック・エヴァンスのドメイン駆動設計に沿ってSymfony2でユーザー管理アプリを作ってみた

あけましておめでとうございます。
去年の暮からエリック・エヴァンスドメイン駆動設計という5200円、500ページもする本を購入して読み始めた自分です。

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

あまりに勿体無かったので試しにこのドメイン駆動設計の設計思想にそって、簡単なアプリをSymfony2で作ってみました。


実際に作られたサイトは、
http://www.soichiro.org/sf
こんな感じです。

  • id: test1@test.com
  • pass: test1

でログインできます。(ユーザー作るだけならadmin/adminpassでもOK)


画面イメージは





こんな感じです。UIにはtwitterのbootstrapを使っています。


ソースコードは、
https://github.com/sifue/UserManagement
からチェックアウトできます。無論MITライセンスです。サンプルとして使えそうな所があったら好きなように改変してご自由にご利用ください。


ドメイン駆動設計はすごく簡単に言うと、システムを作る時って業務モデル(実際のビジネスを抽象化した概念、例えばUMLのクラス図で書いたようなもの)の共有ってすごく重要だよねって話から始まって、そのモデルとシステムをいかに近づけるかって話と、実際のシステムを実装するときには、

に分けて実装すれば良いよっていうところから、更に深く実際のいろいろなプラクティスが書かれています。


かなりじっくり読まなきゃいけないタイプの本で、読めば読むほど考えさせられる所もあって読み進まないタイプの本でした。オブジェクト指向大規模アプリ設計の教科書と言っても過言ではないのでしょうか。


読み進んでビックリしたのは、ここに出てくる概念がほとんどSymfony2で実装されているというところでした。たぶんこの本に相当影響を受けてるんだと思います。例えで言うと、

  • サービス : Symfony2ではサービスコンテナというDIコンテナ
  • エンティティ: Symfony2ではエンティティというデータクラス
  • ファクトリ: Symfony2ではサービスにインスタンスを作るファクトリを登録したり、サービスを作るファクトリを設定できる
  • リポジトリ: Symfony2ではリポジトリというORMとの接続処理実装集約クラス
  • モジュール: Symfony2ではバンドル、OSGiのバンドルのように拡張ポイントもある

とかがありました。
残念ながらなかった概念は、値オブジェクト。Javaenumのように不変でstaticなインスタンスを作る仕組みがPHPにはないのでやむなしというところなのかもしれません。


実際にアプリ作るにあたってエリック・エヴァンスドメイン駆動設計によるとドメイン層はできるだけ集約して一つにして高い凝集にしたほうがよいということだったので、実際に作ったこのユーザー管理システムのバンドル設計、クラス図はこんな風になりました。

Symfony2の思想としては、ドメイン層とアプリ層とUI層を一つのバンドル内で作ってしまうように作られていたのですが、よりドメイン層に全てのビジネスロジックを集約する意図を持たせるために、DomainBundleというものを作りました。
赤がインフラ層、オレンジがドメイン層、緑がアプリ層、青がUI層をあらわしています。UI層のテンプレートファイルやルーティング設定ファイル類は割愛しています。


確かにこの設計で作ってみると非常にアプリの作りがいいように感じます。あと、クラスそれぞれが単一責務になっている。


以下に実際にUserFactoryの実装を書きますが、ここではUserのインスタンスができる際に守らせなければならない、インスタンス作成時は有効になっていることや、ランダムなハッシュ用のsaltの設定などを守らせることができます。

<?php
namespace Sifue\Bundle\DomainBundle\Factory;
use Sifue\Bundle\DomainBundle\Entity\User;

/**
 * Userインスタンスの作成とインスタンスが守らなければいけない整合性を保つ責務を持つクラス
 */
class UserFactory
{
    /**
     * ユーザーのインスタンスを取得する
     * @return Sifue\Bundle\DomainBundle\Entity\User
     */
    public function get()
    {
        $user = new User();
        $user->setIsActive(true);
        $user->setSalt(base_convert(sha1(uniqid(mt_rand(), true)), 16, 36));
        return $user;
    }
}

いい具合に責務が分離されています。
これなら大規模開発で拡張、リファクタリングを続けていっても耐えられる構成なのではないかなという確信を得ることができました。


とは言えまだドメイン駆動設計、ぶっちゃけ業務でいろいろやってみないとわからないことも多そうなので、いろいろなテクニックを取り入れながら少しずつ試して行けたらななんて考えています。まだ初心者な所もあるので、ご指摘などあればコメント下さい。


ちなみにこのアプリ。Symfony2のインストールのためのPHP環境、MySQL環境を揃えた後、

$ git clone git@github.com:sifue/UserManagement.git
$ vim app/config/parameters.yml
parameters:
    database_driver:   pdo_mysql
    database_host:     127.0.0.1
    database_port:     ~
    database_name:     symfony
    database_user:     root
    database_password: password

    mailer_transport:  smtp
    mailer_host:       127.0.0.1
    mailer_user:       ~
    mailer_password:   ~

    locale:            ja
    secret:            ThisTokenIsNotSoSecretChangeIt
$ php app/console doctrine:database:create
$ php app/console doctrine:schema:update --force

とするだけで、UserManagement/webをウェブサーバーに公開すれば動かすことができるようになっています。


テストもひと通り実装してあり、PHPUnitがインストールしてあれば、

$ phpunit -c app

で全てのテストが実施可能です。
Symfony2はPHPUnitをいい具合に拡張してあって、WebのUIをエミュレーションするテストやサービスコンテナのテストができるところが便利です。


ぜひ試してみて下さい。


追伸
ちなみに自分はPHPなんてオブジェクト指向言語の風上にも置けない言語だと思っていたのですが、Symfony2に触れて全然そんなことはないな、普通に戦えるフレームワークだなと思いました。PHPはひどい言語だけど、捨てたもんじゃない!きっとw