🐈

プロトタイプのプロダクトから大規模導入に適合させていった道のりと、多くの気づき

2023/07/10に公開

はじめに

こんにちは。
Contrea株式会社で業務委託エンジニアとして携わっている mogi と申します。
同時に北海道大学に在籍しており、情報生物学を専攻しています。

今回は、弊社プロダクトが成長していく中で、プロダクトのアーキテクチャが初期とは大幅に変化していった、その過程についての記事になります。初期の技術選定の背景から、直近で行われた意思決定と実際に取り組んだことについて、時系列を踏まえながらご紹介します。

プロダクトについて

まず、メインで開発しているプロダクトについて軽くご紹介します。

弊社は「医療にかかわる全ての人に安心を。」というミッションのもと、病院・クリニックに導入していただく SaaS「MediOS」を開発しています。医師・看護師と患者さんをつなぐプラットフォームであり、プロダクトの特長として、医療従事者用の画面(以後、スタッフ画面)、患者さん用の画面(以後、患者画面)、そして管理者用の画面(以後、管理画面)、といったの 3 つのクライアントが存在します。

技術としては Nuxt.js を社内全体で取り入れており、データベースには Firebase の Cloud Firestore を採用しています。それぞれ静的 Build を行い、 Firebase Hosting で公開しています。

リリースから昨年まで(~2022 年 12 月)の全体設計

プロダクトがリリースされたのは 3 年ほど前になります。初期の構成は、それぞれのクライアントが直接 Cloud Firestore を参照するかなりシンプルな形でした。

当時の技術選定としてはベストだったと思います。以下にメリデメを簡単に紹介します。

メリット

データベースのスキーマを自由に変更することができる

弊社はスタートアップであり、かつ医療業界というレガシーな領域に挑戦しています。まだ試行錯誤が必要なフェーズであるため、改善サイクルを速いスピードで回す必要があります。

リレーショナルデータベースだと毎回マイグレーションファイルを生成したり、外部キー制約など情報の整合性を担保する必要がある一方、Firestore を採用することで、スキーマの key を簡単に変更可能な柔軟なデータ保持を行うことができるため、仕様変更に耐えやすい状態を作ることができます。

チームのエンジニア全員が、同じ技術を触れることができる

スタートアップとして進んでいく中で、フロントエンドエンジニアとバックエンドエンジニア、そして PM が分断されている状態は、個々の力を最大限発揮できる一方で、それぞれの領域の相互理解やコミュニケーションコストを踏まえると、結果的に開発スピードが遅くなってしまうのではないか、という危惧がありました。

上記のような設計であると、Nuxt と Firestore を見るだけなので、全員がプロダクトについての理解を正しく早く行うことができ、誰もがどこのコードも触れられる状態を形成することができました。結果的に、機能要件に対して「〇〇を知っているエンジニアが今忙しいから開発できない」のようなことがほぼない状態でした。

上記設計のデメリット

上記のようなメリットは、プロダクトの PMF までの道のりを進んでいく中でとても重要であると改めて感じさせられます。スタートアップ初期はエンジニアが足りないことは当然であることに加え、医療機関でのシステムにおいては導入までのハードルが高いため、PMF 達成までの道のりが長いことが一般的です。

とはいえ、これはこれで技術的にとても多くの課題が蓄積してしまいます。

データモデルの定義が 3 つのクライアントに分散してしまった

Firestore との接続を 3 つのクライアントと行っていましたが、これだと、3 つ全ての環境で接続処理や、スキーマのインターフェイス(TypeScript で定義した、Firestore の document のデータ型)が定義されていました。

それぞれ別のリポジトリになっている状態だったので、共通化しようと思ってもしづらく(npm private registry にする方法もあったが、結局行動できていなかった)、結果的に別クライアントのスキーマの定義がされていなかったり、どれが正しい定義か分からなくなったりして、保守性に顕著に欠けている状態でした。

ドメインロジックが分散してしまっていた

上記のデータモデルの定義だけでなく、ドメインロジックも分散してしまいます。これは、アプリケーション全体で正しい挙動を担保しづらい可能性があります。firebase functions を用いて解決する方法はいくつか存在し、API サーバーを立てたり、部分的なドメインロジックを実現する関数を作成することができます。ただし、前者はあくまで軽量なものに対応しインフラ面などの拡張性に乏しいこと、後者はドメインロジックごとに関数を作成することになるので保守性に欠ける、といったデメリットがあることを踏まえると、本質的な解決が難しい状態でした。

2023 年に行った改革とその指針

これまで説明してきたメリットデメリットについて向き合わなければいけない中、プロダクトは PMF に向けて着実に前進し、導入数も増加しました。2023 年時点で弊社プロダクトの核になっているのは動画コンテンツで、その動画数も大幅に増加し、適切なバージョン管理や複雑な権限管理などの需要も発生してきました。(プロダクトについてより知りたい方はこちらをご覧ください)更なる追加プロダクトの必要性も増加してきています。

