Slide 1

Slide 1 text

現実世界における、 スキーマ設計の妥協 日本経済新聞社 Yuta Ide Encraft #2 サーバーとクライアントを結ぶ技術 1

Slide 2

Slide 2 text

自己紹介 module.exports = { name: “Yuta Ide”, belong: “日本経済新聞社”, role: [“Client”, “Edge”, “BFF”], lang: [“TypeScript”, “Rust”].push(“Scala”), “schema now I’m using”: [“JSON Schema”, “zod”, “joi”, “GraphQL”, “pbuf”] } 2

Slide 3

Slide 3 text

3

Slide 4

Slide 4 text

羨ましい!!! 4

Slide 5

Slide 5 text

tl;dr スキーマレスに10年以上開発されているものに、 スキーマを導入するのは難しすぎる! 5

Slide 6

Slide 6 text

日本経済新聞社の負債返済活動 ● 3ヶ月開発を止めて非機能要件開発 ○ Node.jsアップデート作業 ○ アラート改善活動 ○ スキーマ改善活動 ○ パフォーマンスメトリクス計測の仕組み ○ 過去ページのアーカイブ化(SSG) ○ E2E整備 https://speakerdeck.com/sadnessojisan/jian-shi-senaakansi-w u-da-zhi-dakeniookamitutena https://www.youtube.com/watch?v=L--AVk29m6w 6

Slide 7

Slide 7 text

スキーマ改善活動 手書きのAPIスキーマ定 義、実際の値と異なって いるから直しても良かで す??? わぁ、いいよ! ありがとう!!! 7 ※ここまで和やかな職場ではありません

Slide 8

Slide 8 text

スキーマ改善活動 ● バックエンドチームを兼務してSpecやMiddlewareを書いていた ● 手書きyaml の Spec から Validator を生成して、それを Django (Python) に組み込む仕組みを作った ● そして現実を知るのである、「Specが信用できないのはAPIチームのサボ タージュや技術不足ではなく、そもそも・・・」 スキーマ駆動開発導入の難しさを知る 8

Slide 9

Slide 9 text

スキーマ駆動開発とは 9

Slide 10

Slide 10 text

ここでの定義は、 フロントエンドとバックエンドで、 疎通のインターフェースを合意し、 そのインターフェースに従う形で お互いが開発すること。 10

Slide 11

Slide 11 text

スキーマの運用を疎かにすると何が起きるのか ● サーバーの開発が終わるまでフロントは疎通できない ● クライアントが想定していないデータを受け取った場合、クライアント側で ランタイムエラーが発生する(かもしれない) ● サーバーからどういうデータが返ってくるのか、コンピュータは分からない 11 API開発 フロント開発 API改修 フロント対応 BE FE

Slide 12

Slide 12 text

反対にスキーマがあると何が嬉しいのか ● サーバーサイドの開発を待たずにどういうレスポンスが返るか分かる ● サーバーの戻り値の型を得られる ● APIクライアントも得られる(作れる) 12 API開発 フロント開発 API改修 フロント対応 schema schema BE FE

Slide 13

Slide 13 text

スキーマ通りの値をサーバーが返すことが前提 スキーマは実値を表しているとは限らない!!!! 13

Slide 14

Slide 14 text

ドキュメント・バリデータ・型を同期させよ! https://blog.ojisan.io/swagger-validator-ts/ 14

Slide 15

Slide 15 text

フロントエンド開発における 代表的な技術 15

Slide 16

Slide 16 text

JSON Schema ● スキーマ: JSON Schema ● 型: typebox, json-schema-to-ts ● バリデーション: ajv 16

Slide 17

Slide 17 text

Swagger ● スキーマ: Open API Spec ● 型: swagger-typescript-api ● バリデーション: swagger-model-validator, ajv(OAS is based on json schema) 17

Slide 18

Slide 18 text

zod ● スキーマ: zod ● 型: z.infer ● バリデーション: z.parse() 18

Slide 19

Slide 19 text

GraphQL ● スキーマ: GraphQL schema ● 型: graphql-code-gen ● バリデーション: ??? (graphql-codegen-typescript-validation-schema と いうのが最近あるらしいが筆者は触っていない) 19

Slide 20

Slide 20 text

gRPC ● スキーマ: Protocol Buffers ● 型: ts-protoc-gen, ts-proto ● バリデーション: ??? 20

Slide 21

Slide 21 text

バリデーションは本当に必要? 21

Slide 22

Slide 22 text

client API Data Source スキーマ駆動開発はここの話 22

Slide 23

Slide 23 text

client API Data Source 23 validation validation スキーマ駆動開発はここの話

Slide 24

Slide 24 text

client API Data Source 型や契約が満たされている処理 24

Slide 25

