🔨

MOSHの新しいフロントエンド技術基盤

2024/12/11に公開

はじめに

この記事はMOSH Advent Calendar 2024 11日目の記事です。
プロダクティビティーチームの@soartec-labです。

この記事ではMOSHの急速な事業成長を遂げる中で直面した技術的課題とフロントエンドでの取り組みについて紹介します。

内容はこちらのスライドにもまとめているので合わせてご一読ください。

背景

MOSHは直近2年間、事業成長2年連続でYoY3倍を達成しております。
急成長によるプロダクト要求の変化によって技術的なボトルネックが顕在化するようになりました。
プロダクト価値探索のための機能追加と、提供しているプロダクトの信頼性向上を求められる中で品質とスピードを両立させることが重要な課題となっています。

既存基盤の課題

プロダクト開発に影響を与えている顕在化しているボトルネックについていくつか紹介します。

1. 設計の複雑さと一貫性の欠如

  • 非効率なAPIデータ取得や制約
    • ページレンダリングの前にAPIでのデータ取得を行う制約があり複数のAPIを呼び出すページではパフォーマンスに課題がある
  • 過剰なレイヤー分割
    • 過剰に実装を細分化してpackage分離していることで1つの機能開発を行う際にも多くのファイルを参照したりボイラープレートコードの増加に繋がる
  • 一貫性のないディレクトリ構成
    • コードの分断により開発が難しくなったことにより開発者によるバラツキが発生し機能によりディレクトリ構成がバラバラ

2. 品質向上の障壁

  • 内製UIコンポーネントの未成熟
    • 機能性だけではなくアクセシビリティや柔軟性などを考慮した品質の向上やメンテナンスがされていない
    • また、共通コンポーネントの配置が分散しているため再利用性や品質の向上が難しい
  • テストコードの不足
    • コンポーネントや機能に対するテストコードがほとんどないため品質担保が不十分

3. 柔軟性とメンテナンス性の低下

  • 早すぎる最適化
  • ページ単位での関心であるにも関わらず、早期に共通化されたUI構築用関数が存在し変更が難しくなっている

これらの課題は全体の中の一部であり他にもフロントエンドの技術基盤には大きな課題がありました。

新基盤での対応

これらの課題を解決するためにフロントエンドではゼロベースで技術選定を行い新しい技術基盤への移行を決断しました。結論付けるまでには「既存の基盤を維持しながら改善する方法がないか」「どこまでは活用できるか」など検討を重ねた上で実現可能性やリスクを踏まえた上で判断をしています。

その中でも特に良かった大きな変化をいくつか紹介します。

1. スキーマ駆動開発の導入

既存機能の資産を活かしOpenAPIを活用したスキーマ駆動開発を採用しました。
それによりAPI仕様の一貫性を確保し以下の利点を得ることができました。

  • OpenAPIによる仕様の一元化
    • API仕様書をOpenAPIで定義し自動生成ツールを使用することでバックエンドとフロントエンド間の不整合を排除
  • APIアクセス用コードとモックの自動生成による開発効率の向上
    • 自動生成ツールにはOrvalを使用しswrmswのコードを自動生成
    • データアクセス部分を自動生成に委ねることで他のフロントエンドの課題解決に専念できる
    • テストコード内で使用するモックも自動生成された関数を使うことでテストケースが簡素化される

生成されるコード

自動生成されるソースコードは以下の様に利用します。

APIアクセス

OpenAPI定義内容を元に生成されたswrのカスタムフックは以下の様に使用します。

// APIアクセス用カスタムフック`useGetUserQuery`が自動生成される
import { useGetUserQuery } from './generated/api';

const { data, error } = useGetUserQuery({ id: 123 });

テストコード内のモック

テストコードでも同様にOpenAPI定義内容を元に生成されたmswのモック定義を行う関数を活用することでAPIリクエストをモックしてテストコードの簡素化をしています。

// `msw`のモック定義を行う関数`getGetUserMockHandler`が自動生成される
import { getGetUserMockHandler } from './generated/mocks';

describe("キャンセルボタンをクリックした場合", () => {
    it("`dialogUpdater`が呼び出されること", async () => {
        const mock = [getGetUserMockHandler()];
    
        server.use(...mock);
    
        render(
            <Conponent/>
        );
    
        await waitFor(() => {
            screen.getByRole("button", { name: "キャンセル" }).click();
        });
    
        expect(dialogUpdater).toHaveBeenCalledTimes(1);
    });
});

2. Remixの規約に沿ったディレクトリ設計

Remixを採用し、File-basedルーティングを活用してページ単位でディレクトリを整理しました。
app/routesにページごとにディレクトリを作成し各ページのコンポーネントを管理する事で特定のページに関連するコンポーネンが把握しやすくなりました。

$ tree app/routes
app/routes
├── $id.inquiry
│   ├── confirm-dialog.test.tsx
│   ├── confirm-dialog.tsx
│   ├── create-form.test.tsx
│   ├── create-form.tsx
│   ├── introduction.test.tsx
│   ├── introduction.tsx
│   └── route.tsx

3. イネイブリングの体制

大きな技術基盤の変化は大きな認知負荷となります。
今後は新しい基盤の上に機能を開発していくことになるためイネイブニングを最優先に行うチーム体制を設置しました。
具体的な取り組みとして以下のようなことを行っています。

  • 新しい技術の共有会
  • 機能開発を行うチームのデイリーMTGへの参加
  • 機能開発時のペアプロ
  • Devcontainerによるローカル開発環境構築
  • README、ADRなどのドキュメントの維持

これらの対応により、キャッチアップのコストを最小限に抑え開発式全体で取り組めるように工夫をしています。

まとめ

フロントエンドの技術基盤の改善は急成長期における課題を乗り越えるための重要な一歩でした。
このときの技術スタックを再利用することでHonoを使ったお問い合わせ基盤の改善が高速に開発できたりと技術基盤の変化が徐々に影響範囲を広げていることを感じています。

明日は、同じプロダクティビティチームで一緒にフロントエンドをガリガリ開発している鳴瀬さんさんです!よろしくお願いします〜!

MOSH

Discussion