Hanamiで動かすネクストエンジンAPIエクスプローラー


この記事は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

コントローラやビュー、テンプレートとルーティング、テストも自動的に生成されます。
ちなみにルーティングは以下のようなコードが生成されます。シンプルですね。

apps/web/config/routes.rb
post '/sample', to: 'sample#create'

コントローラーとビューの置き換え

HanamiにはRailsと近いけど微妙に違うビューヘルパーがあります。2
例えばフォームはこんな感じ。Railsを知っている人なら素直に読めるのではないでしょうか。

index.html.erb
<%=
  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
%>

コントローラーはこんな感じ

controllers/authentication/create.rb
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でコールバックをかけたりと必要な機能は用意されているという印象です。

で、ビューはどうなんだ

web/views/explorer/execute.rb
module Web::Views::Explorer
  class Execute
    include Web::View

    template 'explorer/index'
  end
end

こんな感じでほとんど何もしていません。templateでアクションのURLとは別のテンプレートを呼び出しているくらい。

モデルの置き換え

公式サイトや紹介記事を読んでいくとrepositoriesがデータソース層(ORMあり)でentitiesがビジネスロジック。データベースを使っていないので今回はエンティティのみ使います。

lib/ne_api_explorer/entities/endpoint.rb
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層用意する?みたいなところもフレームワークがあらかじめ用意してくれているのはよいのかなと思います。

出来上がったソースはこちら。ツッコミ・プルリク大歓迎です。

参考


  1. これを書いている時点で最新のv1.1.0ではRuby2.3.1以上が必要なので注意 

  2. 結局ルーティングのヘルパーの書き方がいまだによくわからない