Slide 25 text

client API Data Source 型が付いている通信 型が付いてるのに、 validation 必要??? 25

Slide 26

Slide 26 text

バリデーション系の話をあまり聴かない ● ajv はともかく、schema に対するバリデーションの話を聞かない ○ ajv は逆で、バリデーションライブラリがあって、要求しているIDLがJSON Schemaという関 係 ● unknown な箇所に validation して型をつけた後のレスポンス作成におい て、プログラミング言語が型安全を保証しているのであればvalidation は不 要では? 26

Slide 27

Slide 27 text

正しい、静的な型があるとは限らない ● そもそも静的型付け言語でなければ型安全にならない ● 動的型付け言語ではそもそもレスポンスの形に型付かないし、コードからド キュメントやSpecを生成できない(難しい) ● 静的な型があったとしても、型をごまかすハッチの存在、型指定をうっかり 忘れるなど、多くの言語では型に対してウソをつける 相手が信用できないのなら、 スキーマ通りの値か検証した方が良い 27

Slide 28

Slide 28 text

コードから生成しないSpecは嘘をつける POST /users 絶対200 手書きSpec Wiki スプレッドシート なんでもあり実装 28

Slide 29

Slide 29 text

言語・FW選択で割と勝敗が決まる ● コードからドキュメントやSpecを生成するのが苦手な言語やFWというもの は存在する ● 静的型付け言語で書かれたコードなら、型情報を使ってレスポンスのSpecを 生成しやすい ● もちろん自分で解析したらどの言語でもできるが、「そこまでできます か?」という問題がある ● 例: MVC FW からモデルを介さずにレスポンスを作る 29

Slide 30

Slide 30 text

言語・FWに依存しない スキーマ駆動開発をしたい! 30

Slide 31

Slide 31 text

JSON Schema という妥協 31

Slide 32

Slide 32 text

日本経済新聞社のアーキテクチャ CDN Origin API GW 13年間も積み重なった レガシー&入稿基幹サー ビス群 Frontend Teamの持ち物 信用できない データ 32 Swaggerとかが生まれる前から稼働してい るサービスなのだから仕方がない!

Slide 33

Slide 33 text

日本経済新聞社のアーキテクチャ CDN Origin API GW 13年間も積み重なった レガシー&入稿基幹サー ビス群 Frontend Teamの持ち物 信用できない データ 33 Gateway の責務は Gateway なので素通しする 信用できない データ

Slide 34

Slide 34 text

日本経済新聞社のアーキテクチャ CDN Origin API GW 13年間も積み重なった レガシー&入稿基幹サー ビス群 Frontend Teamの持ち物 信用できない データ 34 信用できない データ どちらかでバリデーションをしたい

Slide 35

Slide 35 text

日本経済新聞社のアーキテクチャ Origin API GW 信用できない データ 本当はここでバリデーションをして、信 用できるデータにしたい 35

Slide 36

Slide 36 text

日本経済新聞社のアーキテクチャ Origin API GW 信用できない データ 本当はここでバリデーションをして、信 用できるデータにしたい 36 一部APIにおいて、バリデーションに使うための OAS準拠の仕様書が存在せず、サーバー側でバリデータを作れない。 (スプレッドシートにある)

Slide 37

Slide 37 text

日本経済新聞社のアーキテクチャ Origin API GW 信用できない データ (APIGWから見た)クライアント側でバ リデーションする 37

Slide 38

Slide 38 text

日本経済新聞社のアーキテクチャ Origin API GW 信用できない データ (APIGWから見た)クライアント側でバ リデーションする 38 クライアントサイドには実績ある型定義があるので、 それをスキーマとして使うことができる。

Slide 39

Slide 39 text

つまりクライアントが 勝手にスキーマを持つ 39

Slide 40

Slide 40 text

ところでそれはスキーマ駆動開発ですか? ● クライアントが独自にスキーマを持ってしまうと、もうそれはスキーマ駆動 開発では無い ● ただし、クライアントのスキーマをサーバーが参照して開発しているのであ ればスキーマ駆動開発もできる validation & typing のためのスキーマを作るだけ 40

Slide 41

Slide 41 text

クライアントで完結する技術選定 ● そりゃあGraphQLやらgRPCが理想ですよ・・・ ● が、それらの導入には APIGW の開発が必要となり、現実的では無い ● JS/TS前提のスキーマライブラリを考える ○ joi ○ yup ○ zod ○ ajv 41

Slide 42

Slide 42 text

クライアントで完結する技術選定 https://blog.ojisan.io/i-use-ajv-instead-of-zod/ 42

Slide 43

Slide 43 text

