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

# はじめに
Advent Calenderのコメント欄には、「JSのお話」と予告しておきながら、当日Rubyネタに変わる……。
あるあるの話ですね（笑）

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

なんですか、またフレームワーク宗教戦争ですか？
そうなんです。Ruby以外の言語でも、Railsライク、Sinatraライクって出てきて、飽きたんですよ（笑）

私も、担当サービスではPadrinoを使用していますが、
新たにアプリケーションを作る場合、何でもRails！という風にはしたくない派です。
Railsは、パフォーマンス面でどうしてもチューニング（特にActiveRecord）が必要になります。
そして、`config/` 下のファイルも多いなぁという印象です。

今回紹介したいのは、**[Hanami](http://hanamirb.org/)**。（旧Lotus。1-2-3じゃないよ。でも、一太郎派だったよ。）
http://hanamirb.org/

意外と[Qiitaの記事が多くなかった](http://qiita.com/search?q=lotus)ので、特徴を紹介しようと思います。

# Hanamiの特徴

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

一方で、HanamiもMonolith Firstではあるのですが、
「MVP（実用最小限の製品）」「Microservices」という考え方を基に、**Container**を組み合わせて、
1つのアプリケーションは小さく、そして継続的に機能拡張していける特徴があります。
後発のフレームワークなので、最近の開発手法にも合っている気がします。

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

- Rails…オールインワン
- Sinatra…シンプル
- Hanami…スケーラブル

という感じでしょうか。

Railsも、Engineを使えば、ごりっとmodular化はできますが、なかなか強引な感じですし、
Sinatraも、modularとして開発可能ですが、機能としては少なく、自分で実装する部分は多いと思います。
Hanamiは、ある程度の機能を持ちながら、拡張を意識して作られています。


## 主な特徴

### ディレクトリ構造

まず、

```bash
$ hanami new sample_app
```

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

```bash
$ tree -L 1 sample_app
sample_app/
├── Gemfile
├── Gemfile.lock
├── Rakefile
├── apps
├── config
├── config.ru
├── db
├── lib
├── spec
└── vendor
```

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

```bash
$ 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`）は、ライブラリとなります。

```bash
$ tree sample_app/lib
sample_app/lib
├── bookshelf
│   ├── entities
│   └── repositories
├── bookshelf.rb
└── config
    └── mapping.rb
```

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

```bash
$ cd sample_app
$ bundle exec hanami generate app admin
```

```bash
$ 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っぽいディレクトリ構成でも作成可能です。

```bash
$ hanami new tiny_sample_app --arch=app
```

```bash
$ 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
`entity`と`repository`に分かれています。
この辺は、DDDの思想を受け、ちょっとJavaっぽいところもあるかもしれません。

```rb:entity
class Book
  include Hanami::Entity
  attributes :title
end
```

```rb: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側で呼び出す時には、こうなります。

```rb: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`の種類が少ないですが、[こんな感じ](https://github.com/arthurgeek/lotus-redis)でRedisやMemcachedに対応するような動きが出てくるのは容易に想像できますし、拡張もしやすいです。

```rb
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`の組み合わせをここで決めます。

```rb:view
module Web::Views::Dashboard
  class Index
    include Web::View
  end
end
```

### Router

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

```rb: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)
```


### その他
`migration`、`helper`、`mailer`もあるので、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