現実世界におけるスキーマ設計の妥協
by
sadnessOjisan
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
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