LoginSignup
0
0

Service嫌いとしてService Layerって何かをしっかり考えたい

Posted at

Webアプリケーションの設計において、Serviceというレイヤーを使うチームが結構多い。でもアプリのフォルダ構造をあらかじめ作っておいてくれる、Ruby on RailsやLaravelなどのフレームワークでは、Serviceというフォルダなど存在しない。

あれは一体なんなんんだ!!??

そこでServiceとは何かをしっかり考えてみたいと思う。

Martin Fowlerの説明

Patterns of Enterprice Application Architectureによると、Service Layerとは下記のものである

ServiceLayerSketch.gif

A Service Layer defines an application's boundary and its set of available operations from the perspective of interfacing client layers. It encapsulates the application's business logic, controlling transactions and coordinating responses in the implementation of its operations.

You probably don't need a Service Layer if your applications's business logic will only have one kind of client – say, a user interface – and its user case responses don't involve multiple transactional resources. In this case, your Page Controllers can manually control transactions and coordinate whatever response is required, perhaps delegating directly to the Data Source Layer.

要は普通のWebアプリにおいて、複雑なトランザクションを行わない限り、Service Layerは不要という主張。

Anemic Domain Model (ドメインモデル貧血症)

同じくMartin FowlerはAnemic Domain Modelに対する批判の中で下記のように主張しています。

One source of confusion in all this is that many OO experts do recommend putting a layer of procedural services on top of a domain model, to form a Service Layer. But this isn't an argument to make the domain model void of behavior, indeed service layer advocates use a service layer in conjunction with a behaviorally rich domain model.

Service Layerを勘違いして、ドメイン貧血症のデザインをする人が多いという批判です。DDDのEric Evans氏の言葉を引用して

This layer is kept thin. It does not contain business rules or knowledge, but only coordinates tasks and delegates work to collaborations of domain objects in the next layer down.

Now, the more common mistake is to give up too easily on fitting the behavior into an appropriate object, gradually slipping toward procedural programming.

ロジック(behavior)を適切なオブジェクトに収めることを諦め、(オブジェクト指向ではなく)手続き型プログラミングに落ち込んでいるという考察です。

私の経験と解釈

使うべきところでServiceが正しく使われているか?

自分が見てきたコードでいうと

  1. Web UIしかなくてもServiceを一様に使っているケースは多い。これらは複雑なトランザクションがなくても、すべてのアクションでServiceを使っている。よってMartin Fowlerの考えによると、使うべきでないところでもServiceが使われてしまっている。
  2. Serviceにロジックをすべて押し込めて、極度のドメイン貧血症を起こしているケースは多い。Service Layerを薄く留めて、ロジックがリッチなドメインオブジェクト集合体にdelegationしているケースは見たことがない。

Martin Fowlerが早くも2003年に懸念した通り、Serviceが厚くなりすぎて、ドメインモデル貧血症を起こしているコードは非常に多いと言える。

Serviceは自動テストが書きやすいが...

Railsの場合はControllerはRequestやResponseと密結合しているし、これが原因でテストがしにくいところがある。Serviceの方がテストしやすいのは全くその通りだと思う。ただ現実には、自動テストを全く書かないチームでもServiceを多く使うし、また自動テストを書く人もServiceのunit testを書くのではなく、controllerとviewも含めたintegration testしか書かない人が多い。つまりServiceは自動unit testが書きやすいのだかが、それが活用されていないケースが大半である。

Serviceを使う理由が自動テストということはなさそうだ。

Skinny Controllerを目指して

Skinny Controllerを目指してServiceを指向するケースは多いと感じている。RailsのMVCパターンの典型としてFat Model, Skinny Controllerはよく言われているので、従来はControllerに書いていた処理を別のところに引っ越しさせるのは一見うなずける。

その際、何も考えないのであれば、Serviceはとても魅力的である。

Martin Fowlerは次のように述べている

The anemic domain model is really just a procedural style design, exactly the kind of thing that object bigots like me (and Eric) have been fighting since our early days in Smalltalk.

私も(Serviceにロジックを押し込める)ドメイン貧血症はオブジェクト指向ではなく、手続型プログラミングだと思っている。実際Serviceはステートを持たせないので、Encapsulationの定義からしてオブジェクト指向にはなり得ない。

