Kaigi on Railsの参加レポートと管理画面用のAPIを統合した話

はじめに

この記事は「技術カンファレンス Advent Calendar 2023 | 2枚目」の12日目です!

こんにちは、Plex Job開発チームの種井です。

今回は10月27日・28日で開催されたオフラインイベント、Kaigi on Railsの参加レポートと、その中でのSMART BANK @ohbaryeさんの発表「管理画面アーキテクチャパターンの考察と実践」が同時期にPlex Job開発チームでも取り組んでいたリアーキテクティングと重なる部分があったため、発表内容を一部をお借りし、我々の取り組みについても紹介したいと思います。

Kaigi on Rails 2023の参加レポート

元々オンライン開催されていた時の登壇資料やアーカイブ動画を見たことがあったのでイベント自体は認知していました。 今回、初のオフライン開催であることや、会社でカンファレンスの参加費用補助制度もできたので、制度を活用して参加してみることにしました。

2日間、現地で講演を聞きましたが、どの発表も非常にわかりやすく、学びになるものでした。 特にRailsをプロジェクトで運用する中で発生するパフォーマンスや保守性の担保に関するものが多かった印象です。 Railsを採用していると立ち上げのスピードと引き換えに、システム規模の拡大とともにパフォーマンスや保守性の壁にぶつかることが多いです。 サービスのスケール時に技術選定から見直すこともあるかと思いますが、なるべくRailsで培った資産を有効に活用していきたい、そういった場合のノウハウが詰まっていました。

基調講演ではRailsコミッターであるzzakさんやbyrootさんの発表がありました。 お二人ともRailsへ携わるきっかけや、課題の特定と解決に向けた取り組み、OSSプロジェクトでの立ち回りで重要なことについて、経験を交えて紹介されていましたが、チームや個人としての成果を出す上では私達が普段プロダクト開発においても意識していることと重なる部分も多いと感じました。

また、イベントとしても初のオフライン開催とは思えないほどスムーズかつ丁寧な進行で、スポンサーブース、懇親会ともに終始楽しく参加することができました。 オフラインならではの熱気があり、私自身、刺激を多く受けて帰ってきました、改めてオフラインイベントのよさを感じた2日間となりました。

ちなみにESMさんのブースのガチャガチャでは#refineメソッドが当たりました。

Kaigi On Rails

さて、今回の講演の中で特に印象に残ったものの一つとして、SMART BANK @ohbaryeさん「管理画面アーキテクチャパターンの考察と実践」の発表があります。

プロダクト開発を行っていると社内向けの管理画面が必要になる機会が訪れます。 管理画面を開発する上で取られるアーキテクチャパターンを体系的化し、ビジネス要件とアーキテクチャ特性から採用したアーキテクチャと、それをRailsで設計・実装する上での工夫について非常にわかりやすく紹介されていました。

ちょうどKaigi on Railsの開催期間中に我々も管理画面のリアーキテクティングに取り組んでいたこともあったので、発表内容をお借りして、我々の取り組みについて紹介をしたいと思います。

社内管理画面のAPI統合

背景

私達も同じく、初期フェーズは仮説検証を優先し、管理機能を運用でカバーしつつ後追いで徐々に管理画面化を進める方法をとっていました。

我々の移行前のアーキテクチャパターンは以下です。

資料中の「マイクロサービスで共有データベースパターン」です。 エンドユーザー向けのサーバーと管理機能のユーザ向けのサーバーを分けるが、同一のDBを参照するというものです。 私達の場合もメリットとして挙げられている、「立ち上げ時の実装を楽にしたい」に加え「管理機能からの時間のかかるリクエストによって、エンドユーザー向けの機能に影響を与えないようにしたい」ことからこちらのパターンを選定した背景があります。

