![見出し画像](https://assets.st-note.com/production/uploads/images/40497620/rectangle_large_type_2_bf061006baeeeef8f8e38cb1ea34be0d.jpg?width=1200)
RoR経験者がHanamiをさわってみた
これは Code Polarisの12月14日分のアドベントカレンダーの記事です🎄
※Code Polarisとは、女性による女性のための技術コミュニティになります。
https://code-polaris.connpass.com
皆さん、こんにちは!
英語大好きエンジニアのえりりんです(*^^*)
現在は株式会社estieさんにて、業務委託のサーバーサイドエンジニアとして働いています。
久しぶりにRuby on Railsを使って仕事ができていて、楽しい日々を過ごしています💎
概要
季節に全く合わない桜の写真をアイキャッチにしてしまいましたが、
タイトルにあるHanamiとは、RubyのWebフレームワークの1つです。
よく使われているのはRailsですが、HanamiやSinatraというものもあります。
HanamiはDDDの思想に沿って作られているとのことなので、今回触ってみました。
(Hanamiについての解説は、るびまの記事がわかりやすいと思います。)
上記記事にもありますが、Railsのディレクトリ構造とHanamiのディレクトリ構造は似ているように見えます。
しかし、明確に決まりがあります。
データフローに関する部分は apps/ 以下に、 ビジネスロジックに関する部分は lib/ 以下に定義する。 apps/ 以下には Routing 、 Controller 、 View 、 Assets に関するもの、 lib/ 以下には Model と Interactor に関するものを置く。
るびまより引用
Hanami では Model を Repository と Entity の2層で表現している。
るびまより引用
この記事では、ModelのRepositoryを中心に、RailsとHanamiそれぞれのDBアクセスについて比較してみたいと思います。
本編
RailsのActiveRecordでは、アソシエーションをうまく利用して関連テーブルの値を取得することができます。
その結果、意図せずにいろんな箇所からDBにアクセスすることもできてしまいます。(これは私が初めてRoRを触ったときにビックリした点です)
一方、Hanamiは責務をしっかり分けようとしたリポジトリ構成になっています。
早速見てみましょう!
以下のような構成のテーブルとデータがあった場合を考えてみます。(ActiveRecordのアソシエーションの説明でよく見るあれです)
Railsの場合
アソシエーションは以下のように貼ります。
class Author < ApplicationRecord
has_many :books
end
class Book < ApplicationRecord
belongs_to :author
end
まず、Booksからid=1のデータをBooksController内で取ります。
def show
@book = Book.find_by(id: 1)
end
この時発行されるSQLは以下のようになります。
SELECT `books`.* FROM `books` WHERE `books`.`id` = 1 LIMIT 1
Controllerから @book をViewに渡します。
このとき、Viewに渡るのはBooksテーブルにある情報だけです。
しかし、実は著者名も必要だったという場合、Viewで著者名を取りに行くことができます。
<%= @book.author.name %>
これでめでたく著者名をViewに表示することができます!
。。。
でも、全然めでたくないんです。
なぜなら、この @book.author.name をしたタイミングで以下のSQLが発行されています。
SELECT `authors`.* FROM `authors` WHERE `authors`.`id` = 1 LIMIT 1
つまり、ViewからDBにアクセスしていることになります。
ControllerからもDBにアクセスしたし、ViewからもDBにアクセスした。。。
DBアクセスの責務が分散してしまっています。
正しく理解していないと、こんなことが起こります。
Hanamiの場合
次にHanamiの場合を見てみましょう(テーブルとデータの構成は同じです)。
アソシエーションも同様に貼ります。
class AuthorRepository < Hanami::Repository
associations do
has_many :books
end
end
class BookRepository < Hanami::Repository
associations do
belongs_to :author
end
end
HanamiにはRepositoryとEntityというRailsにはないものがあります。
Repositoryとは
Repository は SQL クエリを発行して、 RDBMS のテーブルに対する CRUD 操作を行う。 システム内で SQL クエリを発行する部分を Repository だけにすることで抽象度を揃えることができる。
るびまより引用
Entityとは
ActiveRecord パターンと異なり、 Entity は RDBMS のデータをオブジェクトとして扱うための入れ物に過ぎず、 SQL の発行等の RDBMS に対する操作は Repository で行う。
るびまより引用
この説明だけで、DBアクセスはRepositoryだけで行われていることがわかりますね。
ではViewへの表示を行っていきたいと思います。
Railsではcontrollersディレクトリ配下に BooksController.rbを配置し、その中にshowメソッドを書きました。
Hanamiでは、ControllerやView、Templateの中はメソッドごとにファイルを分けます。
controllersディレクトリ配下にbooksディレクトリが生成され、その中にshow.rbファイルを配置し、callメソッドを書きます。
expose :book
def call(params)
book_repo = BookRepository.new
@book = book_repo.find(1)
end
この時発行されるSQLは以下になります。
SELECT `id`, `name`, `author_id`, `published_at`, `created_at`,
`updated_at`
FROM `books` WHERE (`books`.`id` = 1) ORDER BY `books`.`id`
Viewでbookのnameは以下のように取れます。
<%= book.name %>
ではRailsで取れてた著者名はどうでしょう?同じように書いてみます。
<%= book.author.name %>
book.author が nil になっていて name が取れずにエラーになっていますね。
Hanamiで著者名を取りたいときは、以下のようにする必要があります。
book_repository.rbの中で明示的にBooksのデータとAuthorsのデータを一緒に返すメソッドを定義してあげます。
(参考:https://guides.hanamirb.org/associations/belongs-to/)
def find_with_author(id)
aggregate(:author).where(id: id).as(Book).one
end
Controller配下のshow.rbでの呼び方が変わります。
expose :book
def call(params)
book_repo = BookRepository.new
@book = book_repo.find_with_author(1)
end
Viewでは以下のように著者名を呼び出せます。
<%= book.author.name %>
まとめ
Railsでの、どこで誰がDBアクセスしているのかわからない問題は、便利な半面、やはり責務の面でとても違和感がありました。
知っていれば、そんなことはしないと思いますが、知らないと次々とやってしまいそうという印象です。
Hanamiは、ちゃんとRepositoryというSQL発行するためのクラスがあり、そこからしかアクセスしないという決まりを作っているのが、とても気持ち良いなと感じました。
それぞれの良さを見極めながら、開発できると良いですね❀
参考サイト
https://magazine.rubyist.net/articles/0056/0056-hanami.html
https://guides.hanamirb.org/introduction/getting-started/
https://little-hands.hatenablog.com/entry/2018/12/10/ddd-architecture
https://speakerdeck.com/davydovanton/viewing-ruby-blossom-kaigi2017
https://qiita.com/tyanakaz/items/2340a7205a041ff75d47