実は手続き方プログラミングは、fat controllerと相性が良い。Controllerはリクエストとモデル、そしてviewの連携をとるのが責務だが、これは1つのメソッドの中で手続型で記載することが多い。HTTPはそもそもがステートレスなプロトコルなので、とても自然である。

そしてServiceもまた、手続型プログラミングのパラダイムである。だからControllerの中のロジックをそのままServiceのメソッドに変換しやすい。

私が思うには、このようにほぼ何も考えなくてもfat controllerロジックをServiceに引っ越しできるのがServiceの最大の魅力ではないかと思っている。

Eric Evansが"give up too easily on fitting the behavior into an appropriate object"に込めた意味はこれではないかと思う。安易な道に流されるな!ちゃんとドメインモデルを考えろ!

改めてService Layerの問題を考える

こう考えると、Service Layerそのものに害があるのではなく(普通のWebアプリでは特に利点もないが)、それに起因するドメインモデル貧血症が問題だとわかる。

確かに私が見てきた限り、Service Layerを多用したアプリは確かにドメインモデル貧血症になっていた。したがってドメインモデル貧血症の問題点を整理した方が良さそうだ。

  1. オブジェクト指向の利点が失われる (特にビジネスのモデリングがしやすい点)
  2. ロジックが分散し、コードの再利用性が損なわれる
  3. ロジックを整理するガイドラインが失われる

オブジェクト指向の利点が失われることについて

私はオブジェクト指向の最大の利点はEncapsulationではないかと私は思っている。"Tell, don't Ask"と言われるように、ロジックを細かく指示出しをするのではなく、オブジェクトに簡単なメッセージを送るだけでプログラムを組み立てられる。

一方でドメイン貧血症になると、"Ask"的に記述になる。各オブジェクトの状況を細かく把握し、マイクロマネージメントする感覚で細かく指示を出すことになる。もちろん記述量は多くなり、複雑化する。

コードの再利用性が損なわれることについて

オブジェクト指向であれば、メソッドのコードはドメインモデルの中に収められる。一方でドメインモデル貧血症の場合は、メソッドのコードはServiceの中に記述する。そしてService Layerはアプリの外縁に存在するのでどうしても再利用がしにくい。

例えばPersonService IService IIで使われていたとする。Personにはfirst namelast nameがあり、Serviceの中ではこれを合体したfull nameが必要になったとしよう。

ドメインモデル貧血症の場合は、full nameのメソッドをPersonに持たせず、Serviceに持たせようと考える。この場合、気をつけないとService I及びService IIの両方でfull nameを独立に(二重に)記述してしまう。Service IIを書いているプログラマーはService Iを確認しない可能性が高いので、気付かないうちに同じコードを複数箇所に書いてしまいやすい。加えていざfull nameのコードを共有しようと思ったらfull name用のモジュールを作る必要が出てくるので、大袈裟になりやすい。

一方でリッチドメインモデルを使っていれば、full modelメソッドはPersonの中に記述する。Service IService IIも単にPersonのメソッドを呼び出せばよく、同じコードを複数箇所に書く心配がない。

これを避けようと思ったらPersonHelperとかPersonServiceみたいなのを作って、そこにPersonに関連するメソッドをまとめても良い。しかしこうなると結局はリッチドメインモデルと同じことをやっているだけなので、メリットは見えなくなってくる。

これは実際に多くのアプリで見られること

私が見ている限り、Service Layerを導入しているアプリでは、上記の問題は実際によく発生する。Serviceに固執しているレベルに応じて、ドメインモデル貧血症の問題は深刻になる。

Service Layerそのものが良いか悪いかではなく、Service Layerを導入するとドメイン貧血症になりやすく、そうなるとコードが大きく劣化する – こういう流れである。

解決策は?

Eric Evansが書いたことと反対のことをすれば解決になると私は思う。

Now, the more common mistake is to give up too easily on fitting the behavior into an appropriate object, gradually slipping toward procedural programming.

"fitting the behavior into an appropriate object" – これをしっかりやることが肝要であろう。ドメインをしっかりモデリングし、手続型プログラミングで問題解決を図るのではなく、オブジェクト指向プログラミングによる解決策を導き出す。そしてControllerから溢れたロジックは、ドメインモデルに持たせる。

もしすでにServiceを多く使っている場合でも、Serviceそのものを外していく必要はない。ただしServiceのLayerを少しずつ薄くしていくために、ロジックを少しずつドメインモデルにdelegateしていくようにすれば良いだろう。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0