読者です 読者をやめる 読者になる 読者になる

sifue's blog

プログラマな二児の父の日常

Play2.1でsbtのマルチプロジェクトビルド機能を利用してDDDのレイヤードアーキテクチャを強制する方法

【blog.soichiro.orgの閉鎖にともない転載しました - 2.2でも問題なく動くと思います】

Play2.1でDDDのレイヤードアーキテクチャをプロジェクトに強制して、大規模開発で誰かがうっかりルール違反が起こせないようにする仕組みを紹介します。
レイヤードアーキテクチャについては、じゅんいち☆かとうさんのスライドなどがオススメです。
http://www.slideshare.net/j5ik2o/ss-6227801

元々Play frameworkはMVCで作られることが前提とされていたため、デフォルトでは、

  • controllers
  • models
  • views

というパッケージに全てを入れるようになっており、これらのパッケージは相互参照できるようになっていました。
ただPlay2系になってから、MVCのどの要素も任意の名前空間のパッケージに入れることが可能になったため、DDDの構成をパッケージ名として利用できるようになりました。

  • org.hogehoge.ui
  • org.hogehoge.application
  • org.hogehoge.domain
  • org.hogehoge.infrastructure

また、更にsbtの持つマルチプロジェクト機能を用いればサブディレクトリ内に複数のプロジェクトとを存在させ、それら個々に正しい依存関係だけのパスを通してビルドすることができるようになっています。
つまりこれを利用して、ドメイン層からはインフラストラクチャー層のクラスを参照できるが、インフラストラクチャ層からはドメイン層のクラスを参照できず、さらに、IntelliJ IDEA等のコードアシストにも出てこないようにすることができます。つまり、下の層から上の層を参照させないというDDDのルールを強制することができるのです。

なおこのたび紹介する方法は、
https://github.com/sifue/play2-layered-sample
にサンプルコードをpushしてあります。なおIDEIntelliJ IDEAにしか対応させていません。

まず内容を説明する前に、DDDをplay2ではじめる方は最初に、全てのレイヤをそれぞれのjarにして、mavenリポジトリに入れてしまえば良いと考えるかもしれませんが、実は効率的ではない面もあります。それは、まずIntelliJ IDEAのワークスペースにマルチモジュール用意して、それぞれパスを通して回らないといけないからです。
そのため、jar分割数が増え10以上のモジュール(プロジェクト)を扱うようになると、開発環境構築コストが非常に高くなってしまします。なおEclipseにはpsfというチームプロジェクトセットという機能がありますが、そもそもEclipse自体がScalaIDEとしておしいという問題があります。
その点、このsbtのマルチプロジェクトビルドを使った方法は、すべてのコードをサブディレクトリに入れて依存関係を構築します。
そして、"play idea"コマンドで、パスを一瞬で通し終えてくれます。逆にこの方法は、サブディレクトリでなくてはいけないのですが、利点として一つのgitプロジェクトで管理することもできるようになり。ニーズがあれば、gitのsubmodule機能などを利用してサブプロジェクトも別リポジトリで管理、それぞれパブリッシュしたり、sbt pacackageやsbt testをすることも可能となります。

ただこの方法、2つだけ制約があって、その2つは、

  • UI層とアプリケーション層は分離できない点。これは、テンプレートの仕組みが同じビルド空間にないとコントローラーのクラスから利用できないという制約からきています。
  • 次にUI・アプリケーション層を担当するプロジェクトは、play2のプラグインのモジュールとして作る必要があるという点。これは、UI・アプリケーション層ではPlay2のライブラリを参照しなくてはいけないからです。

なお、Play2のプラグインモジュールの作成方法は、
http://www.objectify.be/wordpress/?p=363
を参考にしました。とは言え基本的に、application.confを空にしたただのplayのプロジェクトを作っているだけとなります。

最終的に以下の様なフォルダ構成になります。

f:id:sifue:20140429115859p:plain



そして肝となるproject/Build.scalaの内容は、

Play2.1でレイヤー化アーキテクチャを強制するBuild.scala

 

のようになります。重要な部分は、settingsにideaのプラグインの設定をそれぞれのプロジェクトに引き継がせることと、ちゃんと一つ一つのプロジェクトの依存関係を定義することとなります。ちなみにdependsOnだけでも大丈夫なのですが、テスト時もサブプロジェクトのテストが実行されるようにaggregateとaggregate in Test := trueも追加してあります。

なおこの方法を拡張すれば、さらにドメイン層をコアドメインと汎用サブドメインにプロジェクト分割したい際にもうまくその依存関係方向の制約をプロジェクトに課すことも可能です。

この設定で、playのideaコマンドによる統合開発環境用の設定ファイルの作成、run起動、プロダクション用のstart起動、リリース用のzipに固めるdistコマンド、testコマンドによるspecs2のテストの実行などもできるようになっています。

また、最初のプロジェクトを作った状態からどのような変更を加えればこのようにするようにできるかもgithubのヒストリを見ればわかりますので、参考にしてみてください。
https://github.com/sifue/play2-layered-sample/commits/master

追伸
play test時にサブプロジェクトのテストまで実行するために、dependsOnに加えてaggregateでもプロジェクトを追加していますが。ファンクショナルテスト(結合テスト)しかしないつもりであれば、dependsOnだけでも大丈夫です。なお、aggregateだけではコンパイルが通りません。(じゅんいち☆かとうさん指摘ありがとうございます)

追伸の追伸
なお、サブモジュール内のspecs2のテストやScalaスクリプトを、IntelliJから実行する際に、コンパイルのエラーが発生する場合があります。それに関しては、以下の図のようにuse external buildのチェックボックスをオフにすることで回避することができます。

f:id:sifue:20140429120130p:plain

では良いScalaライフを!