こんにちは!株式会社ゼストでエンジニアをしている山下です。
今年の3月、弊社の在宅医療・介護向け訪問スケジュール自動作成クラウド『ZEST』をフルリニューアルリリースしました。
このリニューアル版ZESTを含め、弊社のアプリケーション開発ではtPRCやZodなどのT3 Stackに挙げられる技術を使ったFull-Stack Typescriptを採用しています。 この技術スタックのおかげでリニューアルやその後の機能開発がとてもスムーズに進んでいるのを実感しているので、簡単にお伝えできればと思います。
なお採用背景については弊社の海老原が別記事で公開しておりますので、ぜひご一読ください。
技術スタックのおさらい
まず、弊社で使用している技術スタックについて説明します。
フロントエンド
フロントエンドはNext.jsのApp Routerを使用しています。 UIライブラリはMantineを採用しており、フォーム周りもmantine/formとZodを組み合わせています。
バックエンド
バックエンドのフレームワークはFastifyを採用しています。といってもルーティングなどは後述するtRPCが担っているため、Fastifyであることを意識することはほとんどありません。
ORMにはPrismaを使用しています。DBスキーマを定義するだけで型の定義が生成されたり関数の自動補完が効くので、開発体験がとても良いです。
共通で使っているもの
フロントエンドとバックエンドの通信は、tRPCとZodを使って型安全に行なっています。 またSuperjsonを使うことで、本来jsonにシリアライズできないDay.jsなどのinstance objectも送受信できるようになっています。
Full-Stack TypeScriptとは
Full-Stack TypeScriptとは、ここでは「フロントエンドとバックエンドをTypeScript で統一している」技術スタックを指しています。
フルTypeScriptなど呼び方に揺らぎがあったのですが、海外ではFull-Stack TypeScriptがポピュラーなようで、最近は国内でも統一しようという発信がされていました。 *1
TypeScriptのエコシステムが進化していることやライブラリの充実度などから、バックエンドでも十分にProduction Readyな開発言語となったことで採用事例が増えています。
Full-Stack TypeScriptの旨み
Full-Stack TypeScriptを採用するメリットはいくつかありますが、その一つに フロントエンド・バックエンドを横断して開発できるというものがあります。
領域を横断して開発することのメリットを3つほど挙げていきます。
領域を横断するハードルを下げる
例えばフロントReact、バックエンドGoのような領域ごとに異なる言語技術スタックの場合、Goの経験がないフロントエンジニアが領域を跨ぐには、言語の習得から始める必要があります。その上、言語の特性や個々のプロダクトコードの雰囲気などのキャッチアップが必要で、横断のハードルは高いです。
一方でFull-Stack TypeScriptの場合は慣れ親しんだ言語が使われているので、今までFEメイン、BEメインだったエンジニアでも、すぐにコードを読んで理解しやすい環境になっており、領域を横断するハードルを下げる効果があります。
型安全でデグレやバグに気づきやすい
また前述のtRPCやZodも、領域を横断するハードルを下げています。
tRPCはフロントとバックエンドで型情報が共有されるため、APIのスキーマを変更した場合、その変更はフロント側も影響を受けます。スキーマのプロパティを減らしたり型を変更した場合には、フロントの参照先で型エラーが起こるため、誤った変更や修正漏れなどを即座に検出できます。例えばバックエンドのAPIスキーマを変更した時に、フロントの修正がされないまま参照できなくなって画面が真っ白になる、なんてことを型チェックの時点で防止できます。
加えてtRPCはsuperjsonと組み合わせることで、Day.jsなどのclassオブジェクトもシリアライズして相互のやり取りすることが可能なことも嬉しいポイントです。RESTをはじめ、通常はレスポンスのjsonを型変換するレイヤーが必要になりますが、そのレイヤーはtRPCが担ってくれているため、よりアプリケーション開発に注力しやすくなっています。
このような領域を横断して型安全な状態を作れるtRPCを採用できるのもFull-Stack TypeScriptのメリットであり、これも領域を横断するハードルを下げています。
少数精鋭で継続的なデリバリーが行える
3つ目に、少人数な開発チームでも継続的なデリバリーを行いやすいというメリットもあります。
例えばフロントエンドとバックエンドが異なる技術スタックのチームの場合、フロントで表示するデータが足りない時はバックエンドに修正依頼を行うことが一般的かと思います。しかし他のタスクで忙しくすぐに着手できない場合があったり、他の機能開発に遅れが生じて、結果的に顧客へのデリバリーが遅れてしまうケースが往々にして起こり得ます。
Full-Stack TypeScript環境で構成されたプロダクトの場合、前述の通り異なる領域に踏み込むハードルが低いので、フロントエンジニアがバックエンドを修正しやすい環境を作れます。その結果、不足しているリソースを補い合うことで、少人数の開発チームでも継続的な機能開発のデリバリーをすることができます。
ゼスト内での領域を横断した動き
ゼストの開発チームでは、実際に領域を横断した機能開発が行われています。
ZESTは在宅医療・介護業界向けのいわゆる業務アプリケーションなので、画面ごとに様々なデータを表示する必要があります。またリニューアル前後の過渡期ということもあり、APIのスキーマにデータが不足していることや、新たな機能の画面や表示項目が日々更新されています。 このようなプロダクトの状況で、弊社では以下のような領域を横断した動きが行われています。
- 画面に新しい項目が増えたがAPIが不足しているとき、FEエンジニアがAPIの修正を行いデータを取得する
- バックエンドのリファクタリングでAPIの構造が変わった時、BEエンジニアがフロント部分も一緒に修正する
- Sentryでフロント側でアラートが上がって、内容的にBE起因の可能性がある際に FEエンジニアがBEのコードを確認して調査する
- BEメインの機能だが画面にも新たな設定項目を追加する必要がある場合、 BEエンジニアが設定画面も同時に作成する
- 大きめの開発アイテムに着手する際に、FE/BEエンジニアがお互いのコードをある程度把握できるので、解像度が高い状態で設計相談などの議論ができる
私自身、新卒時代からずっとフロントエンドを専門にしてきてバックエンドはほとんど未経験だったのですが、TypeScriptという慣れ親しんだ言語であることやフロントとバックエンドで共通の型定義を使うことで、バックエンドの世界に足を踏み入れやすい開発環境だと感じております。実際にここ数ヶ月間で、何度もBE側のコードに手を入れたり、BEメインの開発アイテムも担当させていただきました。フロントとバックエンドでは、DB読み書きやトランザクション、パフォーマンスなど注意する観点が異なるのでまだまだ勉強中という状態ですが、少なくともバックエンドに触れる機会が確実に増えたのはFull-Stack TypeScript環境の恩恵だと思います。
コードレビューについてはBEエンジニアの方にお願いすることで、コード品質の維持はもちろん、テストコードも細かく書くことでデグレの防止も行なっています。反対にBEエンジニアの方がフロントを変更した際には、自分を含めFEエンジニアでレビューを行っています。 各々の専門領域の知識を活かしつつ、リソースを補い合うことで少人数のエンジニアでもスムーズに新機能や機能改善のリリースを行えています。
横断開発の課題感
良いことばかりを挙げてしまいましたが、もちろん課題も存在します。
まず前提として、全ての開発メンバーが必ず横断開発をできるわけではありません。
現状のゼスト開発チームでも全員が横断開発を行えているわけではなく、前述のようなフロントエンジニアがAPIの追加をするケースと比べると、バックエンドエンジニアがフロントを修正するケースは少なめです。 これはスタイルや状態管理、Reactのhooksなど、フロントエンド特有の難しさがハードルとなっている場面もあります。
逆にフロントエンジニアも、バッチ処理やDB設計など少し複雑な場面になってくるとまだ手が出ない状態です。領域を跨いだ上で、フロントエンド・バックエンドそれぞれ固有の設計技術や知識は別途学ぶ必要があります。
フルスタックな動き方も、チーム全体のリソースやアサインされるアイテムの都合、個々人のWillに寄る部分もあるので、必ずしもFull-Stack TypeScriptを導入したからといって全員がすぐに横断開発をできるわけではないことにも注意が必要です。
またtRPCについては、現状TypeScript間で真価を発揮するRPCなのでそれ以外の言語では通常のRPCと変わりません。 ネイティブアプリなど他の言語が混在するプロジェクトの場合は、採用が難しいケースもあります。
Full-Stack TypeScriptは領域を横断する際の後押しとなる技術スタックですが、全てを解決できる銀の弾丸というわけではないので、開発チームの技術スタックやプロダクトの方向性などを踏まえた上で採用する必要があります。
終わりに
今回はFull-Stack TypeScript環境による、FE/BEの領域横断のメリットについてご紹介しました。 今後もゼストの技術的な取り組みの発信を行っていきたいと思います。