選ばれたのは JSON Schema でした ● 2023年現在、一番良いのは zod だと思う ● が、これまでの歴史上 joi -> yup -> zod とスキーマライブラリが乗り換えら れている ● zod の次が出た時、zod ベースのソースはどうなるのか?スキーマはサー バーの根幹であり、そこが流行に振り回されていいのか??? ● 長生きするIDLを採用したい -> JSON Schema 言語や流行に左右されないIDLを使おう 43

Slide 44

Slide 44 text

JSON Schema とは ● JSON で定義する、JSONの形 ● 詳しくは JSONとJSON Schemaを改 めて理解する 44

Slide 45

Slide 45 text

ajv とは ● Another JSON Validator ● JSON Schema で定義された validator を作れる https://ajv.js.org/ 45

Slide 46

Slide 46 text

fastify との相性が良い ● frontendチームの技術スタックは Fastify での自作SSR ● Fastify は Ajv が標準で組み込まれ ており、req/res を検証・シリアラ イズできる ● APIを持つ場合のSpec生成に Swagger を使う後JSON Schema か らSwaggerを吐き出すプラグインが ある https://blog.ojisan.io/swagger-validator-ts/ 46 https://blog.ojisan.io/swagger-validator-ts/

Slide 47

Slide 47 text

スキーマ導入までの道筋 1. スキーマを生成する 2. バリデータを生成する 3. バリデーションに失敗した時のハンドリングをする 47

Slide 48

Slide 48 text

スキーマ導入までの道筋 1. スキーマを生成する 2. バリデータを生成する 3. バリデーションに失敗した時のハンドリングをする 48

Slide 49

Slide 49 text

クライアントサイドにどうやって Schema を作るか ● 手書きされた API Spec から JSON Schema を作る ○ 手書きされたSpecが実値と乖離しすぎていて却下 ● サーバーの実値から JSON Schema を生成する ○ 動的なキー(やめろ!)やOptionalがあるので実値からだとスキーマの完成形が分からない ● TypeScriptの型から JSON Schema を生成する ○ クライアント側はすでに await res.json() as any as Type としていて稼働実績ある型を使っ ている。それが正しいのかはさておき・・・ 49

Slide 50

Slide 50 text

TypeScriptの型から JSON Schema を生成する ● quicktypeやtypescript-json-schema ● すでにクライアントサイドにある型から JSON Schemaを自動生成する。運用し ている型なのである程度正しい実績もあ る。 ● 実は Optional, Nullable, Undefined の 扱いが怪しい・・・ https://blog.ojisan.io/typescript-json-schema-ajv/ 50 https://blog.ojisan.io/add-nullable-to-json-schema/

Slide 51

Slide 51 text

1 API に 610 行のスキーマが生成される。 コードからの自動生成でしかスキーマを導入できない!

Slide 52

Slide 52 text

1 API に 610 行のスキーマが生成される。 コードからの自動生成でしかスキーマを導入できない! _人人人人人人人人_ > Over Fetching <  ̄Y^Y^Y^Y^Y^Y^Y^Y^ ̄

Slide 53

Slide 53 text

スキーマ導入までの道筋 1. スキーマを生成する 2. バリデータを生成する 3. バリデーションに失敗した時のハンドリングをする 53

Slide 54

Slide 54 text

バリデーションしたあとの値には型がついて欲しい User型がついて欲しい 54

Slide 55

Slide 55 text

ajv で型を付ける JSON Schemaに対応する型の Genericsを渡す。(出鱈目な 方を渡すとコンパイルエラーに なる) 型がつく 55

Slide 56

Slide 56 text

JSON Schema に対応した型を作る ● 今回はTSからJSON Schema を生成し たので考えなくてもいいが、そうで無 い場合どうすればいいか ● TS first な JSON Schema を生成でき る IDL を使う ○ typebox ○ zod ● json-schema-to-ts の型 Util https://blog.ojisan.io/ajv-to-type/ 56

Slide 57

Slide 57 text

スキーマ導入までの道筋 1. スキーマを生成する 2. バリデータを生成する 3. バリデーションに失敗した時のハンドリングをする 57

Slide 58

Slide 58 text

バリデーションに失敗すること前提で作る ● そもそも “single source of truth” が存在していないところにスキーマを導 入するので、バリデーションがスキーマ通りに成功する訳が無い ● 残念ながら、バリデーションに失敗した時にそこで例外を投げられない ● 例:記事表示ページで、おすすめ記事一覧リストのデータがバリデーション 違反でした。例外を投げて記事全体を表示されなくなることが許されるの か? 58

Slide 59

Slide 59 text

バリデーションに失敗すること前提で作る ● バリデーションに失敗したら型を無理やり付けて素通しさせる ● ただしSentryなどに吐き出されるようにしておく 59

Slide 60

Slide 60 text

