背景
昨年からスタートアップに参画して、Python のフレームワーク Django で開発しています。5年近く開発のブランクがあったので、「今どきの開発はこうなのかー」と日和見を決めこんでいました。でも、いろいろなアンチパターンや突っ込みどころが満載だったのです。
前の前の担当者と前の担当者がやめて、そのつけがまわってきました。残されたコードを見て、頭をかかえたり、あきれかえったり。
そんな中でリファクタリングしたときの話、これからリファクタリングする話をまとめようと思います。
やらかされていた問題
語り始めると止まらなくなりそうですが、アーキテクチャから見て残念なものが二つありました。
スマートすぎるビュー
もともとバッチ処理で作成されたデータをビューが取得するだけのシンプルなアプリだったようです。しかし機能追加があって、ビューでいろいろ計算するようになりました。
※ よくわかっていなかったころの私は、ある時期にコピペ駆動開発でそれに加担してしまいました。
orz
散らかされたロジック
前の担当者が何とかしようとしたようなのですが、ビューにロジックが残ってたり、モデルにロジックがあったり。二つの異なるパッケージで循環参照を生じさせたり。ろくに動作確認もしてないし。もうひどいことになりました。
根本問題は…
時間が足りなかったとか、担当者がアホだったとか、状況や人に依存することは横に置きます。
そもそも Django はテーブルとモデルが1:1になっている、シンプルな構成を想定しています。本来の意味とは若干ずれるのですが、Active Record パターンに該当するでしょう。
この構成はアプリが複雑になってくると問題が出てきます。
- データ構造の問題
機能が複雑になってくると、テーブルが変更されます。当初想定していたモデルと合わなくなってくることがあります。
- 結合したデータの問題
Django だとビューからモデルを直接呼びがちになると思います。
複数のテーブルの情報を結合させるとビューでロジックを書くことになりがちです。複雑な計算をさせると「え?これってこのモデルが担当するの?」と責任の所在が分かりにくくなります。
- アーキテクチャの混乱
そもそも Model-View-Controller はプレゼン層のパターンです。それがモデルにデータソースのアーキテクチャパターンが混じっているようで、分かりにくくなっています。(単純に私が理解していないだけとも思いますが)
解決策
基本に立ち返ってレイヤー化することから始めてみました。その際、ドメイン駆動設計や、(訳がひどいと評判ですけど)エンタープライズアーキテクチャパターンを参考にしてみました。
個人的にはエンタープライズアーキテクチャパターンの内容が腹に落ちました。
http://www.amazon.co.jp/dp/4798121967
http://www.amazon.co.jp/dp/4798105538
おさらい:基本的なアプリケーションレイヤー
いくつかパターンがありますが、4層にわける方法が分かりやすかったです。
- プレゼン層: View やデータローダー、外部連携
- サービス層: プレゼン層にたいして粗い API を提供、ドメイン層への Facade でもある。この層でリモート Facade にすることも。
- ドメイン層: データとビジネスロジックを持つ。細かい API を提供する。
- データ層: データソースと CRUD したり、オブジェクトに変換したり
繰り返しになりますが、個人的に Django が分かりにくかったのが、プレゼン層のモデルとドメイン層とデータ層がひとつにまとまっていたように見えたところです。このあたりを分割するのがキモになりました。
サービス層の導入
- インターフェースと引数
一つのビューに対して一つのサービスを提供するようにしました。メソッドのパラメータが複雑化することがいやなので、Data Transfer Object っぽいパラメータをわたして処理させました。
- サービスの分類
サービスも二種類にわけました。
一つは基本的な CRUD を処理するもの。命名規則も XxxService。
もうひとつは複数のオブジェクトを結合して、複雑な計算・処理をするもの。命名規則も XxxEngine。
色々な解説をよんでもよくわからんかったので、以下のコードを参考にしました。
http://www.infoq.com/jp/news/2015/04/ddd-trading-example
https://github.com/archfirst/bullsfirst-server-java
- サービスの Singleton 化
サービスのインスタンスを都度作成するのも非効率なので Singleton 化しました。
- Strategy の導入
これは今後の課題ですけど。
XxxEngine の中に似たような処理がいくつかありました。これは Strategy パターンで分離すれば、もっと見通しがよくなるハズです。
モデルの扱い
Django のモデルはデータ層と見なすことにしました。
ドメイン層の導入
今後の予定ですが、ドメイン層を導入しようと考えています。Django のモデルを継承したクラスでドメインロジックを実装します。
昔、テーブルからオブジェクトに変換するときには、POJO(Plain Old Java Object)へマッピングしていました。ドメインモデルとしてロジックを実装するには POJO を継承したクラスで対応していました。
それを参考に、POMO(私の造語w:Plain Old Model Object) を継承して、ドメインモデルにしよう考えているわけです。
まとめ
まだ微妙な気がしますが、現状はこんなところです。
Rails でサービス層を導入した話をみかけて「同じことを考える人がいるものだなー」と思い、Django で考えたこと/対応したことをまとめました。
ただ、今回悩んだことはエンタープライズアプリケーションパターンで語られていた問題でした。今となってはフレームワークが隠蔽していて意識していないものが多くあります。しかしフレームワークの限界を超えようとするとき、基本をしっかり知っていることが大事だなー、と思ったのでした。