LoginSignup
8
3

More than 3 years have passed since last update.

RailsエンジニアがDDDやクリーンアーキテクチャに触れるためにとりあえずHanamiを始めてみる

Last updated at Posted at 2020-03-19

Introduction

これまで Rails をそれなりにやってきて、ちょっとしたWebアプリならそれなりサクッと作れるようにはなりましたが、 Fat Controller だの Fat Model と言われるようにどこかで臨界点が来て、従来のMVCアーキテクチャとは違う別の設計を模索してみたい欲が出てきました :flushed:

そのとっかりとして、巷で話題の クリーンアーキテクチャ に触れるために、 Rails の対抗馬として名乗りを挙げた Ruby 製フレームワーク Hanami を始めてみようかと思いました :fire: :fire: :fire:

Install

まずは Hanami を立ち上げてみます。

$ mkdir hanami-tutorial
$ cd hanami-tutorial
$ bundle init

Gemfile を書き換えます。

Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "hanami"

ライブラリをインストールして Rails と同様にサーバーを立ち上げます。

$ bundle
$ bundle exec hanami new .
$ bundle exec hanami server

導入完了!と思いきや、2020年3月20日現在、エラーになりました :cry:

Bundler could not find compatible versions for gem "dry-types":
  In snapshot (Gemfile.lock):
    dry-types (= 0.12.3)

  In Gemfile:
    hanami (~> 1.3) was resolved to 1.3.3, which depends on
      hanami-validations (>= 1.3, < 3) was resolved to 1.3.6, which depends on
        dry-validation (~> 0.11, < 0.12) was resolved to 0.11.2, which depends on
          dry-types (~> 0.12.0)

    hanami-model (~> 1.3) was resolved to 1.3.2, which depends on
      dry-types (~> 0.11.0)

Running `bundle update` will rebuild your snapshot from scratch, using only
the gems in your Gemfile, which may resolve the conflict.

指示に従って、もう一度サーバーを立ち上げます。

$ bundle update
$ bundle exec hanami server

http://localhost:2300/ を開くと...

image.png

:yum:

Static Page

Rails チュートリアルと同様に、まずは静的なページを作ってみます。

ルーティングは Rails 感があります。

apps/web/config/routes.rb
root to: 'home#index'

アクションは Rails と違い独立したクラスを作ります。

apps/web/controllers/home/index.rb
module Web
  module Controllers
    module Home
      class Index
        include Web::Action

        def call(params)
        end
      end
    end
  end
end

ビューも Rails と違います。 Rails における View は Hanami においては ViewTemplate に分かれていて、 Rails の Helper などの UI に関わるロジックは View で、 HTML テンプレートは Template が担当します。

apps/web/views/home/index.rb
module Web
  module Views
    module Home
      class Index
        include Web::View
      end
    end
  end
end
apps/web/templates/home/index.html.erb
<h1>Bookshelf</h1>

http://localhost:2300/ を開くとページが差し替わったはずです。
これで自由にページを作れるようになりました :smile:

Template Engine

Hanami のデフォルトのテンプレートエンジンは Rails と同様に Erb ですが、 Slim や Haml で書きたいですよね。
Slim をインストールして、先ほど作ったテンプレートを書き換えます。

Gemfile
gem 'slim'
$ bundle
$ mv apps/web/templates/home/index.html.erb apps/web/templates/home/index.html.slim
apps/web/templates/home/index.html.slim
h1 Bookshelf

これで Slim は導入できました :thumbsup:

Pry

Ruby 2.7 以下だとデバッグのために Pry を導入したいですね。これも簡単。

Gemfile
gem 'pry'
$ bundle
$ bundle exec hanami console
[1] pry(main)> 

Ridgepole

意見は分かれますが、自分はプロトタイピング重視で、 Rails のマイグレーションではなく Ridgepole を使いたいです。

Gemfile
gem 'ridgepole'
$ bundle

Rails の database.yml に相当するものが無いので、便宜上新たに作ります。

config/database.yml
# ridgepole を使うために用意

development:
  adapter: sqlite3
  database: db/hanami_tutorial_development.sqlite

Ridgepole が使う Schemafile を生成します。まだテーブルを定義していないので空ファイルが出力されます。

$ bundle exec ridgepole -c config/database.yml -e > Schemafile

テーブルを作ってみます。

Schemafile
create_table :users, force: :cascade do |t|
  t.string :email, null: false
  t.timestamps null: false
  t.index :email, unique: true
end
$ bundle exec ridgepole -c config/database.yml -a
Apply `Schemafile`
-- create_table("users", {})
   -> 0.0052s
-- add_index("users", ["email"], {:unique=>true})
   -> 0.0035s

テーブルが作られたのでモデルを作ります。 Rails におけるモデルは Hanami においては RepositoryEntity に分かれます。クリーンアーキテクチャが見えてきましたね :point_up:

lib/hanami_tutorial/repositories/user_repository.rb
class UserRepository < Hanami::Repository
end
lib/hanami_tutorial/entities/user.rb
class User < Hanami::Entity
end

コンソールで動作確認してみます。

[1] pry(main)> UserRepository.new.create(email: 'test@example.com')
[2] pry(main)> UserRepository.new.find(1)
=> #<User:0x0000000000000000 ...
[3] pry(main)> UserRepository.new.users.where(email: 'test@example.com')
=> #<ROM::Relation::Composite name=users dataset= ...

Hanami は ActiveRecord ではなく Rom という ORM を使っていますが、 Rails エンジニアなら雰囲気で使えそうな気がしますよね? :grin:

Job Queue

Rails の ActiveJob に相当する非同期処理モジュールが Hanami にはありませんが、自分の経験上 Job Queue ミドルウェアを移行することはそんなに無いので、ここでは Sidekiq を入れてみましょう。

Gemfile
gem 'sidekiq'
config/sidekiq.rb
Sidekiq.configure_server do |config|
  config.redis = { url: ENV['REDIS_URL'] }
end

Sidekiq.configure_client do |config|
  config.redis = { url: ENV['REDIS_URL'] }
end

ワーカーを作ります。引数で与えられた秒数だけ待機して foo! と叫ぶだけのやつです。

lib/hanami_tutorial/workers/sleepy_echo_worker.rb
class SleepyEchoWorker
  include Sidekiq::Worker

  def perform(time)
    sleep time
    puts 'foo!'
  end
end

Sidekiq を立ち上げます。

$ REDIS_URL=redis://localhost:6379 bundle exec sidekiq -r ./config/boot.rb 

コンソールでワーカーを呼び出します。

[1] pry(main)> SleepyEchoWorker.perform_async(3)

Sidekiq を立ち上げたターミナルで、3秒間待機した後に foo! とログに出ることが確認できましたね :sunglasses:

Conclusion

Rails エンジニアが Hanami でWebアプリを開発するための初期導入についてまとめてみました。今後 Hanami で開発する際にハマったことがあればここに追記していこうかと思います。

ありがとうございました :bow:

8
3
2

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
8
3