はじめに
テックラボの藤井(@yutafujii)です。
社内向けのプロダクト「タレントエージェンシー支援システム(SFA/CRM)」のサーバーサイドエンジニアとして日々活動しています。
今回はRailsで作られたプロダクトにVueを導入したプロセスを紹介すると共に、実際に直面した論点や工夫点をお伝えできればと思います。
いま現在の実装環境
いま現在フロントエンド周りはNuxt/TypeScriptで主に動いておりますが、詳細な技術スタックがどうなっているのかは、別途まとめたこちらの記事を参照していただくこととして、本投稿ではRailsだけで書かれていたサービスでフロントエンドフレームワークを導入する過程について焦点を当てて書かせていただきます。
SPA化の背景
私が担当しているタレントエージェンシー支援システム(SFA/CRM)は、主に社内のヒューマンキャピタリストが利用するもので、顧客管理や業務プロセス管理のような役割を果たしています。
顧客管理やプロセス管理というアプリケーションの特性上、ユーザーは文字通り細かい作業をシステムで行うことが多く、いちいちページロードすると不便であることからリッチなフロントの動きが求められておりました(RailsガイドにあるようなRails-ujsを利用してapp/views/配下にたくさんの.js.erbを作成するには複雑すぎました)。
また、タレントエージェンシー支援システムを社内外問わず他のプロダクトと連携させるためにはAPIのエンドポイントが必要ですが、早めにこれを準備しておきたいという狙いもありました。
まとめると、今回SPA化を行ったのは主に2点の理由ということになります:
- リッチなフロントの動きが求められたこと
- APIのエンドポイントを用意することで他のプロダクトとの連携に備えること
Vueを選定
SPA化するにあたりフロントエンドにはVueを選定しました。主な背景は以下の通りです:
- 当社では他にもVueを用いたシステムが既に存在しており、今回も統一しておくことでVueを書く人を柔軟に配置できる
- サーバーサイドエンジニアが多く、学習コストを抑えたい
なお、導入時の各ステップは後述しますが、最初はNuxtは利用せずVueのみで実装しました。タレントエージェンシー支援システムはSEOの考慮が不要でSSRする必要がなかったほか、最初のステップではルーティングも必要としなかったためです。後半過程でフロント/サーバーを分離する際に初めてNuxtを導入することになります。
導入方針を考える時のチームの状況
ざっというと、SPA化しようと話し合っていたころのチームとプロダクトの状況はこんな感じでした:
- 基本的には全てRailsで書かれている、画面も基本的にerbで実装
- フロントエンドエンジニアが1名(着任したばかり)
- サーバーサイドエンジニアが2名
- デザイナーが初めてチームにアサインされてからまだ3ヶ月目
- SPA化を前提としたUIのリデザイン案は作成済
- デザイナー不在でも実装できるようCSSフレームワークが多分に活用されている
- 事業利益へのインパクトを考えると、SPA化そのものに多くの時間は割けられない
要約するならば、デザイナー・エンジニアのなかでプロダクト・ソースコード・事業への理解度が高いメンバーはごく限られており、小回りしやすいように進めていく必要がありました。
実際に導入する
大きく3ステップに分ける
導入当時のチームの状況から、まずはミニマルなステップとして部分的にコンポーネント化を行い、その間にデザインシステムを完成させてリファクタリングと共にアトミックデザインを実装し、そのあと別途Nuxtで立ち上げるクライアント・BFFの乗ったサーバーに画面を段階的に移行するという3ステップで行うことにしました。
各ステップの概要は以下の通りです。
Step.1
既存のRails/viewファイル中にコンポーネントを導入し、既存ページの一部をVueでリプレイス(合わせてリデザイン)する
実際の最初のコンポーネントを作成した時のerbファイルの様子(変数名などは仮のものに置き換えてあります)
# app/views/clients/show.html.erb
<section class="content">
<%= render 'client_detail', client: @client, display_button: true %>
<!--
ここにcontroller中で定義したインスタンス変数をDOMにattributeとして仕込み、JSがそれを起点にVueコンポーネントを生成する
-->
<div id="jobs-list" data-meeting-id="<%= @client.meeting_id %>"></div>
<%= javascript_packs_with_chunks_tag 'spa/pages/clients' %>
<%= stylesheet_packs_with_chunks_tag 'spa/pages/clients' %>
</section>
Step.2
デザインシステムを完成させ、アトミックデザインにディレクトリをリファクタリング
さらに別の一部のページをコンポーネント化
vueコンポーネント周りのファイルは以下のような構成で管理されるように
Step2を終えた段階のRails側のディレクトリ構成(一部)
app/src/javascripts/spa
├── assets
├── components
│ ├── atoms
│ │ └── 10 files
│ ├── molecules
│ │ └── 10 files
│ ├── organisms
│ │ └── 23 files
│ ├── pages
│ │ └── 1 files
│ └── templates
│ └── 4 files
├── mixins
│ └── 4 files
├── pages
│ └── 4 files
└── store
└── 15 files
Step.3
Nuxt/Vueで新たにクライアント・BFFサーバーを作り、既存機能を段階的にサーバー・フロント分離で実装
新規リポジトリでNuxt/TypeScript、ExpressでBFFを構成
Nuxtサーバー側のレポジトリ構成図
.
├── README.md
├── babel.config.js
├── deploy
│ ├── prod
│ └── stg
├── docker
│ ├── nginx
│ └── nuxt
├── docker-compose.yml
├── jest.config.js
├── jsconfig.json
├── node_modules
├── nuxt.config.ts
├── package.json
├── shims-vue.d.ts
├── src
│ ├── assets
│ ├── components
│ ├── composition
│ ├── layouts
│ ├── middleware
│ ├── pages
│ ├── plugins
│ ├── server
│ ├── static
│ ├── store
│ └── test
├── static
│ └── sw.js
├── stylelint.config.js
├── tsconfig.json
└── yarn.lock
悩んだ点・検討点など
実際に取り組む過程で遭遇した技術的な検討点などをいくつか紹介します。
認証方法の検討
一般的なRailsアプリケーションに倣って、タレントエージェンシー支援システムでもDeviseでセッションベースの認証を行っていました。APIでは認証をTokenベースで行うか検討したのですが、DeviseとDeviseTokenAuthを併用すると、omniauthリダイレクトのルーティングや一部Configがオーバーライドされるなど(https://github.com/lynndylanhurley/devise_token_auth/issues/1299)イシューが複数見つかり、まずはセッションベースで認証を行う方針にしました。
既存JSやWebpackerとの相性
タレントエージェンシー支援システムではこれまでデザイナーがいなくてもある程度の見た目が担保できるよう、CSSフレームワークを多く活用していたほか、jQueryがまだ随所に残っている状態でした。また、コンパイルにWebpackではなくWebpackerを利用していました。
これらの背景から、追加で実装したVueまわりのソースコードがコンパイル時にエラーを生じることがあり、レポジトリ内の他のソースコードを考慮した実装が必要でした。
最初のコンポーネント化を終えてアトミックデザインにディレクトリをリファクタリングしようとしたときの検討メモ
TypeScriptの導入
タレントエージェンシー支援システムは金融・会計のような型の厳密さが要求されるシステムでこそありませんが、サーバーサイドをRailsで記述していることもあり、フロントエンドでは型安全なシステムとして構築することは全体の安定性を向上させると考えました。Vue3.0ではTypeScriptサポートが拡充される風潮も踏まえ、このタイミングで合わせて導入することとしました。
TypeScriptの学習コストは必要ではあったものの、サーバーサイドエンジニア全員でモブプログラミングを行うなど時間を確保して実装スキルの底上げを図りました。
Composition APIの利用
TypeScriptを利用するにあたり、これまでもvue-class-componentsを用いてClassをTypeScriptクラスとして用いる方法が存在しましたが、現在も実装方針が未確定のdecorator(https://github.com/tc39/proposal-decorators#standardization-plan)に依拠しているなどの問題も抱えています。それと比較してComposition API(https://composition-api.vuejs.org)はTypeScriptとの親和性が高いため、このタイミングで導入してみることにしました。
振り返り
着想してから9ヶ月が経ち、途中チーム人数も3名から5名に増え、新規機能開発も並走しながらの取り組みでしたが、9月には無事にNuxtのサーバーが本番環境で稼働を開始し、現在も大きな問題なく運用できています。
フロントエンド・サーバーサイドのエンジニア目線で言えば、デザイナーさんがこまめにXDを共有してくれていたことや、ワイヤーフレームを元にSwaggerでエンドポイントの定義を最初に時間をしっかりとって行ったことは良かった点なのかなと考えています。
プロジェクトの振り返りをチームで実施してKPTを洗い出し、次回同じプロジェクトに取り掛かるのであれば最初にDONEの定義を決めておくことなどがTryとして掲げられました。
おわりに
今回はRailsにフロントエンドフレームワークとしてVueを導入する過程について紹介させていただきました。
現在はまだerbで記述された部分もありますが、今後も段階的に画面をVueでの記述に変更していき、最終的にはRailsはAPIサーバーとして①データソース(RDB・Elasticsearchなど)からデータを取得する②ビジネスロジックを記述してAPIの戻り値を整形する、という2つの役割に専念させていきたいと考えています。
段階的に移行するという作業はゼロから作り直すより往々にして時間がかかりますが、移行作業で必要な検討事項というものは、ユーザーに負の価値を与えないこと・事業をどう継続させるか考えることに他なりません。こうした実装を通して、プロダクトチームとしてもより事業目線も技術力も高めていきたいと思います。
今後も各プロダクトのフロントエンドを一緒にやってくれる人募集しています!!