この記事はHamee Advent Calendar 2017の10日目の記事です。
今年もネクストエンジンAPIエクスプローラーネタです。
作った時からわかっていたのですが、Railsである必要が全くない(DB使っていない、JSONでおしゃべりするだけ)アプリケーションなので、もっと軽量なフレームワークにしたいなーと思って早2年。
いまさら「Sinatraにしました」ではアドカレネタには弱いので、RubyKaigi2017でも話題になったHanamiに書き換えてました。
Hanami - New Ruby Web Framework
Hanamiとは
The web, with simplicity http://hanamirb.org
Railsよりも軽量でSinatraよりも機能が豊富。ORM(not activerecord)、ビューヘルパー、コードジェネレータ、コンソールと欲しい機能はわりと最初からそろっています。
DDDに強い影響を受けていて、例えばRailsでいうModelがRepositoryとEntityに、ControllerがControllerとViewに、ViewがTemplateという具合に分かれています。
また、1アクション1クラスにするのがHanami流のようで、Railsよりも細かく細かく処理を分けていくことをフレームワークに後押しされます。
インストール1
gem install hanami
アプリケーションの生成
Railsライクに以下のコマンドで行けます。
hanami new YOUR_APP
ディレクトリ構成(一部省略)
tree -L 3
.
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile
├── apps
│ └── web
│ ├── application.rb
│ ├── assets
│ ├── config
│ ├── controllers
│ ├── templates
│ └── views
├── config
│ ├── boot.rb
│ ├── environment.rb
│ └── initializers
├── config.ru
├── db
│ ├── migrations
│ └── schema.sql
├── lib
│ ├── ne_api_explorer
│ │ ├── entities
│ │ ├── mailers
│ │ └── repositories
│ └── ne_api_explorer.rb
└── public
└── assets
└── favicon.ico
Railsにどっぷり使った身としては
- modelがない・・・だと・・・?
- webの下にcontrollerとviewのディレクトリがある。なるほど
- viewとtemplateが分かれてる・・・じゃあviewとは?
- webの外側lib配下にentitiesとrepositories・・・?
といったところに戸惑うわけです。
コードジェネレータ
Hanamiにはコードの自動生成機能がデフォルトで備わっています。
hanami g action web sample#create --url=/sample
create apps/web/controllers/sample/create.rb
create apps/web/views/sample/create.rb
create apps/web/templates/sample/create.html.erb
create spec/web/controllers/sample/create_spec.rb
create spec/web/views/sample/create_spec.rb
insert apps/web/config/routes.rb
コントローラやビュー、テンプレートとルーティング、テストも自動的に生成されます。
ちなみにルーティングは以下のようなコードが生成されます。シンプルですね。
post '/sample', to: 'sample#create'
コントローラーとビューの置き換え
HanamiにはRailsと近いけど微妙に違うビューヘルパーがあります。2
例えばフォームはこんな感じ。Railsを知っている人なら素直に読めるのではないでしょうか。
<%=
form_for :authentication, '/authentication/new', class: 'form-horizontal' do
div class: 'form-group' do
label 'クライアントID', for: 'authentication-client-id', class: 'col-sm-3 control-label'
div class: 'col-sm-9' do
text_field 'client_id', class: 'form-control'
end
end
div class: 'form-group' do
label 'クライアントシークレット', for: 'authentication-client-secret', class: 'col-sm-3 control-label'
div class: 'col-sm-9' do
text_field 'client_secret', class: 'form-control'
end
end
div class: 'form-group' do
div class: 'col-sm-offset-3 col-sm-9' do
submit '送信', class: 'btn btn-default'
end
end
end
%>
コントローラーはこんな感じ
module Web::Controllers::Authentication
class Create
include Web::Action
include UserInteractor::Util
def call(params)
auth = NeAPI::Auth.new(redirect_url: authentication_url)
response = auth.ne_auth(params[:uid], params[:state])
session[:access_token] = response['access_token']
session[:refresh_token] = response['refresh_token']
redirect_to '/'
end
end
end
素のRackアプリケーションを触ってるように見えますが、redirect_to
だったりbefore
でコールバックをかけたりと必要な機能は用意されているという印象です。
で、ビューはどうなんだ
module Web::Views::Explorer
class Execute
include Web::View
template 'explorer/index'
end
end
こんな感じでほとんど何もしていません。template
でアクションのURLとは別のテンプレートを呼び出しているくらい。
モデルの置き換え
公式サイトや紹介記事を読んでいくとrepositoriesがデータソース層(ORMあり)でentitiesがビジネスロジック。データベースを使っていないので今回はエンティティのみ使います。
class Endpoint < Hanami::Entity
def self.all
@endpoints ||= YAML.load_file("#{Hanami.root}/config/api.yml")["endpoints"]
end
def self.all_to_hash
all.each_with_object({}){|(target, methods), obj|
methods.map{|method, name| obj[name] = target + "_" + method}
}
end
end
ほとんどビジネスロジックらしいものがないのでこんな感じ。個人的にはRailsでFat Modelを経験しているので、repositoriesとentitiesが分かれていることで肥大化は防げそうな予感はしている。
Interactorという概念
ここまで書いてきて困ったことがありました。コントローラーでの共通処理である認証制御を書く場所はどこだ?
調べたところHanamiではInteractorという概念があり、ここに書くのが良さそうです。
HanamiはRubyの救世主(メシア)となるか、愚かな星と散るのかによると
最後に Interactor の話をしよう。 Interactor は Hanami - Guide に登場しない。 しかし DDD の観点からすると非常に重要な層なので説明する。 Interactor の責務はビジネスロジックをまとめることだ。
##まとめ
最近、現場で役立つシステム設計の原則〜変更を楽で安全にするオブジェクト指向の実践技法を読んだこともあり、DDDやWebアプリケーションにドメインモデルをいかにうまく組み込むかについて書かれたとてもいい本です。そのあたりに興味津々でタイムリーだったこともあり、Hanamiに触れてみました。
私はRails大好きなのですが、ActiveRecord脳に染まった私にはWebアプリケーションの設計を見直すよい機会になったと思います。アンチRailsな人に触ってみてほしい。
正直全然使いこなせてる感はないのですが、Railsだと大まか過ぎてconernsに持っていくかservice層用意する?みたいなところもフレームワークがあらかじめ用意してくれているのはよいのかなと思います。
出来上がったソースはこちら。ツッコミ・プルリク大歓迎です。