【失敗備忘録】あいまいなレイヤー設計によるPrisma機能の二重抽象化でGraphQL的抽象化を再発明してしまった話

## はじめに
複数のアプリケーションで同一のデータ基盤を再利用したい——この要求は、多くのプロジェクトで生じる典型的なニーズです。  
静岡大学情報学部 ITソリューション室では、室員の情報を一元管理する「室員データベース」を様々なWebアプリケーションから手軽に活用するため、`shizuoka-its/core`という共通基盤パッケージを開発してきました。

初期は、レイヤードアーキテクチャやDIパターンを「なんとなく」適用し、**Prisma**が本来提供しているデータアクセス（リポジトリ的機能）をわざわざ再実装したり、柔軟なリレーション取得を支えるために**GraphQL的な抽象化**を再発明しそうになりました。結果として、過度な抽象化がかえって開発・保守を複雑にし、「車輪の再発明」に陥ったのです。

本記事では、  

    - なぜPrismaが既に「リポジトリ」的役割を果たすのに再実装してしまったのか  
    - なぜ柔軟なユースケース対応を追求した結果、GraphQL的な型定義・クエリモデルを再構築しそうになったのか  
    - v0（サービスレイヤー過剰） → v1（スキーマのみ） → v2（再度サービス導入）の変遷を経て、どのように「本当に必要なレイヤー」を見極めるようになったのか

これらを振り返り、Prisma・GraphQL・サービスレイヤーの正しい役割分担と、過剰な抽象化を避けるための指針を紹介します。

## 背景：共通基盤開発の理想と現実
ITソリューション室では、Discord連携を通じ全室員データが一元化されており、展示作品管理ツールやイベント運用システムなど、様々なWebアプリでこのデータを利用します。  
「npm install一発でユーザーデータに型安全にアクセスできる共通基盤があれば、開発効率が上がる」という発想から`shizuoka-its/core`が誕生しました。

## v0期：レイヤーパターンの雰囲気適用による過剰抽象化
最初の実装（v0）では、なんとなく「きれいそう」という理由でリポジトリ層・サービス層を導入しました。しかしここで問題が発生します。

### Prismaが既に「リポジトリ的機能」を提供している
PrismaはDBアクセスに対して、

    - 型安全なクエリインターフェース  
    - 柔軟なリレーション取得（`include`句による深いネスト対応）  
    - CRUD操作を標準化

といった機能を提供します。これらは典型的な「リポジトリパターン」の要件（ドメインモデルへのアクセス抽象化）を多くカバーしています。  
にもかかわらず私たちは、Prismaの上にさらに独自のリポジトリクラスやインターフェースを重ね、既存の機能を再抽象化してしまいました。結果として、単純なデータ取得すら回りくどくなり、N+1問題の誘発やパフォーマンス低下、メンテナンスコスト増大といった副作用が生まれました。

### GraphQL的抽象化の再発明
また、室員データにはDiscordアカウント、イベント参加、展示作品など複数のリレーションが存在します。多彩なユースケースに柔軟対応しようとした結果、「このメソッドは特定のリレーションをinclude」「あのメソッドは別の関連をfetch」といったパターンが乱立。型定義やメソッド設計が複雑化する中、「まるでGraphQLスキーマを自前で作り、特定クエリで必要なフィールドを返す構造を定義するような作業」に陥りました。

GraphQLは、API境界で柔軟な型定義とクエリ選択を可能にする仕組みです。Prismaと独自レイヤーによる柔軟化は、GraphQLが得意とする「必要なデータのみを取得」モデルを、ORM層で無理に再構築しているような状況でした。

## v1期：抽象化撤廃によるシンプル化
この気づきを経て、v1では思い切って抽象化を取り除きました。`shizuoka-its/core`はPrismaの`schema.prisma`と自動生成される型定義（Prisma Client）のみを提供し、**アプリ側がPrismaを直接利用**してデータ取得を行う方針へ転換しました。

```typescript
// v1: Prisma本来の力を活用
const member = await prisma.member.findUnique({
  where: { id: "member-1" },
  include: {
    discordAccount: true,
    events: {
      include: {
        event: true,
        exhibits: { include: { members: true } }
      }
    }
  }
});
```

こうすることで、柔軟なクエリ設計はPrismaが標準で提供する機能で完結し、煩雑なレイヤーは不要に。GraphQL的な抽象化の再実装も回避でき、N+1問題やパフォーマンス低下といった課題も軽減されました。

## v2期：改めてサービスレイヤー導入を考える理由

「シンプル化」にしたv1を経た現在、v2では再度サービスレイヤーの導入を検討しています。「なぜまたサービス？」と思うかもしれません。

ここでのポイントは、v0でのサービス導入目的とv2でのサービス導入目的が違うことです。

    •	v0：何となくの設計美学でサービスやリポジトリを積み、Prismaが既に解決しているデータアクセス問題を再発明してしまった。
    •	v2：Prismaはあくまでデータアクセスをシンプルにするツールであり、業務ロジックや複雑なユースケース（例えば、イベント参加時の複合的なバリデーションや整合性維持、複数ドメインオブジェクト間の整合チェック）をまとめる場としてサービスレイヤーを使う。

つまり、サービスレイヤーはビジネスルールのカプセル化やトランザクション管理、複数エンティティ操作の一元化といった、本来のドメインロジック集約のために活用します。Prismaが「リポジトリ的機能」を提供する一方、ドメインサービスは「ビジネスロジックを定義する」役割を果たし、両者は明確な責務分離が可能です。

## 得られた教訓と最適なバランス

この変遷から得た教訓は以下のとおりです。

    1.	Prismaの標準機能を最大限活用する
    Prismaは高水準のデータアクセス抽象化を提供します。これを無視して独自リポジトリを作ると二重抽象化となり、複雑性が増すだけです。
    2.	GraphQL的問題はGraphQLで解決する
    柔軟なフィールド取得や型定義は、GraphQLという別のレイヤーで解決されるべき課題です。ORM層で無理に同様の抽象化を再発明する必要はありません。
    3.	サービスレイヤーは本来の目的で使う
    ドメインルールや複雑なロジックが必要な場合にのみサービス層を導入することで、責務分離を明確にできます。データ取得そのものはPrismaに任せ、サービスはビジネスロジック専用とすることで、過剰な再発明を避けられます。
    4.	段階的な進化が有効
    最初から完璧な抽象化を求めず、v0→v1→v2と段階的に設計を見直すことで、本当に必要なものが見えてきます。ツール（Prisma、GraphQL）の特性とドメイン要件を理解した上で、必要な抽象化レベルを適宜再考しましょう。

## まとめ
    •	v0では「なんとなく」の設計でPrismaの機能を再発明し、複雑化。
    •	v1で抽象化を剥がし、Prisma本来の力を活かしてシンプル化。
    •	v2では、この学びを踏まえ、本当に必要なビジネスロジック抽象化（サービスレイヤー）を再導入。

本記事で紹介した経験は、多くの開発現場で起こりうる「過剰抽象化」と「車輪の再発明」を回避するヒントになるはずです。
PrismaやGraphQLといった強力なツールを活用し、本来の役割を見極めながら、最小限かつ必要十分な抽象化を行うことで、健全で拡張しやすいアーキテクチャを実現できるでしょう。