Rails/Laravel使いに送るドメインモデル~アクティブレコードの功罪~

みなさん、こんにちは！RailsやLaravel使ってますか?　ActiveRecord(LaravelではEloquent)ってめっちゃ便利ですね。ただ便利ゆえにActiveRecord以外の存在を知らない人がいるので、メリット・デメリットをまとめてみました。最終的にはドメインモデル入門になっています。

最初にRailsやLaravelから入った人(つまり僕)にありがちなのですが、ActiveRecordがどのようなものか理解せずに実装するため、ActiveRecordなのにロジックがないことがあります。また、ActiveRecordパターン以外を知らないのでActiveRecordのメリット・デメリットを理解してません。そこでActiveRecordがどのようなものかを説明していきたいと思います。

ただ、一年ほどRailsのコードに触れていないので、もし書き方がおかしかったら容赦なく突っ込んでくださし。また個人の見解が多分に含まれているので、皆さんの思うところがあるかもしれません。その時は、ガンガン言ってください。

## 注意
Railsのよさは結合度の高さによる実装の速さです。RailsはActiveRecordを前提としています。後半に紹介する``POROs``や``Repository``はRailsWayから外れたものです。この記事はRailsにRepository層を設けることを勧めているわけではなく、このような考え方もあるよという記事です。

もしDDD前提の設計をしたいのであればHanamiというフレームワークがおすすめです。
https://magazine.rubyist.net/articles/0056/0056-hanami.html

## ActiveRecordとは

マーチン・ファウラーという方が書いた「Pattern of Enterprise Application Architecture (PofEAA)」という本に、

> データベーステーブルまたはビューの行をラップし、データベースアクセスをカプセル化してデータにドメインロジックを追加するオブジェクト

と書かれています。ここからActiveRecordの役割が３つあることがわかります。

* データベースアクセス
* テーブルの行に対応するデータの保持
* ドメインロジックをもつ

### データベースアクセスは

```:ruby
user = new User(params)
user.save
```
のようにモデル自体に``save``や``create``を持つことです。

### テーブルの行に対応するデータの保持は

|id|name|
|---|---|
|1|ハト太郎|
|2|ハム助|

とテーブルがある場合、

```:ruby
user = User.find(1)
puts(user.id)  #ハト太郎
```

のように行単位で属性を保持するインスタンスを生成できます。

### ドメインロジックをもつは
例えば、20歳以下であれば料金が半額とする場合はコントローラーに

```:ruby
if user.age <= 20 then
# 料金半額
end
```

とするのではなく、モデルにロジックを追加して

```:ruby
class User < ApplicationRecord

  中略

  def isPriceHalf
    self.age <= 20
  end

end 
```

```:ruby
if user.isPriceHalf then
  # 料金半額
end
```

というふうにユーザーに関するロジック(ドメインロジックといいます)をユーザーモデルにカプセルしてしまうことです。重要なのは自身のデータを用いたインスタンスメソッドを実装することです。

## 歴史
そもそもアクティブレコードはドメインモデルの一つです。ドメインモデルはかんたんに説明するとオブジェクト指向にのっとり、データとそれに付随するロジックをクラスに閉じ込めたものになります。なぜそうするのが良いかというと、ロジックがデータと一緒にあることで、コードの重複が防げるからです。ここらへんは「現場で役立つシステム設計の原則(増田享)」という本に詳しく載っています。

データに付随するロジックをコントローラ層やサービス層に書くのは自由ですが、一応そういう考え方もあることを知っておいてください。

##  ActiveRecordのメリット・デメリット

### メリット

* テーブルと１対１にモデルが存在するので、テーブル設計が終わったあとすんなりと実装に入れる
* データと永続化メソッドが１つのモデルにあるので、実装スピードが早い

### デメリット

* ツールありき。自ら実装するのは割と手間(RailsとLaravelは標準装備なのでこれはデメリットではない)。
* テーブルと１対１にモデルが存在するので、モデルがテーブルに引っ張られる。例えばテーブルを変更するとモデルに影響を与えるので、テーブルとモデルの結合度が高い。
* ツールを利用した場合、モデルの継承(extend)を利用する事が多く１つしかできない継承を消費してしまう(Rubyはmixinという機能があるのであまり問題にならないかもしれない)。

