RubyといえばRails、RailsといえばActiveRecordというくらいに、事実上ActiveRecordはRuby界隈におけるORマッパのデファクトスタンダードだ。しかしながら、Piotr Solnicaというソフトウェア・エンジニアは、ActiveRecordパターンが常にスケーラブルなアプリケーションにとっての最適解ではないという思想から、ROM.rbというRubyアプリケーションにおけるレイヤ分割を強力にサポートするためのORマッパー*1を開発している。
ROM.rbの何がよいのか?
長期間にわたってRailsアプリケーションの中でActiveRecordを使ってきた経験があれば、ActiveRecordにおいてモデルの中にロジックがてんこもりになり、コードベースが肥大化してきてしまうのに悩んだ経験があるのではないかと思う。ActiveRecordには、データベースの構造とドメインモデルの構造を密結合なものにすることによって開発者がアプリケーション/データベース間におけるデータ構造のインピーダンス・ミスマッチを意識する必要がなくなるという強力な利点がある。しかしながら、同パターンにおいて、モデルはドメインモデルとデータアクセスの責務を同時に担っており、モデルの中にデータベースの操作に関するロジックと、アプリケーション固有のドメインに関するロジックが入り混じってしまいがちだ。こうした困りごとに関して、Rails界隈では様々なリファクタリング・パターンが考案され、駆使されてきた。たとえば、GitLabやMastodonなどはActiveRecordとそれに関する責務分割のテクニックを駆使して、巨大だがメンテナブルなアプリケーションをRailsで構築してきた、いい例ではないかと思う。
一方で、ROM.rbはこうしたARにまつわる問題点を考慮し、データマッパーパターン*2でアプリケーションを開発するために作られた永続化周りのモジュールになっている。ROM.rbを使うことによって、アプリケーションにおけるモデルを、データアクセスの責務を持たない純粋なドメイン・モデルないしアプリケーション・モデルとして扱い、データの永続化に関する処理をレポジトリレイヤに抽象化しながら、実装をアダプタレイヤに任せることによって、強力かつ明確な責務の分離をすることができる。
ROM.rbの思想
ROMの思想を理解するにあたっては、いわゆるレイヤード・アーキテクチャと呼ばれる、アプリケーションをいくつかのレイヤに責務分割することで変化に強いアプリケーションを作る手法に言及する価値がある。
レイヤード・アーキテクチャ
たとえば、下はボブおじさん(Robert C Martin)と呼ばれるアプリケーション設計界隈(?)の有名人によって考案されたクリーン・アーキテクチャの図だ。
クリーンアーキテクチャ(The Clean Architecture翻訳) | blog.tai2.net
この図は、アプリケーションがいくつもの階層(レイヤ)によって分割されて作られているということを示している。アプリケーションをこのようなレイヤに分割する大きな利点は、アプリケーションに対する変化を柔軟にするということだ。開発者がアプリケーションを作るとき、そのアプリケーションには、ほぼ必ず固有の処理がある。固有の処理は、一般にビジネス・ロジック(ドメイン・ロジック)と呼ばれ、最もアプリケーションの中で重要なものとして扱われる。一方で、開発者である私達がアプリケーションを開発する際に使われる言語、フレームワーク、ミドルウェア、インフラストラクチャなどは、一般的にはビジネス自体とは強い関連を持たない。なぜなら、それらの事象はビジネスをアプリケーションで表現するためのツールでしかないからだ。
単なるツールであるものたちがアプリケーションのコアであるビジネス・ロジックと密接に関連してしまうと、アプリケーションは柔軟性を失うことになる。たとえば、PostgreSQLを使っていたアプリケーションが、のちにパフォーマンス改善のために新たなBetterSQLと呼ばれるミドルウェアを使うことになったとする。もしも、そうなった際に、アプリケーションのビジネス・ロジックがPostgreSQLに強く依存した設計になっていれば、アプリケーション全体の設計を見なおさなければならない。これが大規模なアプリケーションになればなるほど、この移行作業はキツイものになる。アプリケーションの設計とスケーラビリティの関係はこうした依存をいかに減らしていくかというところに関わる。アプリケーションのレイヤ分割の意義とは、できるだけ粗結合なレイヤをアプリケーションの中に仕切りのように設けることで、来たる未来に備えるということだ。
永続化レイヤのプラガビリティ
さて、永続化レイヤのモジュールであるROMが主要な関心事とするのは、上で触れた図における青(External Interface)と緑(Gateway)のあたりだ。ROMは、永続化レイヤをアプリケーションのためだけのものとして扱わない。例えば、RedisやMemcacheのようなオンメモリ・データベースだったり、サードパーティのWebAPIもまた、データベースと同等な永続化レイヤの一部として扱うことができる。これを実現しているのは、ROMがアプリケーションから外部への入出力をリポジトリと呼ばれる責務でカプセル化し、具体的な実装をリポジトリに差し込まれたアダプタに任せるからだ。開発者はROMのアダプタを使ったり、必要な場合には自分で作ることで、アプリケーションとそれに対する外部入出力を永続化レイヤの責務としてとりまとめることができる。このようなアダプタによるプラガビリティの利点は、アプリケーション自体がその外側の具体的な実装を気にしなくて良くなるという点にある。
より具体的にROMの思想を知るには、ヘキサゴナル・アーキテクチャの図を見るのが良い。
ヘキサゴナルアーキテクチャ(Hexagonal architecture翻訳) | blog.tai2.net
簡単に言うと、ヘキサゴナル・アーキテクチャは、アプリケーションとその外部の間にアダプタをおくことによって、アプリケーションを粗結合にするというものだ。たとえば、それまでアプリケーションでStripeを使っていたが、とある事情でそれをPayJPに乗り換える必要が出てきたとする。この場合、アプリケーションの中で決済処理を行う箇所の具体的なAPIの操作がアダプタによって綿密にラップされていれば、アダプタをStripeのものからPayJPに差し替えるだけでよくなる。とりわけ、サードパーティAPIなどに依存しているアプリケーションであれば、WebPayのサービス終了などの事例を顧みるに、アプリケーションを出来る限り外の世界と粗結合にしておくことにこしたことはないだろう。
FP + OO
これ以外にも、ROM.rbは関数型プログラミングとしてのアプローチや、オブジェクト指向性を強く意識しているらしいが、さほど今回のイントロダクションでは重要ではないのでここでサラッと触れる程度に留める。
さあ使ってみよう!
残念ながら書くのにとても疲れたので、ROM.rbの具体的な使い方については、次の記事にしようと思う*3。自分で試しにアプリケーションを書いてみても良いが、ネット上にはROM.rbに関する記事が恐ろしく少ない。公式のドキュメントもはっきり言ってとても少ない。だが、英語の記事はググるとわりとでてくるので、それらを試しに読んでみると良いだろう。
また、ROM.rbの作者はオーストラリアにあるicelabと呼ばれる企業のエンジニアだが、現地で開催されたRubyConfAU 2017でのROM.rbに関する講演がこのモジュールの思想や具体的な使い方を知る手助けになるだろう。このためだけに英語をしっかりと学ぶ価値さえあるのではないか、というくらいだ。
追記
第2回書きました izumisy-tech.hatenablog.com 第3回も書きました izumisy-tech.hatenablog.com
*1:厳密には、作者はROM.rbを単なるORマッパーではないと述べているが、この記事では一旦ORマッパーとして理解を統一する
*2:ActiveRecordのように永続化レイヤとモデルの構造を一致させず、間にマッピング・オブジェクトを挟むことで責務の分離を行うパターン(http://bliki-ja.github.io/pofeaa/DataMapper/)
*3:「イントロダクション」などと記事の名前をつけた手前、詳しくROM.rbの使い方を解説していく予定ではあるが、いつになるかは分からない