スキーマ違反を素通しさせて意味があるのか? ● 「結局スキーマ違反な値にウソの型を与えてクライアントに返せば、クライア ント側でランタイムエラー起きますよね」「わかっとるわ!!!!」 ● Validation Error を集計することが大切 ● クライアントが期待するデータに対する Validation Error を集めることで、 APIチームやさらにその裏側に「こういうデータを返してください」と言える API側の手書きスキーマを改善する サイクルを作るための第一手 60

Slide 61

Slide 61 text

もしゼロから作り直すならどうするか 61

Slide 62

Slide 62 text

日本経済新聞社のアーキテクチャ CDN Origin API GW 13年間も積み重なった レガシー&入稿基幹サー ビス群 Frontend Teamの持ち物 信用できない データ 62 信用できない データ どこかでバリデーションをしたい どこか一層に信頼できないデータ層が生まれると、その前段に信頼できないデータは伝 播していく。つまりスキーマ通りの値を返す仕組みを作るのであれば、入稿や DBのス キーマから正しいスキーマを伝播していく必要がある

Slide 63

Slide 63 text

日本経済新聞社のアーキテクチャ CDN Origin API GW 13年間も積み重なった レガシー&入稿基幹サー ビス群 Frontend Teamの持ち物 信用できない データ 63 信用できない データ どこかでバリデーションをしたい 信用できないデータソースがある状況で信用できるデータを返すようにしたいのであれ ば、結局はどこかの層でバリデーションは必要で、誰かが APIがエラーを返す覚悟で例 外を投げなければいけない

Slide 64

Slide 64 text

リプレイスする時に守る1つの鉄則 ● スキーマやドキュメントに違反した 値を返してはいけない ○ 外部に公開するAPIは必ずSpecを公開す る ○ Spec違反の値を返さないように検証する ● 防御的(!?)・予防的・攻撃的・契約 プログラミングの考え方を自チーム や自社に当てはめて戦略を考えよう ○ 0ベースで開発する場合、違反するもの は落として欲しい 64 https://speakerdeck.com/twada/php-conference-2016

Slide 65

Slide 65 text

DBや入稿側から正しいスキーマを伝播させる ● DBに面する入稿システムやマイクロサービスが、DBのスキーマを元に型安 全なAPIを作ると信頼を伝播させられる ○ ORM ○ Compile-time SQL Check DBからクライアントまでを 型安全に繋ぐ技術を採用する 65

Slide 66

Slide 66 text

例: ORM ● Entity とクエリのマッピング ● DBからの値が Entity として型がつくようになる ● 例) Prisma, TypeORM, Active Record, Django ORM… 66

Slide 67

Slide 67 text

例: sqlx ● Compile-time checked queries ● ORMではないが、コンパイル時にス キーマを検証し、DTOへのマッピン グまでもマクロで可能 ● コンパイル時にDBに対してクエリが 走り、クエリの実行結果とRustの世 界で型検査できる https://github.com/launchbadge/sqlx 67

Slide 68

Slide 68 text

型安全な言語を利用する ● 多くの言語は、GraphQLもgRPCも型やスキーマをごまかして使えてしまう ので、クライアントからするとクエリやメッセージ通りの値を受け取れる保 証がない ● 自分がサーバーを実装するなら Rust で書きたい!!!!!!!!! ● Rustの一番好きな点は、「型を誤魔化すコストが高く、誤魔化すモチベー ションがなくなる」ところ。any や ts-ignore 的な抜け道が塞がれている。 Rustで書かれたサーバーはそれだけでフロントエンドエンジニア視点で信用 できて、過剰な防御をやめる動機になる!!! 68

Slide 69

Slide 69 text

っていうのは全部妄想です ● まとめ①: スキーマレスで動いてしまってるものにスキーマを入れるのは難しいです。フル リプレイスは救いですが、現実的にフルリプレイスなんてものは簡単にできるわけがないの で、できるところからやっていきましょう。妥協するには JSON Schema が良いです ● まとめ②: しかし妥協するからにはコンピューターフレンドリーで自動化された運用フロー に期待できず、人間が運用フローを作らないといけないです ● そんな運用フローを一緒に整備してくれる方を募集しています 「散々レガシーなこと話しておいて人が来ると思ってるの?」と思うかもしれませんが、どこの会 社にもレガシーはあるはずだし悪いことだとは思っていないです。むしろそういった過去の遺産 を、技術力や組織の力でどう未来に繋げていくかという仕事はクリエイティブで面白いと思います し、自分も楽しいです。面白そうと思った方はぜひカジュアル面談しましょう。もちろんモダン環 境(Nix, Rust, Scala)でひたすら楽しい開発ができる仕事もあります。是非カジュアル面談をしま しょう。 69