Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
TypeSpecで繋ぐ複数プロダクトの型安全 — スキーマ共有による「型契約」の実践_mitsui
Search
UPSIDER, Inc. Tech&Product div.
June 15, 2026
4
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
TypeSpecで繋ぐ複数プロダクトの型安全 — スキーマ共有による「型契約」の実践_mitsui
TSKaigi2026でmitsuiが登壇した資料です。
https://2026.tskaigi.org/talks/23
UPSIDER, Inc. Tech&Product div.
June 15, 2026
More Decks by UPSIDER, Inc. Tech&Product div.
See All by UPSIDER, Inc. Tech&Product div.
400超のデータポイントを型で制す — UPSIDERの与信審査エンジンを支えるTypeScriptの「柔軟性」と「結合力」_泉雄介
upsider_tech
0
100
フルスタックTypeScriptで挑む、UPSIDER法人審査システムの再構築_Mitomi
upsider_tech
0
300
TypeScriptの型はAIに届いている か?_shotaro
upsider_tech
1
580
AI時代におけるプロダクト開発のあり方 を考える_Akari
upsider_tech
0
150
品質を保ちながら、 プロダクトの核に迫るための取り組み_Tanaka Yoshihiro
upsider_tech
0
210
CxOを動かす会社の条件_Akanuma
upsider_tech
0
210
Temporalを用いた Sagaの実践とプロセスモデリング_konnさん
upsider_tech
0
80
決済基盤を作る人から見た、クレカの裏側_Yuya Tanaka
upsider_tech
0
480
【日経×TOKIUM×UPSIDER】課金・決済・経理DX開発者が語るAI共創で変わる開発と意思決定_Daisuke
upsider_tech
0
800
Featured
See All Featured
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.5k
What the history of the web can teach us about the future of AI
inesmontani
PRO
1
610
Rails Girls Zürich Keynote
gr2m
96
14k
Discover your Explorer Soul
emna__ayadi
2
1.1k
Designing for humans not robots
tammielis
254
26k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.1k
Stewardship and Sustainability of Urban and Community Forests
pwiseman
0
220
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.8k
The Cost Of JavaScript in 2023
addyosmani
55
10k
Are puppies a ranking factor?
jonoalderson
1
3.5k
GraphQLの誤解/rethinking-graphql
sonatard
75
12k
SERP Conf. Vienna - Web Accessibility: Optimizing for Inclusivity and SEO
sarafernandez
2
1.5k
Transcript
TypeSpec で繋ぐ複数プロダクトの型安全 スキーマ共有による「型契約」の実践
TypeSpec って何? Microsoft 製の OSS — API ・データ契約の定義言語 書き味は TypeScript
ライク (model / op / @decorator) 1 つの .tsp → OpenAPI / JSON Schema / Protobuf に変換 @service(#{ title: "Pet Store" }) namespace PetStore; model Pet { id: string; name: string; } @route("/pets") op listPets(): Pet[]; TypeSpec とは
バリデーションも、型に焼き込む TypeSpec は単なる型定義だけじゃない。 正規表現 / 長さ制限 / 値域 といったバリデーションを 型と一体で書ける。
@pattern("^[a-z0-9]{8,}$") scalar UserId extends string; model User { id: UserId; @minLength(8) password: string; @minValue(0) @maxValue(120) age?: int32; } この制約は OpenAPI 経由で Zod にも変換でき、実行時バリデーション としても効く バリデーション
設計ルールも、tsp compile で縛れる TypeSpec の Custom Linter で、組織固有の設計ルールを tsp compile
時に強制できる。 例: 「string scalar には @pattern 必須」をルール化、違反は warning 。 createRule({ name: "require-pattern", severity: "warning", create: (ctx) => ({ scalar: (s) => isStringScalar(s) && !hasPattern(s) && ctx.reportDiagnostic({ target: s }), }), }); bad.tsp:4:8 - warning scalar 'UserId' は string を継承していますが、 @pattern が指定されていません。 > scalar UserId extends string; ^^^^^^ Found 1 warning. 契約の 書き方 だけでなく 設計ルール までガードレールに TypeSpec の拡張性
私たちの使い方 ① プロダクト内 .tsp → tsp compile → OpenAPI YAML
├─ openapi-typescript ─→ TS 型 └─ orval ──────────────→ Zod / API クライアント / MSW モック フロント (React): フォーム validation / API 呼び出し / テストモック バックエンド (Hono): リクエスト validation / レスポンス型 「パスワード 8 文字以上」を TypeSpec で 1 行書けば、FE ・BE ・テスト全部に伝播 使い方 ①
チーム間の型契約 Section
サービス間では、契約が型として存在しない # dev-cross-team サービス A エンジニア 3 分前 @team Webhook
のペイロード、これって 何型ですか? サービス B エンジニア 2 分前 Notion 見たけど 3 ヶ月前から更新止まってる… サービス C エンジニア 1 分前 サンプル JSON から 型を推測しました… 人手で守るしかなく、コンパイラの出番がない サービス間の課題
同期 API も 非同期 Webhook も 同じ言語で書ける 同期 API は
OpenAPI 、非同期メッセージは AsyncAPI / 独自スキーマ、と 別の言語で書くのが普通。 TypeSpec なら 1 つの .tsp に 両方の契約を同居 できる。 // 同期 API(@route + op で表現) @route("/applications") op listApplications(): Application[]; // 非同期 Webhook イベント(model で純粋なデータ契約として表現) model EntityApprovedEvent { type: "entity.approved"; data: ApprovalData; } どちらも 同じ TypeSpec ソースから生成される契約として扱える 独自ポイント
ドキュメントも、契約と一緒に書ける OpenAPI YAML 管理だと 型と説明文が別場所 で並走しがち。 TypeSpec なら /** ...
*/ で 型と同じ場所に 書け、OpenAPI に自動反映される。 /** 法人ユーザー(管理者を含む) */ model User { /** 一意 ID (8 文字以上) */ id: UserId; /** 連絡先メールアドレス */ email: string; } User: type: object description: 法人ユーザー(管理者を含む) properties: id: description: 一意 ID (8 文字以上) email: description: 連絡先メールアドレス 型とドキュメントが 同じソース で管理されるから、説明が古くなりにくい 管理の co-location
私たちの使い方 ② サービス間 上流チームが TypeSpec で書いた契約は npm パッケージとして公開される。 使う側のプロダクトは npm
install + import するだけで取り込める。 上流チーム TypeSpec で契約定義 → ビルド → npm publish → npm レジストリ 📦 @org/contracts 型 / Zod / API クライアント → 使う側のプロダクト npm install + import 別チーム / 別サービス "dependencies": { "@org/contracts": "^1.0.0" } import { EntityApprovedEvent } from '@org/contracts' EntityApprovedEvent.parse(payload) サービス境界を跨ぐ契約を、ドキュメントから「型」へ 使い方 ②
サービス跨ぐ契約変更を、マージ前に検知する 上流が破壊的変更を含む新 version を publish すると、Renovate / Dependabot 等の依存更新 bot
が自動で PR を起票する。 CI checks ✓ lint ✗ typecheck error TS2339: 'oldField' does not exist on 'NewEvent' ❌ Merge blocked マージ前に、契約変更の影響範囲が型エラーとして列挙される 効果
Go 中心のチームでも、TypeSpec で契約を書ける とあるチームの実装は Go ベース。 それでも契約定義は TypeSpec を採用 している。
書き味は TypeScript ライク Go メイン実装でも、契約 DSL の学習コスト は低い 出力は実装言語に依存しない OpenAPI に変換すれば Go の codegen にも 繋がる 複数チームの共通言語 上流 (Go) と下流 (TS) が同じ TypeSpec を見 て契約を共有 契約を書く言語と、サーバーの実装言語は独立して選べる 拡がり
今日の話を 3 行でまとめると 1 API も Webhook も、TypeSpec 1 ファイル
で書ける 同期 API ・非同期イベント・バリデーション・設計ルールまで、同じ DSL に集約 2 書いた契約は npm で配れて、破壊的変更は CI で落ちる サービス境界の契約が package.json の依存として存在し、影響範囲はコンパイルタイムに列挙される 3 TypeScript 以外 の実装言語でも、契約は TypeSpec で書ける 契約 DSL と、サーバーの実装言語は独立して選べる — 異なる技術スタックの共通言語になる 契約は ドキュメント ではなく コード、そして ガードレール へ 総括
スキーマ駆動を、もう一段厳格に。