エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

Reactで管理画面(SPA)を作った時の技術選定とか。

これは エムスリー Advent Calendar 2022 の3日目の記事です。

エンジニアリンググループの山本です。 主にクラウド電子カルテサービス エムスリーデジカルのフロントエンドを開発しています。 この記事は、デザインの刷新と技術的負債(AngularJS)の返却を目的にデジカルの施設管理画面をリニューアルした時の記録になります。

愛犬のぽめちわ


施設管理画面

今回開発したのは施設管理画面と呼ばれるもので、ユーザーである医療機関(クリニック)側の管理画面になります。

現状だと18タブ(ページ)のシンプルなSPAとなっていて、オプションや連携等の各種設定、ユーザーの管理、監査ログの閲覧等が可能な画面です。

日用的に業務で利用される受付やカルテや画面とは違い、必要に応じて開かれる画面になります。

技術選定

チームメンバーでそれぞれプロトタイピングをした内容をもとに、以下のような技術選定をしました。

  • React

    • デジカルのフロントエンドはAngularJSからReactへ長い年月をかけて移行してます。
    • 既存のReactの資産を活用できる点や、カルテ画面もReactでリニューアル予定なので無難に選定しました。
    • ただ、普通Reactだよね、みたいな時代が少しづつ終わりかけてる気配も感じるので、Svelte等の後発の技術にチャレンジしたい気持ちも多少はありました。
  • Next.js

    • Reactで新規開発をする際に選択肢として挙がってくると思います。
    • しかし、開発環境が楽に用意できる、ルーティングが楽になる、くらいのメリットしか思い浮かびませんでした。
    • 逆にフレームワークのせいで依存ライブラリのバージョンアップがしにくくなったり、細かい設定がしにくくなる等のリスクが懸念点として浮かびました。
    • チームに経験者がいなかったので、使ってみたい感もありつつも、あえて使う必要はなさそうという結論で採用しませんでした。
    • 余計なものは使わずシンプルに作りたいというマインドも影響してたのかもしれません。
  • Vite

    • Next.jsを使わないので環境を自前で用意する必要があります。
    • 速い、設定値が簡単、cliで雛形が作れる、dev-server標準搭載等で便利なViteを採用しました。
    • 他に気になるバンドラーもありましたが、Viteで問題なさそうと判断しました。
    • 既存のフロントエンドはwebpackを使っていたので、開発体験が大幅に向上しました。
  • wouter

    • SPAなのでルーティング用のライブラリが必要です。
    • 正直最低限のルーティングができればなんでも良かったので、スター数とかを気にしつつ、軽量なwouterをえらんでみました。
    • ただ軽量であることのメリットはあまりないです。
    • /path にアクセスしたら PathPage コンポーネントを表示する程度の使い方しかしてないので、移行も容易だと思います。
  • Radix UI

    • digikar-uiというデジカルのデザインシステムで使っています。
    • react-bootstrapの移行先として、AngularJSが土台になってる環境でも使える点で選定しました。
    • 各種packageは個別にインストールできる割に、中に依存してるライブラリに互換性がなく、結局他のpackageとバージョンを合わせないといけなかったり、細かい挙動周りに悩まされていますがなんとかやっていけています。
  • emotion/css

    • CSS in JS には emotionのcssパッケージを選びました。
    • フレームワーク非依存な点、classを定義してclassNameに指定するという、ほとんどCSSのような使い勝手を好んで使っています。
    • styled packageに関しては、可読性がイマイチだったりReact依存でスタイルの可搬性が低下する等を上回るメリットを感じなかったため利用しませんでした。
  • SWR

    • データの取得と状態管理に便利なので使いました。
    • Suspenseと合わせて少ないコードで実装できました。
  • ky

    • Http通信をする際に利用しました。
    • 素のfetch api も充分使えますが、より簡潔なAPIで、ありがちな設定を楽にできそうなライブラリの導入を検討しました。
    • axiosは XHR ベースという理由で利用しませんでした。
  • OpenAPI

    • 信頼できる型定義が欲しいという理由でOpenAPIでスキーマを定義しました。
    • 従来はレスポンスの型定義をクライアント側で手で書いており、微妙に正確性に欠いており課題感がありました。
    • 後付けでスキーマを定義したものの、バックエンド側でスキーマの整合性を担保してないので、結局信頼性に欠けるスキーマを作っています。
      • 最初はレビューで担保するという方針にしましたが、だんだんゆるふわになっていきました。
    • 新しくAPIを作ってるわけでもなく、チームとしてフロントとバックエンドの境界もないので、短期的にはスキーマの管理コストに見合うメリットがないかもしれません。
    • しかし、将来的にAPIの品質を向上させたいという意思のもと、投資的な意味で導入してみました。
    • また、クライアント側のコード生成は行いませんでした。自動生成するメリットをそこまで感じなかったこと、個別のカスタマイズが想定され、生成されたコードをそのまま使えないといった点が理由です。
  • Aspida

    • OpenAPIのスキーマを元に型を生成するために利用しています。
    • メンテナンス性に対する不安や、生成されるクライアントのインタフェースが独特で、少しだけ導入を躊躇っていました。
    • クライアント側のコードは生成しない点、型の生成に関してはOpenAPIのGeneratorより扱いやすいという検証結果になったため利用しました。
    • 型の生成だけなので、最悪移行もしやすそうと判断しました。
  • その他

    • react-hook-form
      • formの実装を楽したいという思いで導入しましたが、正直使わなくて良かったかもしれません。
    • dayjs
      • 既存のmoment.jsの資産を流用しやすいように、dayjsを使いました。 ただ、moment -> dayjs に置換するだけとはいきませんでした。
    • nanoid
      • idの生成に利用しました。

