記事検索

検索ワードを入力してください。
Sky Tech Blog
End-to-End Type Safetyに​よる​型安全の​考え方

End-to-End Type Safetyに​よる​型安全の​考え方

Webシステム開発におけるバグの温床となりがちなデータの「型」のズレを防ぐ設計思想、「End-to-End Type Safety(E2E型安全)」について解説します。単一の真実のソース(SSoT)や境界での実行時検証といった基本思想から、TypeScript環境でtRPCとZodを用いた具体的な実装例までを紹介。システム全体で「型=契約」を守ることで、開発体験と品質を同時に高めるアプローチを説明します。

End-to-End Type Safetyとは

Webシステムは、クライアント(ブラウザ・アプリ)とサーバ、データベース、さらに外部サービスが連携して動いています。
その行き来の中でデータの形(型)がズレてしまうと、開発者の想定と実際が噛み合わず、バグや障害の原因になってしまいます。

End-to-End Type Safety(E2E型安全)は、システムの端から端まで「」を契約書のように扱い、開発時(コンパイル時)と実行時の両方で破綻しないようにする考え方です。

基本思想

いくつか基本的な考え方を紹介します。

  • 単一の真実のソース(Single Source of Truth)
    • データ型の定義を一か所にまとめ、そこをサーバやクライアントが参照することで型を統一していきます。
  • 境界優先(Boundary First)
    • ネットワーク越し、プロセス間、DBとのやり取りなど「境界」では、必ず型の確認とランタイム検証を行います。
    • ここで正しいデータの型であることをチェックし、中に不正なデータを入れないようにします。
  • コンパイル時の型安全 + 実行時検証の併用
    • TypeScriptの型は実行時に消えてしまいます。
    • したがって、受信したデータを Zod などで「今まさに動いている状態」で検証することが不可欠です。
  • Zodとは、TypeScript向けのスキーマ宣言とデータ検証のためのライブラリです。
  • コンパイル時は「間違いに気づくスピードを上げる」、実行時は「侵入させない」と役割を分けて考えます。
  • ドメインモデルの明確化
    • ユーザーID」と「プロジェクトID」を取り違えないように、識別子をブランド化して区別します。
  • 互換性とバージョニング
    • 破壊的変更(フィールドの削除など)はバージョンを分ける、または Union 型で段階的に移行できるようにします。
  • any」を避ける
    • any は検査をすり抜けるため、最終的に事故の温床になります。
    • unknown を受けて、Zod などで絞り込む使い方が安全になります。
  • 型の流通経路を意識する
    • それぞれ責務を分け、必要に応じて変換レイヤを挟むことで、後からの拡張や差し替えがしやすくなります。

具体的な​例

サーバとクライアントが同じ TypeScript で動く環境の場合は、TypeScript の型をそのまま契約として共有できます。

以下はtRPC + Zodを使ったサンプルになります。

サーバ​(Node / Express も​しくは​ Fastify)

// server/trpc.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';

const t = initTRPC.create();

export const appRouter = t.router({
  user: t.procedure
    // 入力を Zod で検証(空文字や未定義を拒否)
    .input(z.object({ id: z.string().min(1) }))
    // 出力も Zod で定義(実行時にも検証可能)
    .output(z.object({
      id: z.string(),
      name: z.string(),
      createdAt: z.string().datetime(), // Date は ISO 文字列に変換して返す
    }))
    .query(async ({ input }) => {
      // DB 層は別型(Prisma/Drizzle など)→ DTO に変換して返す
      const entity = await getUserById(input.id);
      return {
        id: entity.id,
        name: entity.name,
        createdAt: entity.createdAt.toISOString(),
      };
    }),
});

export type AppRouter = typeof appRouter;

クライアント​(React)

// client/trpc.ts
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../server/trpc';

export const trpc = createTRPCReact();

// 使用例
function UserCard({ id }: { id: string }) {
  const { data, isLoading } = trpc.user.useQuery({ id });
  if (isLoading) return <span>Loading...</span>;
  // createdAt は ISO 文字列なので、表示時に Date オブジェクトへ
  return <div>{data!.name}(作成: {new Date(data!.createdAt).toLocaleString()})</div>;
}

ポイントとしては、

  • 入力・出力を Zod で定義することで、型安全(TypeScript)とランタイム安全(実際の検証)を両立できる
  • サーバとクライアントで同一の型が共有され、自動補完やエラー検知が可能
  • 日付や複雑な構造はシリアライズ方針(ISO文字列など)を決めて、境界で変換する というところです。

まとめ

End-to-End Type Safety は、「型=契約」をシステム全体で守ることで、バグの温床になりがちな境界部分を安全にし、開発体験と品質を同時に高める設計思想です。
同言語なら tRPC のような RPC 系で素早く体感でき、異言語・組織間では OpenAPI や GraphQL などのスキーマ駆動で強固な契約を築けます。

入力・出力のスキーマを明確にし、境界で必ず検証する。
これを積み重ねるだけで、システム全体の「型の流れ」が整い、変更に強く、安心してリファクタリングできる基盤が育っていきます。


\シェアをお願いします!/
  • X
  • Facebook
  • LINE
キャリア採用募集中!

入社後にスキルアップを目指す若手の方も、ご自身の経験を幅広いフィールドで生かしたいベテランの方も、お一人おひとりの経験に応じたキャリア採用を行っています。

Sky株式会社のソフトウェア開発や製品、採用に関するお問い合わせについては、下記のリンクをご確認ください。
お問い合わせ
ホーム