Slide 1

Slide 1 text

品質とスピードを両立: TypeScript の柔軟な型システムを バックエンドで活用する kosui (@kosui_me) 1 1

Slide 2

Slide 2 text

 https://kosui.me ﵵ @kosui.me ﰏ @kosui_me kosui / 岩佐 幸翠 日本の医療体験をしなやかにするために 医薬業界向けのサービスを多面的に展開 医療システムに要求される高い品質と 医療体験を本気で変えるためのスピードを両立させたい プロダクト開発チームと一緒に テックリードとして認証基盤・組織管理システムなどを開発  株式会社カケハシ  社内プラットフォームシステムの開発 2 2

Slide 3

Slide 3 text

バックエンドとフロントエンドの共通点 リンター、フォーマッター、パッケージマネージャーなど Array 、 Promise 、 JSON など keyof などの型演算子や、 Partial などのユーティリティ型などの活用方法 言語を統一すれば、共通の知見や資産を活用できる エコシステムへの知見や設定 標準ライブラリへの知見 型の表現 3 3

Slide 4

Slide 4 text

バックエンドとフロントエンドの相違点 フロントエンド: 使用性を重視 エラーを明快にフィードバックするべき 選択肢に無い値を選んだ場合のエラー文言は不要 バックエンド: 信頼性と機能性を重視 悪意あるユーザーや想定外の動作環境などを考慮し より厳密に検証するべき const FRUITS = [" りんご", " みかん", " ぶどう"] as const; if (FRUITS.includes(selected /* 納豆 */)) { throw new Error("invalid"); } ? 言語を統一すれば、設計や実装の技法も統一できる? ! そうとは限らない 例: バリデーションの場合 4 4

Slide 5

Slide 5 text

この発表のねらい バックエンドのような信頼性・機能性が重要なシステムを  TypeScript で開発するためには? TypeScript の型システムとバックエンドの相性の良い部分・悪い部分を理解する TypeScript の型システムの柔軟性を活かしたデザインパターンを使いこなす おことわり 私の発表ではバックエンドにフォーカスしますが 「フロントエンドとの連携」などについては、この後の発表をご期待下さい! ? 問題  解決策 方針 5 5

Slide 6

Slide 6 text

バックエンドの品質特性 なぜバックエンドでは信頼性・機能性が重要か 6 6

Slide 7

Slide 7 text

役割と品質特性から見たバックエンド 機能性 定義された I/F を満たし、正しくセキュアに処理できる 信頼性 安定して動作し、障害やエラーから復旧できる  会計・決済など正確性が重要な 処理は改ざん防止のため バックエンドで行う  プロダクトの性質に応じて 一貫性や整合性などを考慮して データを永続化する  全てのユーザー・顧客の 個人情報や認証情報など リスクのあるデータを扱う バックエンドで重視される品質特性 バックエンドの役割 正確性が重要な処理 データの永続化 非公開データの参照 7 7

Slide 8

Slide 8 text

TypeScript の型システムと バックエンドの相性 8 8

Slide 9

Slide 9 text

 TypeScript が採用している構造的部分型 型の名前で互換性を判定する 以下は Java の例 // Java record Animal( String name ) {} record Human( String name, int age ) {} Animal animal = new Human("kosui", 29); // ^^^^^^^^^^^^^^^^^^^^^^ // Incompatible types... オブジェクトの構造で互換性を判定する 記述量を抑えつつ型システムの恩恵を得られる // TypeScript type Animal = { name: string; }; const human = { name: "kosui", age: 29, }; const animal: Animal = human; // `human` がAnimal を継承しなくてもビルドが通る 名前的部分型 ( 公称的部分型) 構造的部分型 9 9

Slide 10

Slide 10 text

構造的部分型の利点 継承関係 ( 祖先・子孫) を認知する負荷を低減できる 記述量を抑えつつ型システムの恩恵を得られる 同じ構造を持つオブジェクトについて型の詰め替えを省略できる 例) データベースから取得したデータからエンティティへの詰替え テスト用のダミーやモックを簡便に表現できる // animal.ts type Animal = { name: string; }; interface AnimalRepository { find(id: string): Promise; } // animal.test.ts const dummyRepo = { find: (id: string) => Promise.resolve({ id, name: "foobar" }), }; 10 10

Slide 11

Slide 11 text

構造的部分型の特性とバックエンドの不具合 type User = { userId: string; }; type Role = { userId: string; role: string; }; const UserRepository = { delete: (user: User) => Promise, }; const deleteRole = async (role: Role) => { // Role と間違えてUser を削除している await UserRepository.delete(role); }; 構造的部分型では構造さえ同じなら何でも通す しばしば思わぬ結果をもたらす 例) リポジトリに誤ったオブジェクトを渡しても ビルドと実行が成功してしまう 永続化処理は不具合があると復旧が難しい 例) オブジェクトに機密データのプロパティが 含まれていることに気付けない データ更新時の不具合 機密データの露出 11 11

Slide 12

Slide 12 text

型定義のデザインパターンと バックエンドでの利用例 12 12

Slide 13

Slide 13 text

タグ付きユニオン - エンティティの区別 type User = { kind: "User"; userId: string; }; type Role = { kind: "Role"; userId: string; role: string; }; type Entity = User | Role; const UserRepository = { delete: (user: User) => Promise.resolve(), }; const deleteRole = async (role: Role) => { await UserRepository.delete(role); // ^^^^ // Argument of type 'Role' is not ... }; リテラル型の値から型を絞り込むことができる kind プロパティでエンティティを区別する User と Role を 取り違えて削除してしまうのを防げる タグ付きユニオン 利用例: エンティティの区別 13 13

Slide 14

Slide 14 text

幽霊型 - リテラル型の区別 実行時には存在しないプロパティを追加する幽霊型は string 型や number 型などのリテラル型を区別するのに便利 type Newtype = T & { [key in `__${Kind}`]: never; }; 異なるエンティティの ID の代入を防げる type PostId = Newtype; type UserId = Newtype; const userId: UserId = "aaa" as UserId; const postId: PostId = userId; // ^^^^^^ // Type 'UserId' is not assignable ... 検証済みの値と生の値を区別できる type Username = Newtype; const Username = { parse: (raw: string): Username | undefined => raw.length > 0 && raw.length < 16 && regexp.test(raw) ? (raw as Username) : undefined, } as const; const username = Username.parse("abc"); 利用例: ID ・コードの区別 利用例: バリデーション済みかを示す 14 14

Slide 15

Slide 15 text

全体観 エンティティ・値オブジェクトを タグ付きユニオン・幽霊型で表現 ドメインを型で守ることで... コントローラー コントローラーから渡した値が 検証済みであると幽霊型で表現できる リポジトリ リポジトリとの疎通は タグ付きユニオンでチェックできる ドメインを型で守る 外界との疎通 15 15