立ち上げ期は単純なCRUD操作を中心に機能要件としても複雑なものは必要なかったのですが、事業の成長とともに、複雑で事業上重要な要件を持つオペレーションの管理機能化や社内ユーザーの増加もあり、保守性や安定性がより求められるようになってきたところですが

  • デグレやすい
    • 依存モジュールの更新やCIの整備が放置されている
    • 単体テストやlinterの整備などが放置されている
  • 開発者のスイッチングコストが大きい
    • 同じドメインのモデル定義や実装を異なるコードベース間で同期させる必要がある
    • デプロイ対象のサーバーコンポーネントが多い

上記のような、課題が大きくなってきました。 安定性や保守性の要件に対して、立ち上げ期ではなくなってきていることや、当初のパフォーマンス面での懸念事項も徐々に改善できていることから、アーキテクチャの変更に踏み切ることにしました。

変更後のアーキテクチャ

資料中の「既存Backendだけ再利用しつつ管理機能はPDS順守パターン」です。 エンドユーザー向けのサーバーと管理機能向けのサーバー、参照先のDBは同じで、フロントエンドをエンドユーザーと管理機能の利用者で分けるというものです。

これにより、以下のようなメリットを享受することができました。

  • 同じドメインを扱うモデル、コードの再利用が可能になった
  • コードベースを統合したことにより、比較的メンテナンスされているコードベースのテストやCIにのっかることができ、保守性やテスト容易性などが向上した
  • サーバーコンポーネントが減ることによって保守・運用コストが削減された

統合に向けてやったこと

今回の統合で上記のようなメリットは得られつつも、統合時に試行錯誤したこと、一部妥協し今後の課題として残っているところがいくつかあるため、その中の一部を紹介したいと思います。

Plex Jobの管理画面の構築には以下を使用しています。

  • Ruby (3.2.2)
  • Rails (7.1.1)
  • graphql-ruby (2.1.6)
  • react-admin (4.10.2)
  • ra-data-graphql-simple (3.19.5)

GraphQLスキーマを共存させる上で

Plex Job開発チームでは、管理画面へ提供するAPIを統合先のエンドユーザー向けと同じくGraphQLで作成されていました。 そのため、React AdminのData Providerである、ra-data-graphql-simpleの仕様に合わせて、GraphQLのスキーマを既存のスキーマに統合する必要があり、統合にあたって考慮が必要になるポイントの一つでした。

GraphQL上のスキーマを共存させる

ra-data-graphql-simpleでは、対象のリソースに対して以下のようなSchemaを提供する必要があります。 以下は、README.mdにあるPostリソースを例とした場合の期待するスキーマです。

type Query {
  Post(id: ID!): Post
  allPosts(page: Int, perPage: Int, sortField: String, sortOrder: String, filter: PostFilter): [Post]
  _allPostsMeta(page: Int, perPage: Int, sortField: String, sortOrder: String, filter: PostFilter): ListMetadata
}

type Mutation {
  createPost(
    title: String!
    views: Int!
    user_id: ID!
  ): Post
  updatePost(
    id: ID!
    title: String!
    views: Int!
    user_id: ID!
  ): Post
  deletePost(id: ID!): Post
}

type Post {
    id: ID!
    title: String!
    views: Int!
    user_id: ID!
    User: User
    Comments: [Comment]
}

input PostFilter {
    q: String
    id: ID
    title: String
    views: Int
    views_lt: Int
    views_lte: Int
    views_gt: Int
    views_gte: Int
    user_id: ID
}

type ListMetadata {
    count: Int!
}

scalar Date

上記のようなスキーマを既存のスキーマに統合するにあたって、私達のコードベースでは以下のような点に注意する必要がありました。

  • Object Type名の競合
  • Input Type名の競合
  • Operation名(QueryやMutation)の競合
Object TypeやInput Type名の競合への対応

