概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: Controllers: the Rails way vs the Hanami way
- 原文公開日: 2018/05/23
- 著者: Eduardo Figarola
対決!Rails wayとHanami way: コントローラ編(翻訳)
このところ私の関心はHanamiに向かっており、作者のLuca Guidiがこれまで選んできたアーキテクチャ設計や決定が大のお気に入りです。
そこで私は、RailsとHanamiをコンポーネントごとに比較し、記事が長くなりすぎないようそれぞれ別記事を書くことに決めました。
本シリーズの第1回目です。MVCデザインパターンになぞらえて今回のタイトルは「コントローラ」で始めることにします。
それでは、RailsとHanamiそれぞれのフレームワークにおける実際のコントローラについて見ていきましょう。
コントローラの基本
Railsの場合
Railsのコントローラは、(その前にルーターが扱った)リクエストを受け取って処理し、出力をリクエスト元(クライアント)に送信する役割を担うクラスです。
コントローラ内にある複数のpublicインスタンスメソッドは、それぞれ異なるリクエストを処理します。次のコードをご覧ください。
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def index; end # GET /users
def show; end # GET /users/1
def new; end # GET /users/new
def create; end # POST /users
def edit; end # GET /users/1/edit
def update; end # PATCH /users/1
def destroy; end # DELETE /users/1
end
上でお見せしたのはRESTfulなコントローラの例です。これはRailsで何かを行う場合のベストプラクティスのひとつであり、アプリのリソースへのアクセスを提供する場合は常にRESTfulにします。
Hanamiの場合
Hanamiのコントローラはモジュールになっており、複数のアクション(これらはクラスです)を束ねる役割だけを担います。アクションは、(こちらもその前にルーターが扱った)特定のリクエストを処理して出力をクライアントに送信する役目を担います。
# apps/web/controllers/users/index.rb
module Web::Controllers::Users
class Index
include Web::Action
def call(params)
end
end
end
# apps/web/controllers/users/show.rb
module Web::Controllers::Users
class Show
include Web::Action
def call(params)
end
end
end
...
上の2つのアクションで、何が行われているかはだいたい見当がつくかと思います。
相違点
Railsの場合、必要に応じて1つのコントローラクラスにいくつものpublicインスタンスメソッドを持ちます。
Hanamiの場合、必要に応じて1つのコントローラモジュールにいくつものアクションクラスを持ちます。Hanamiのアクションクラスは、publicインスタンスメソッドである#call
を定義するためだけに必要とされています。
どちらがどうよいのか
私の意見としては、Hanamiアーキテクチャの方がクリーン度が高いと思います。というのも、1つのアクションが1つのクラスになっていることで、「1つのクラスの責務はひとつでなければならない」という単一責任の原則を満たせるからです。
一方、Railsのコントローラの問題として非常によく知られているのは、コントローラクラスがだんだん巨大化して読むのもひと目で理解するのもつらくなってしまうという問題です。「コントローラは薄く、モデルは厚くせよ」というRailsのベストプラクティスはご存知かと思いますが、コントローラが肥大化する心配がまったくないに越したことはないとは思いませんか?
Hanamiの場合、アクションの肥大化はかなり簡単に回避できます。1つのクラスが1つのリクエストを扱うことが必須になっていますし、コントローラの複数のアクションでコードが重複したら、Ruby組み込みのソリューションであるモジュールが普通に使えます。繰り返し使われるコードを1つのモジュールに配置して、それを必要とするアクションクラスでinclude
すればおしまいです。次の例をご覧ください。
# apps/web/controllers/users/set_user.rb
module Web::Controllers::Users
module SetUser
def self.included(action)
action.class_eval do
before :set_user
end
end
private
def set_user
@user = UserRepository.new.find(params[:id])
halt 404 if @user.nil?
end
end
end
# apps/web/controllers/users/show.rb
require_relative './set_user'
module Web::Controllers::Users
class Show
include Web::Action
include SetUser
def call(params)
# ...
end
end
end
# apps/web/controllers/users/edit.rb
require_relative './set_user'
module Web::Controllers::Users
class Edit
include Web::Action
include SetUser
def call(params)
# ...
end
end
end
キリリと引き締まったコードですね。
ビューテンプレートへの変数公開
コントローラは、データをビュー層に公開する役割を担います。HanamiとRailsのやり方は非常に似通っていますが、わずかに異なる点があります。
Railsの場合
Railsでは、コントローラ内で宣言したインスタンス変数はすべてテンプレート(ビュー)からアクセスできるようになっています。
Hanamiの場合
Hanamiの場合、expose
というクラスメソッドを使わないとインスタンス変数を公開できません。次の例をご覧ください。
# apps/web/controllers/users/index.rb
module Web::Controllers::Users
class Index
include Web::Action
expose :users # @usersインスタンス変数を公開する
def call(params)
@users = UserRepository.new.all
@another_instance_variable = {} #これはビュー/テンプレートからアクセス不可能
end
end
end
まとめ
ここまで読んだ方なら「RailsでやれることをHanamiでやろうとするとコード量が増える」とお思いかもしれませんし、実際そうかもしれません。Railsアーキテクチャの設計は「設定より規約(Convention over Configuration)」に依存する部分が多くなっています。Railsで作業していると魔法のように感じられるのはそのためです。
一方Hanamiの規約は少なく、意図を明確にする方向に開発者を強制します。私見ですが、コントローラのアクションを個別のクラスにするアイデアは実に素晴らしいと思います。1つのリクエストを受け取るときに何が起きるか、そこだけに注意を払えばよいのですから。このアーキテクチャは正しいように思えます。
本シリーズの次回では、Railsのモデル(ActiveRecord)とHanamiのモデルドメイン(エンティティとリポジトリ)が対決します。乞うご期待!
補足: RubyKaigi 2018にもHanami登場
明日からのRubyKaigi 2018の第一日目で、Anton Davydov氏がHanamiアプリのアーキテクチャをテーマに登壇します。
追記: ツイートより
Hanamiのコントローラはホント良いと思っている派です。
なんかHanamiの記事増えてきた?
この前勉強会行ったときも懇親会でHanamiの話して盛り上がったし、徐々に来てるのか?対決!Rails wayとHanami way: コントローラ編(翻訳) https://t.co/IH0dke1AQ3
— fakiyer (@fakiyer1) May 30, 2018
最近、RailsのControllerを保守しやすく書く方法として各エンドポイントのメソッドをActionモジュールとして切り出す独自実装を入れてたんだけど、Hanamiが同じような実装だった。独自実装が有名FWの思想に近くてちょっと嬉しい。https://t.co/vurEqQZ4SU
— 山下 徳光 / Norimitsu Yamashita (@nori3tsu) June 5, 2018