ちなみにPofEAAには次のように書かれています。

> アクティブレコードの最大のメリットは、シンプルな構造である。アクティブレコードの構築は容易であり、また理解しやすい。最大の問題は、アクティブレコードが有効であるのが、アクティブレコードオブジェクトがデータベーステーブルと直接対応している(同一構造スキーム)場合だけという点である。

これはテーブルと１対１のモデル設計のメリット・デメリットですね。ほかにデメリットとして

> ビジネスロジックが複雑な場合には、オブジェクトの直接的な関係、コレクション、継承などを使用したいとまず考えるだろう。しかし、これらの部品は簡単にはアクティブレコードにマッピングできず、また、断片的に追加すると状況はより複雑になる。以上の理由からデータマッパーの使用を考えるようになる。

と書かれています。オブジェクト指向とリレーショナル・データベースは同一のものではありません。例えばrubyでの配列

```:ruby
array = ["a", "b", "c']
```

をデータベースに保存するときにどのように保存すればよいでしょうか？ リレーショナルデータベースはデータの横持ちが苦手なので、縦持ちにしてテーブルに保存するかもしれません。

また、``Carクラス``とCarクラスを継承する``HybridCarクラス``のデータを保存することを考えると、それぞれを保存するのはどうすればよいでしょうか？

```:ruby

class HybridCar < Car
  
  def doSomething 
  end

end 

```

おそらく、一般的には``type``属性を加えて継承を表現するかもしれません。様々な方法があるかもしれませんが、NoSQLとは違ってリレーショナルデータベースはこのような継承を表現するのが苦手です(苦手であってできないことはない)。アクティブレコードはリレーショナルデータベースのテーブルと密結合なので、テーブルが苦手な表現はアクティブレコードも苦手です(くどいができないことはない)。

##  Plain Old Ruby Objects(POROs)について
いままでActiveRecordしか使ったことのない方は、ぜひ他のモデルパターンを知ってほしいです。これから紹介するのは``POROs``と``Repository``です。もしデータベースとロジックの分離をしたいのであれば、こちらのパターンはおすすめです。

Plain Old Ruby Objectsは特になんのひねりもなくただのRubyの``Class``です。マーティンが単純なJavaのクラスをJavaBeansに対してPlain Old Java Objects (POJOs)と呼んだことにちなんでいます。

```
class Dog

  def initialize(name)
    @name = name 
  end

  def doSomething
    # do something
  end 
end
```
ただのRubyのClassなのでActiveRecordなどは継承していません。特定のツールに依存しないので、自分の自由に実装できます。外部API由来のモデルもDB由来のモデルもすべて同じように扱えます。継承を消費しないのでデザインパターンを適用しやすくなります。POROsはオブジェト指向を気持ちよく実装できます。

## Repositoryパターンについて
RepositoryパターンはドメインモデルのIOを担当します。今回だと上記のPOROsと外部API通信やデータベースへの永続化を担当します。Repositoryという層をはさむ事によってモデルはデータベースを知る必要がありません。また、データベース以外に外部APIをモデルにマッピングすることもできます。背後にActiveRecordを使ってもQueryBuilderを使っても構いません。

注意: Repositoryパターンは本来インターフェイスを用いて実装することが多いです。今回はクラスとして実装しています。

```:ruby
class DogRepository

  def self.findDogById(id)
    # ActiveRecordもしくはQueryBuilderによって実装
  end

  def self.save(dog)
    # ActiveRecordもしくはQueryBuilderによって実装
  end

end
```

```:ruby
class DogController < ApplicationController

  def show
    @dog = DogRepository.findDogById(params.id)
  end

  def create

    dog = new Dog(params)
    # dog.save()ではない
    DogRepository.save(dog)
    
  end

end
```

Repositoryパターンを使うとモデルはActiveRecordではなく、モデル自身に永続化のメソッドがないので、``dog.save()``ではなく、``DogRepository.save(dog)``となっていることに注目してください。Repositoryパターンではモデルは自身のロジックに集中してデータベースへの永続化の処理はRepositoryが担当します。モデルは永続化については気にしなくて良いのです。