数年後にメンテナンスする人が困らないように可能な限り配慮して技術選定したつもりですが、 負債にならない技術はないし、将来のことは分かりません。トレンドの技術や使ってみたい技術を導入したい衝動に駆られることもありました。 どっちが正解だったかはわかりません。

ただ、剥がしやすさ(もしくは剥がしやすく作れるか)は、負債になった時のやり直しコストが小さくなるので大事な視点だと思いました。

逆にいうと、インタフェースがシンプルだったり、利用箇所が限られるものは、 比較的軽い気持ちで技術選定をできるのかもしれません。しらんけど。

その他実装方針

  • ディレクトリ構成

    • ファイルの見つけやすさを重視して以下のようにしました。巷のディレクトリ構成とは異なるかもしれません。
        .
        ├── apis
        ├── digikar-ui
        ├── utils
        ├── institution-admin
        │   └── pages
        │       ├── audit-log
        │       └── user-list
        └── package.json
    
    • yarn workspaceを利用して4つのpackageに分けました。
    • institution-adminの下にpagesディレクトリを、その下に各pageごとのディレクトリをきり、関連ファイルがまとめてあります。
  • 命名規約(キャメルケース or スネークケース)

    • TypeScriptではキャメルケースが主流だと思います。
    • しかし、API側のレスポンスがスネークケースで返ってくるため、命名を統一するためにはレスポンスのキーの変換処理を挟む必要があります。
    • そこまでして命名規約を守るべきか、みたいな意見があり一度APIのレンポンスはそのままスネークケースで扱う方針で実装してみました。
    • しかし、formの値などAPIの型に引きずられる形でスネークケースで定義したくなる箇所が増える結果になり、結局キャメルケースに統一する方針に戻しました。
    • それでもAPIがスネークケースで定義してる都合上、クライアント側のコードを全てキャメルケースにすることには限界がありました。
  • コンポーネントのメモ化

    • React.memoやuseMemo, useCallbackなどのhookは基本的に使っていません。
    • クライアント側のパフォーマンスが求められる画面ではないのでメリットがあまり無いわりに、コードが冗長になり見通しが悪くなるデメリットの方が大きいと判断しました。
    • また、チームの都合上、普段フロントエンドの開発に携わらない人が、誤った使い方をする可能性が高いこともあり、利用を避けました。
  • APIのエラーハンドリング

    • 4xx系のエラーハンドリングを従来のコードではtry/catchのcatchでやっていましたが、業務アプリケーションとして想定されるエラーを、例外として扱うこと等に違和感を感じていました。
    • APIのリクエストメソッド内でエラーハンドリングをし、ラップしたエラーを返すことでtryの中で処理する方針にしました。
  • テスト

    • Vitest+Testing Libraryを使ったテストとPlaywrightを使ったテストを用意していますが、現状あまり工数を割けていないです。
  • デプロイ

    • フロントの資産はS3にホスティングし、CloudFrontで提供しています。インフラの都合上、実際にはrouterサーバーと呼ばれるプロキシサーバーによってCloudFrontにプロキシされます。
    • また従来の施設管理画面がdigikar.jp/institution_adminで提供していたのに対して、リニューアルした画面はdigikar.jp/institution_admin/で提供することになりました。
      • これもインフラの都合上の制約だったのですが、開発環境やQA環境で新旧2つの画面を同時見れたり、段階的なリリースもしやすくて意外と良かったです。

おわりに

具体的なコードのない内容となってしまいましたが、一例としてどなたかの参考になれば嬉しいです。

今回初めて3~4人程度のチームでフロントエンドの開発をしたのですが、個人的には

  • 解決したい課題は何なのか
  • それは解決すべき課題なのか
  • 解決のためにどういうアプローチができるのか
  • それぞれの選択肢にどういうメリット・デメリットがあるのか
  • それは現在のプロジェクトにおいて本当に当てはまるのか

みたいなことを考えるのが好きなので、技術選定とか実装方針の相談は楽しかったです。

また、人によって価値観が違うので、例えばシンプルに作りたいという方向性に関しては同じなのに、 人それぞれシンプルさに対する感覚が異なることもあり、選定技術に違いがありました。その辺の自分と違った視点に出会えるのもチーム開発の魅力だなぁと感じました。

(経験や好みにより、ある人にとっては可読性の良いコードがある人にとっては可読性の悪いコードだったりする、みたいな話です)

We are hiring!

デジカルのフロントエンドは絶賛リニューアル中です。今後はコア機能のカルテ画面のリニューアルに着手予定です。 他にも、デジカルをもっと使いやすくするための機能追加、機能改修を続々と開発予定です。

  • ReactやTypeScriptが好きな人
  • プロダクトを良くしていくことにやりがいを感じる人
  • チャットやHuddle(Slack)で実装方針を話しながら作りたい人
  • 電子カルテの開発に興味がある人

などいらっしゃいましたら、カジュアル面談でお待ちしております。 気軽にご応募ください。

jobs.m3.com