👏

スキーマ駆動開発を拡張したソフトウェアデザインを考えている

2024/12/16に公開

開発効率、整合性、スケーラビリティの向上を目指して


背景と課題

現在の課題

  1. フロントエンドとバックエンドの同期が困難

    • 手動でのインターフェース同期作業が時間と労力を要する。
  2. REST APIの設計・保守コストが高い

    • 仕様変更や拡張時に高コストな再設計が必要。
  3. 機能追加や要件変更で「技術的負債」が蓄積

    • アーキテクチャの整合性が失われ、保守性が低下する。

提案の概要

スキーマ中心の完全自動化アーキテクチャ

  1. Prismaを起点としたスキーマ駆動設計

    • Prismaスキーマがシステム全体の"信頼できる単一のソース"となる。
  2. GraphQLを「システムの膜」として活用

    • フロントエンドとバックエンドの橋渡し役を担い、柔軟性と整合性を提供。
  3. フロントエンド・バックエンドの完全自動統合

    • Prismaスキーマから自動生成される成果物により、開発効率を飛躍的に向上。

技術的に発想したもの

主要な自動生成内容

Prismaスキーマから生成されるもの

  1. データベースセットアップ

    • Prisma CLIでスキーマからマイグレーションとテーブルを自動生成。
  2. GraphQLスキーマ

    • Prismaスキーマを基に型安全なGraphQLスキーマを自動生成。
  3. APIスタブ

    • REST API不要でGraphQLを直接活用。
  4. TypeScriptモデル、Reducer、API Hooks

    • フロントエンドのモデル・状態管理・データ取得ロジックを自動化。

ワークフロー

Mermaidシーケンス図


メリットの整理

開発効率の向上

  • スキーマを基点にした完全自動化により、工数を大きく短縮する見込み

整合性の維持

  • スキーマ駆動設計により、アーキテクチャの一貫性が保証される

スケーラビリティの向上

  • 機能追加や要件変更に柔軟に対応する為の検討から始まった

ビジネス価値(コストメリット)の向上

  • REST API設計の不要化はコストを削減する
  • 開発スピードの向上は、ROIを最大化するだろう

実装と検証

パート毎の実装検証とその見込み成果

  1. 開発スピード向上: 作業時間を従来比で30%は短縮するだろう
  2. 技術的負債の削減: 要件変更時でもシステムの整合性は維持される
  3. チーム全体の生産性向上: フロントエンド・バックエンド間の同期作業を完全自動化する事により、各担当チームでのパラレルな作業進行により生産性の向上が見込まれる

サンプルコード

ステート管理: コンパクトなReduxの例

import { configureStore } from "@reduxjs/toolkit";
import userSlice from "./slices/userSlice";
import authSlice from "./slices/authSlice";

const store = configureStore({
  reducer: {
    user: userSlice,
    auth: authSlice,
  },
  devTools: process.env.NODE_ENV !== "production",
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

export default store;

自動生成されたAPIフックの例

import { useQuery, useMutation } from "@apollo/client";
import { GET_USER, UPDATE_USER } from "./graphql/queries";

// ユーザー取得用のカスタムフック
export const useGetUser = () => {
  const { data, loading, error } = useQuery(GET_USER);
  return {
    user: data?.user,
    isLoading: loading,
    hasError: !!error,
  };
};

// ユーザー更新用のカスタムフック
export const useUpdateUser = () => {
  const [updateUser, { data, loading, error }] = useMutation(UPDATE_USER);
  return {
    updateUser,
    updatedUser: data?.updateUser,
    isLoading: loading,
    hasError: !!error,
  };
};

本設計を応用したOSSプロダクトを開発中

https://github.com/vvvvise/invvarch

Discussion