そこで、プロダクトチーム として、全体設計の改善に着手する方針を固めました。全体的な方針は以下になります。

これらを行っていく意思決定には、以下のメリットが大きかったように感じます。

適切に上記設計のデメリットを解消することができる

ロジックが 3 画面に混在している状態から、API にそれが委任される状態にすることで、保守コストが減少し、3 つの画面がより安定して稼働を続けられると感じています。

大規模導入に耐えうる選択肢の増加

複雑な認証・権限管理や、リクエスト数の増加に耐えうる設計、など、個別に対応していかなければいけないことが今後増えていく未来が見えている中で、柔軟な選択肢を選択できる状態にしていきたいと考えていました。

採用やナレッジ収集のしやすさを優先し、サーバーレスよりも API を用意したメジャーな構成に移行する意思決定をしました。

技術選定

アップデートを行っていく中で行った技術選定は以下になります。

  • クライアント側 ... Nuxt.js / TypeScript
  • サーバー側 ... TypeScript / Node.js / Express
  • インフラ ... Cloud Run / Cloud SQL
  • 認証 ... Firebase Auth
  • その他 ... OpenAPI

極力構成を変えないかつリソースを割かないで上記を実現する方向性で選定を行いました。サーバー側の TypeScript は事例が多くないとはいえ、言語自体チームの全員が理解できるということ、設計次第では十分スケーラブルなプロダクトの作成が可能である部分が選定の判断材料になりました。

また、共通のデザイントークンを利用していけるようにしていきたい、より型安全に開発することを意識していきたい、ドキュメントも同時に適切にきれいに管理したい、などの違った観点での課題が同時にたくさんありました。これらを解決する土壌を作成してしまいたかったので、以前別リポジトリで管理されていた画面と、新設する API に対し、モノレポ化を実現する、という方針を取りました。この意思決定により、以下のことで恩恵を受けることができました。

  • OpenAPI を利用し、API のインターフェイスを yml から生成し、その型定義をクライアント側とサーバー側で共通化した。これにより、より安全な開発体験を実現でき、ドキュメントも同時に作成することができるようになった。
  • 今までバラバラだった eslint や prettier などの linter の設定、tsconfig.json などを共通化することができ、より開発者が頭を悩ませないで開発することができるようになった。
  • CI/CD の設定を共通化できた。

他にも、Nuxt2 から Nuxt3 へ、デザインシステムの構築、新規プロダクトの開発など、たくさんのやることが出てきていますが、これらは別記事で紹介します。

全体を通して感じたこと

ここからは、自分が他のエンジニアと議論する中で得られた視野などについて書きたいと思います。

まず、状況に合わせた技術選定を行うことができるようになったのではないかと感じています。自分自身が技術書などでアーキテクチャや単体テストの書き方など、より良い設計・コードについて勉強していく中で、弊社の技術負債については常に感じていました。初めは、より良いコードを書くことがエンジニアの務めだと感じていましたが、これらの経験を踏まえ、「より良い状態、は事業フェーズや組織などによって異なるということ」「適切な時に適切な技術選定・改善ができることがエンジニアとしての価値を高めること」に気づくことができました。

結果として、我々は最初にお話しした構成で 3 年弱運用していくことができました。保守が大変な状況ではありましたが、バグは思ったより少なく、スピード感を持ってユーザーに価値を届ける機能を多くリリースさせることができました。よかったことはいろいろあるかと思いますが、「多分動くからリリースしようぜ」といった気持ちがチーム内で統一され、同じ方向を持って実装を進めていけたことが要因の一つではないかと感じています。

そして、それで実際に導入数が増えてきたこと、より描くミッションが見えてきたことを考えると、やはり技術以上に「プロダクトをどう成長させていくか」という観点が大事であること。「技術は手段に過ぎない」ということは改めて感じました。

ただ、自分はエンジニアです。プロダクトを伸ばすだけなら、エンジニアである必要がないと考えています。技術が好きですし、技術的な観点で全社に貢献していきたいと考えています。長期的なものを見据え、大改革な議論を積極的に行い、スタートダッシュを決めることができたのは本当に良かったなと感じています。これからも、技術的に圧倒的な成長ができればと感じています。

最後に

ここまで読んでいただきありがとうございました。

弊社では積極的にエンジニア採用を行なっております。特に、技術的な観点から全社に貢献し、技術リードしていただける方を強く求めております。

興味を持った方がいらっしゃれば、お気軽にご連絡いただければと思います!

https://contrea.notion.site/Contrea-09fa86165dd245f696a37032fe39e869

Contrea Tech Blog

Discussion