Trailblazer 入門
Ginzarb 第35回 / @y-yagi
Trailblazer
- 通常のMVCとは異なる層を提供する
- Operation(サービスオブジェクト)、View Model(cells)、Form等
- 各処理を適切な層に記載する事で、view / controller / modelがfatになるのを防ぐ
- また、各層を疎結合にする事で各層毎でのテストをしやすくする
Trailblazer
- Webフレームワークではなくて、あくまでWebフレームワークと一緒に使用する事を前提としたライブラリ
- Rails + Trailblazer
- Sinatra + Trailblazer
- Grape + Trailblazer
- Hanami + Trailblazer
- etc
- Trailblazer自体にWebフレームワークとしての機能は無い
Rails辛い問題
- 気付くとfat controller
- fat controllerを避けようとした結果、今度はmodelがfatになる
- viewにビジネスロジックが記載されている
- ビジネスロジックをどこに書けば良いのかわからない
- プロジェクト / 人毎にビジネスロジックを定義する場所がバラバラになる
- etc
それらの問題を解決する為に作られたのがTrailblazer。 Rails標準では足りてない部分を補う。
Trailblazerのstack
-
trailblazer gemが提供するのは先の図でいうOPERATION部分
- 基本的に機能毎に、gemが分かれている(View Modelはcell等)
- 正確にはFORMもreform gemに分かれている
-
trailblazer gemのdependencyに含まれているのはreformだけ。後は必要に応じて追加する形。
- gemはそれ単体で使用出来るようになっている
-
trailblazer gemは使用した事無くても、cellは使用した事ある、という人もといるのでは
Controller
- HTTPのエンドポイントとしての処理のみを書く
- 当然ビジネスロジックは書かない
class CommentsController < ApplicationController
def new
form Comment::Update
end
def create
run Comment::Update do |op|
return redirect_to comments_path(op.model)
end
render :new
end
end
Model
- Modelにはassociaton、scope、finderのみを定義する
- 当然ビジネスロジックは書かない
- callback / validationも書かない
class Comment < ActiveRecord::Base
has_many :users
belongs_to :thing
scope :recent, -> { limit(10) }
end
Operation
- ビジネスロジックを定義する(service object)
- modelを保存する処理を定義するのはOperationだけ
- validation / callback / authorizationもOperationに含まれる
class Comment::Create < Trailblazer::Operation
contract do
property :body
validates :body, length: {maximum: 160}
end
def process(params)
if validate(params)
...
else
...
end
end
end
Form
contract do
property :body
validates :body, length: {maximum: 160}
property :author do
property :email
validates :email, email: true
end
end
Callback
- operationクラスで実行するCallback処理を定義する
- operationクラスに直接定義しても良いし、当然別ファイルに分けても良い
class Comment::Create < Trailblazer::Operation
callback :after_save do
# this is a Disposable::Callback::Group class.
end
end
class AfterSave < Disposable::Callback::Group
on_change :notify!
end
Policy
- 認可処理
-
punditっぽく書ける
- "policy class is heavily inspired by the excellent Pundit gem"との事
- punditで書いたpolicy classをそのまま使う事も可能
class Thing::Policy
def initialize(user, thing)
@user, @thing = user, thing
end
def create?
admin?
end
def admin?
@user.admin == true
end
# ..
end
View Model
class Comment::Cell < Cell::ViewModel
property :body
property :author
def show
render
end
private
def author_link
link_to "#{author.email}", author
end
end
<div class="comment">
<%= body %>
By <%= author_link %>
</div>
Views
- view層はそのまま使える
- ただ、パーツ毎にcellを使い、viewはシンプルな状態のままにしておくことが推奨されている
<h1>Comments for <%= @thing.name %></h1>
This was created <%= @thing.created_at %>
<%= concept("comment/cell",
collection: @thing.comments) %>
Representer
class SongRepresenter < Representable::Decorator
include Representable::JSON
property :title
property :track
end
SongRepresenter.new(song).to_json
#=> {"title":"Fallout","track":1}
File Layout
- classes, views, assets, policiesをまとめてconceptsディレクトリ配下におく
-
conceptsディレクトリはリリース毎に分ける
まとめ
- "Trailblazer gives you a high-level architecture for web applications"
- TrailblazerはMVCスタックにより適切な構成を構築する為の仕組み
-
trailblazer gem だけではなく、各層毎にgemが提供されている
- まずはview model(cell)を入れてviewを綺麗にしていく、というようなアプローチも出来る
気になること
- 学習コストの高さ
- Webフレームワーク + Trailblazerになるので、最初の学習コスト高そう
- どのくらいの規模になるとその学習コストがペイ出来るかは難しい
- fat operationにならない?
- callback / formを適切に分割出来れば大丈夫?
- この辺りの勘所は実運用してみないとわからなそう