Edited at

Hanami(旧:Lotus) - MicroservicesのためのRubyフレームワーク

More than 3 years have passed since last update.


はじめに

Advent Calenderのコメント欄には、「JSのお話」と予告しておきながら、当日Rubyネタに変わる……。

あるあるの話ですね(笑)

さて、Rubyでアプリケーション作る場合、みなさんやっぱりRailsなんですかね?それともSinatra?

なんですか、またフレームワーク宗教戦争ですか?

そうなんです。Ruby以外の言語でも、Railsライク、Sinatraライクって出てきて、飽きたんですよ(笑)

私も、担当サービスではPadrinoを使用していますが、

新たにアプリケーションを作る場合、何でもRails!という風にはしたくない派です。

Railsは、パフォーマンス面でどうしてもチューニング(特にActiveRecord)が必要になります。

そして、config/ 下のファイルも多いなぁという印象です。

今回紹介したいのは、Hanami。(旧Lotus。1-2-3じゃないよ。でも、一太郎派だったよ。)

http://hanamirb.org/

意外とQiitaの記事が多くなかったので、特徴を紹介しようと思います。


Hanamiの特徴


思想

Railsは、「アプリケーションが扱うDBは1つである」の通り、作りがどうしても巨大なMonolith(一枚岩)になってしまいます。小さいアプリケーションを作りたくても、最初からでかいです。

一方で、HanamiもMonolith Firstではあるのですが、

「MVP(実用最小限の製品)」「Microservices」という考え方を基に、Containerを組み合わせて、

1つのアプリケーションは小さく、そして継続的に機能拡張していける特徴があります。

後発のフレームワークなので、最近の開発手法にも合っている気がします。

特徴を一言でいうならば、


  • Rails…オールインワン

  • Sinatra…シンプル

  • Hanami…スケーラブル

という感じでしょうか。

Railsも、Engineを使えば、ごりっとmodular化はできますが、なかなか強引な感じですし、

Sinatraも、modularとして開発可能ですが、機能としては少なく、自分で実装する部分は多いと思います。

Hanamiは、ある程度の機能を持ちながら、拡張を意識して作られています。


主な特徴


ディレクトリ構造

まず、

$ hanami new sample_app

を実行してできる中身は、こんな感じです。

$ tree -L 1 sample_app

sample_app/
├── Gemfile
├── Gemfile.lock
├── Rakefile
├── apps
├── config
├── config.ru
├── db
├── lib
├── spec
└── vendor

1つのContainerはこんな感じです。

$ tree sample_app/apps/web

sample_app/apps/web
├── application.rb
├── config
│ └── routes.rb
├── controllers
├── public
│ ├── javascripts
│ └── stylesheets
├── templates
│ └── application.html.erb
└── views
└── application_layout.rb

一方でデータソースを扱う部分(model)は、ライブラリとなります。

$ tree sample_app/lib

sample_app/lib
├── bookshelf
│ ├── entities
│ └── repositories
├── bookshelf.rb
└── config
└── mapping.rb

もし、フロントのWebアプリケーションを作成した後に、管理ツールが欲しくなった場合は、こうなります。

$ cd sample_app

$ bundle exec hanami generate app admin

$ tree -L 2 sample_app/apps/

sample_app/apps/
├── admin
│   ├── application.rb
│   ├── config
│   ├── controllers
│   ├── public
│   ├── templates
│   └── views
└── web
├── application.rb
├── config
├── controllers
├── public
├── templates
└── views

Railsも、頑張ってディレクトリ構造を変えて、複数アプリをmountするようなこともできますが、

Hanamiは、始めからその想定で作られています。

本当に小さいサイズで済んでしまうアプリケーションの場合は、プロジェクト作成時に--arch=appを付けて、Railsっぽいディレクトリ構成でも作成可能です。

$ hanami new tiny_sample_app --arch=app

$ tree -L 1 tiny_sample_app

tiny_sample_app/
├── Gemfile
├── Rakefile
├── app
├── config
├── config.ru
├── db
├── lib
├── public
└── spec

