Upgrade to Pro — share decks privately, control downloads, hide ads and more …

現実世界におけるスキーマ設計の妥協

 現実世界におけるスキーマ設計の妥協

sadnessOjisan

April 25, 2023
Tweet

More Decks by sadnessOjisan

Other Decks in Technology

Transcript

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

    View Slide

  2. 自己紹介
    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

    View Slide

  3. 3

    View Slide

  4. 羨ましい!!!
    4

    View Slide

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

    View Slide

  6. 日本経済新聞社の負債返済活動
    ● 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

    View Slide

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

    View Slide

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

    View Slide

  9. スキーマ駆動開発とは
    9

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  26. バリデーション系の話をあまり聴かない
    ● ajv はともかく、schema に対するバリデーションの話を聞かない
    ○ ajv は逆で、バリデーションライブラリがあって、要求しているIDLがJSON Schemaという関

    ● unknown な箇所に validation して型をつけた後のレスポンス作成におい
    て、プログラミング言語が型安全を保証しているのであればvalidation は不
    要では?
    26

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  31. JSON Schema という妥協
    31

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  46. 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/

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  50. 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/

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  56. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  64. リプレイスする時に守る1つの鉄則
    ● スキーマやドキュメントに違反した
    値を返してはいけない
    ○ 外部に公開するAPIは必ずSpecを公開す

    ○ Spec違反の値を返さないように検証する
    ● 防御的(!?)・予防的・攻撃的・契約
    プログラミングの考え方を自チーム
    や自社に当てはめて戦略を考えよう
    ○ 0ベースで開発する場合、違反するもの
    は落として欲しい
    64
    https://speakerdeck.com/twada/php-conference-2016

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide