フロントエンドエンジニアの小林和弘です。
去年の 4 月に「Web アプリを 3 つ使ったサービスを 3 ヶ月でつくりたい」という話が降ってきて、無茶を現実にした話をします。
新規サービス「やくばと」で Workspaces のモノレポを使って、医療機関画面、薬局画面、患者画面の 3 つの Nuxt を構築しました。
やくばととは
医療機関、薬局、患者さまの間でやり取りされる医療機関起点の薬局向け処方せん画像事前送信サービスです。処方せんに関する業務負荷を軽減すると共に、患者さまの大きな負担である医療機関、薬局での待ち時間を軽減するということを主な目的としたサービスです。
医療機関、薬局の DX を進め、患者さまの負荷軽減に貢献できる素晴らしいサービスになっています。
開発チームでは医療機関、薬局、患者さまに提供している Web 画面をそれぞれ医療機関システム、薬局システム、患者システムと呼んでいます。
モノレポとは
モノレポとは、ひとつのリポジトリ内で複数プロジェクトを管理するソフトウェアの開発手法を指します。
フロントエンドのモノレポツールでは Lerna が有名です。やくばとでは複雑なビルドフローを組む必要がなかったことと、開発当初は Lerna v4 のメンテナンスが止まっていたことから、技術選定の選択肢からは外していました(やくばと開発中に Nx 社が Lerna のメンテナンスを開始したため、現段階ではモノレポツールとして Lerna は選択肢に入ると思います)。
やくばとでは、yarn の Workspaces を利用してモノレポを実現しています。
「Nuxt サービス 3.5 個」の 0.5
記事のタイトルに書かれている 3.5 個の Nuxt とは何かを説明します。
やくばとでは医療機関システム、薬局システム、患者システムをすべてひとつのリポジトリで管理しているわけではなく、患者システムと薬局システム、患者システムと既存サービスの 2 つのモノレポリポジトリで開発しています。
患者システムは、すでに存在していた既存サービスのリポジトリをモノレポ化してそこに組み込むという形をとっています。既存サービスのフォームコンポーネントなどのコードを、患者システムに流用するために大幅に書き換えています。0.5 はこの既存コードの変更部分を指しています。
ひとつの巨大なフォームコンポーネントを、入力項目ごとにコンポーネント化し、小分けにしたコンポーネントを共通利用するようにしています。作業としては単純ですが、ディレクトリ構造の変更を行っているため、リポジトリ内のほぼ全てのファイルが修正対象になっています。
患者システムは既存サービスのコードを使い回しているので QA テストの工数も大幅削減することができました。
モノレポの設定
モノレポの設定方法は非常に簡単です。
医療機関システムと薬局システムを管理しているモノレポを例に見ていきます。
package.json の設定
モノレポでは package.json をプロジェクトの複数箇所に設置する必要があります。
. ├── package.json └── packages ├── medical │ └── package.json ├── pharmacy │ └── package.json └── ui └── package.json
まず、プロジェクトルートの package.json の workspaces プロパティにモノレポ対象になるディレクトリパスを指定します。workspaces プロパティには文字列の配列を定義しますが、ワイルドカード *
も利用可能なのでやくばとでは下記のように指定しています。
{ "workspaces": [ "packages/*" ] }
次に医療機関システム、薬局システム、共通 UI コンポーネントのための package.json を作成していきます。
{ "name": "@yakubato/medical" "dependencies": { "@yakubato/ui": "*" } }
{ "name": "@yakubato/pharmacy" "dependencies": { "@yakubato/ui": "*" } }
{ "name": "@yakubato/ui" }
各 pacakge.json の name プロパティに指定した文字列が package を呼び出すときのネームスペースに使われます。
たとえば、UI の Button コンポーネントを呼び出すとすると、
import Button from '@yakubato/ui/components/atoms/Button.vue'
といった形になります。
yarn workspace コマンド
yarn workspaces を使うと、node_modules のインストールコマンドが変化します。
プロジェクトルートの package.json に Nuxt を追加する場合は、 yarn add nuxt -W
といった形で -W
オプションをつける必要があります(オプションをつけなかったらエラーログが流れるのですぐに気づけます)。
また、workspaces で指定したディレクトリ配下の package.json に node_modules を追加する場合は、別のコマンドが必要になります。
packages/ui に @storybook/vue を追加する場合は、プロジェクトルートのディレクトリで yarn workspace @yakubato/ui @storybook/vue -D
を実行する必要があります。workspace コマンドの第 1 引数に追加したい package.json の name プロパティを指定して、第 2 引数に追加する node_modules を指定します。
モノレポの利点
モノレポの利点はいくつかありますが、やくばとプロジェクトでは下記のような恩恵を受けられました。
- 設定ファイルの共通化
- node_modules のアップデートが楽
- UI コンポーネントの共通利用
設定ファイルの共通化
個別のリポジトリで複数サービスを管理した場合、開発ツールの設定ファイルをそれぞれのリポジトリで管理する必要があります。
モノレポ管理をすると、ひとつのリポジトリで .eslintrc.js, .prettierrc, jest.config.js, stylelint.config.js 等々が一元管理できます。lint のルール変更も 1 ファイル変更するだけで各システムに変更が適応されます。
node_modules のアップデートが楽
node_modules のアップデートには dependabot や renovate といった、PR を自動作成してくれるボットがよく使われると思います。
モノレポ管理をすると、当然ですがリポジトリがひとつなのでこれらの PR 対応が一度で済むことになります。
また、今後発生するであろう Nuxt3 対応も一度で対応できるのも大きいです。
UI コンポーネントの共通利用
やくばとでモノレポを採用した一番の目的は UI コンポーネントの共通利用でした。
デザイナーさんに医療機関システムと薬局システムの UI デザインを共通化していただいた上で、汎用的な Button や Modal コンポーネントを一元管理することで開発工数を大幅に削減しました。
UI の開発工数を抑えるということであれば、UI ライブラリ(Vuetify, Chakra UI, Tailwind UI, etc)を使えばよいのでは? という話なんですが、長期運用で UI ライブラリが負債化するリスクと作成するコンポーネントの数や UI・UX の品質担保を天秤に掛けて、独自 UI コンポーネントの開発という選択をとっています。
UI コンポーネントのみをパッケージ管理できているため、他のプロジェクトでも同じ UI を使いたいという話が出てきたときに、 npm や GitHub Packages でパッケージとして UI を提供できる状態にあるというのもモノレポ管理の良い点です。
各システムの簡略図
医療機関システムと薬局システムは 2 つの Nuxt と、共通の UI コンポーネント管理を行う Storybook のプロジェクトがひとつの GitHub リポジトリ内で管理されています。
CircleCI 上で 2 つの Nuxt を generate し、生成物を AWS の 各 s3 にデプロイしています。CI 設定が共通化できるのもモノレポの利点です。
患者システムもほぼ同じ構成で、共通 UI コンポーネント管理に Storybook を使っていないくらいの違いしかありません。
患者システムは、Storybook が導入されていない既存サービスをモノレポ化しています。すでに UI が構築されていてテスト済という状態だったので、Storybook の導入を行わないという選択をしています(そもそもスケジュールがタイトでインストールする余裕がなかった)。
まとめ
複数ドメインで Web 画面を管理する必要があるようなサービスの場合、UI コンポーネントを共通化してモノレポ開発すると、初期構築や運用コストが抑えられるといった話でした。
複数の Web 画面を利用しないサービスでも、汎用 UI コンポーネントを切り出しておくとパッケージとして切り離しやすくなるという利点があるので、軽い気持ちでモノレポ構成にしてみるのも面白いかもしれません。
是非読者になってください!
メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!
■募集ポジションはこちら
■エンジニア紹介ページはこちら