$ tree -L tiny_sample_app/app
tiny_sample_app/app
├── controllers
├── templates
│   └── application.html.erb
└── views
└── application_layout.rb


Model

entityrepositoryに分かれています。

この辺は、DDDの思想を受け、ちょっとJavaっぽいところもあるかもしれません。


entity

class Book

include Hanami::Entity
attributes :title
end


repository

# .persist(entity) – Create or update an entity

# .create(entity) – Create a record for the given entity
# .update(entity) – Update the record corresponding to the given entity
# .delete(entity) – Delete the record corresponding to the given entity
# .fetch(raw) – Fetch raw datasets for the given raw query string (eg. SQL)
# .execute(raw) – Execute raw command (eg. SQL)
# .all - Fetch all the entities from the collection
# .find - Fetch an entity from the collection by its ID
# .first - Fetch the first entity from the collection
# .last - Fetch the last entity from the collection
# .clear - Delete all the records from the collection
# .query - Fabricates a query object
class BookRepository
include Hanami::Repository

def self.raw_all
fetch("SELECT * FROM books")
end

def self.find_all_titles
fetch("SELECT title FROM books").map do |book|
book[:title]
end
end

def self.max_price
result = 0

fetch("SELECT price FROM books") do |book|
result = book[:price] if book[:price] > result
end

result
end
end


Action側で呼び出す時には、こうなります。


controller

module Web::Controllers::Books

class Index
include Web::Action

expose :books

def call(params)
@books = BookRepository.all
end
end
end


さらに、Modelごとに、どのデータソース(File/Memory/SQL)からデータを取ってくるかを選べます。

Memoryはまだadapterの種類が少ないですが、こんな感じでRedisやMemcachedに対応するような動きが出てくるのは容易に想像できますし、拡張もしやすいです。

Hanami::Model.configure do

##
# Database adapter
#
# Available choices:
#
# * File System adapter
# adapter type: :file_system, uri: 'file:///db/bookshelf_development'
#
# * Memory adapter
# adapter type: :memory, uri: 'memory://localhost/bookshelf_development'
#
# * SQL adapter
# adapter type: :sql, uri: 'sqlite://db/bookshelf_development.sqlite3'
# adapter type: :sql, uri: 'postgres://localhost/bookshelf_development'
# adapter type: :sql, uri: 'mysql://localhost/bookshelf_development'
#
adapter type: :sql, uri: ENV['BOOKSHELF_DATABASE_URL']

# ...
end.load!


View

私的にはMojaviの記憶がよみがえりますが、view層があります。

どういった表示にするか、templateの組み合わせをここで決めます。


view

module Web::Views::Dashboard

class Index
include Web::View
end
end


Router

Rackベースで書こうと思えば、こんな書き方もできます。


router

get '/proc',       to: ->(env) { [200, {}, ['Hello from Janami!']] }

get '/action', to: "home#index"
get '/middleware', to: Middleware
get '/rack-app', to: RackApp.new
get '/rails', to: ActionControllerSubclass.action(:new)


その他

migrationhelpermailerもあるので、Rails、Sinatraユーザであれば、問題なく使えると思います。


パフォーマンスの違い

鵜呑みは良くないですが、かるーくSinatraの倍のパフォーマンスは出ている気がします。

https://github.com/luislavena/bench-micro

http://blog.ragnarson.com/2015/03/23/lotus-performance-tested-against-sinatra.html


さいごに

まだ、2014年6月出たばかりで、2015年12月現在v0.5.0です。

ますます、今後に期待です!


参考

http://hanamirb.org/guides/

https://speakerdeck.com/jodosha/lotus-for-rails-developers

https://speakerdeck.com/jodosha/lotus-rubyday-2015

https://blog.codeship.com/a-survey-of-non-rails-frameworks-in-ruby-cuba-sinatra-padrino-lotus/

https://speakerdeck.com/yyagi/about-lotus-framework

https://speakerdeck.com/takai/microservices-in-action