LoginSignup
77
77

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-12-02

はじめに

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

77
77
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
77
77