GraphQLサーバーはその仕様上、/graphqlという単一のエンドポイントのみを持ち、リソースはREST APIのようにパスではなく、Typeを定義することで表現します。 そのため、今回のように扱うドメインは同じではあるが、ユースケースとしては異なる複数のクライアントに同じリソースを返す際に名前が競合してしまいます。 GraphQLではスキーマにnamespaceの概念の導入するProposalも作成されていますが、導入されるには至っておりません。 今回は、書籍「PRODUCTION READY GRAPHQL」を参考にプレフィックスで明示的にnamespaceを定義する方法をとることにしました。

以下は、同じUserというリソースを扱う場合の例です。

# エンドユーザー向け
type User {
  ...
}

# 管理画面向け
type AdminUser {
  ...
}
Operation名(QueryやMutation)の競合への対応

リソース名をプレフィックス付きで定義すると

  • all_admin_users
  • _all_admin_users_meta

Operation名もリソースを踏襲することになるため名前の競合を気にする必要はありません。

ただし、graphql-rubyを使用している場合はMutationのResolverを定義するクラス名に注意する必要があります。 graphql-rubyではPayload Typeがクラス名を元に自動で書き出されることから、両クライアント向けに同名のMutationクラスが定義されている場合にPayload Typeが競合する問題がありました。 冗長ではありますが、Mutationのクラス名を、PrefixつきのPayload Typeが書き出されるように定義することにしました。

以下、例

module Mutations
  module Admin
    class CreateAdminFeature < GraphQL::Schema::Mutation
      ...
    end
  end
end

コードを共存させる上で

元々管理画面用にあったコードベースを統合するにあたって、管理画面用の定義であることが把握しやすい作りにしておきたいです。 SMART BANKさんの取り組みでも紹介されていましたが、私達もnamespaceでの分離を行っています。

以下、例

app/graphql/types/admin/admin_user_type.rb
module Types
  module Admin
    class AdminUserType < Types::BaseObject
      ...
    end
  end
end

今のところ、使用するModelは同じにしており、主にapp/graphql配下でnamespaceによる分離を行うようにしています。 SMART BANKさんでは、同じモデルを利用する場合は継承やコンポジションを使用し、管理画面特有の振る舞いに拡張させるなど、同じnamespaceによる分離でもより分離レベルを高める取り組みをされているようで、非常に参考になりました。

今後に向けて

名前空間の分離レベルを上げたい

前述した通り、現状だとモデル内でエンドユーザー向け機能のための振る舞いか、管理画面のユーザー向けの振る舞いかが分かりづらくなっています。 SMART BANKさんの取り組みのように、振る舞いを分離するような工夫をしていきたいです。

GraphQLのスキーマを共存させるためにとった一時的な対応の改善をしたい

こちらも前述した通り、名前空間が競合する可能性があるものにPrefixをつけて対応していますが、コードベースの名前空間との兼ね合いで定義が冗長になってしまっています。 また、Mutationクラスの命名をはじめ規則性が弱く一部把握しづらいコードになってしまっており

  • より柔軟なGraphQL向けのData Providerを持っていそうなRefineに乗り換える
  • 自分たちで名前空間の切り分けを行いやすいData Providerを実装する

など、引き続き改善に向けて検討を行っていきたいところです。

実行プロセスの分離を行いたい

資料中でもコードベースは同じでありつつ、プロセスを分けることによるメリットについて言及されていました。 Plex JobではHerokuからGKE Autopilotへのインフラ環境の移行を計画していることから、サーバープロセスを柔軟に配置することができるようになる見込みなので、合わせてチャレンジしていきたいです。

終わりに

今回、さらっとKaigi On Railsの参加レポートを兼ねて、弊社Plex Jobでの管理画面用のAPI統合の取り組みについてお話させていただきました。

統合は完了したもののまだまだ課題は多く残っています。 このような課題にチャレンジしていただけるソフトウェアエンジニアフロントエンドエンジニアを募集しています。

少しでも興味を持っていただけた方は業務委託や副業からでも、ぜひご応募いただけると幸いです。

参考資料