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
WebAPIのバリデーションを、型の力でいい感じにする
Search
takuya kikuchi
September 29, 2023
0
120
WebAPIのバリデーションを、型の力でいい感じにする
Web API LT会 - vol.3 のLT資料です #webapilt
takuya kikuchi
September 29, 2023
Tweet
Share
More Decks by takuya kikuchi
See All by takuya kikuchi
AIエージェントを支える設計
tkikuchi1002
13
3.7k
「現場で活躍するAIエージェント」を実現するチームと開発プロセス
tkikuchi1002
8
2k
20250708_engineering_bd
tkikuchi1002
0
110
Agentic Workflowという選択肢を考える
tkikuchi1002
1
1.2k
生成AI時代のソフトウェアエンジニアが持つべきケイパビリティを考える
tkikuchi1002
8
5.8k
RAGをテーマに考える、LLMの認知アーキテクチャとソフトウェア設計
tkikuchi1002
3
1.6k
生成AIの不確実性と向き合うためのオブジェクト指向設計
tkikuchi1002
3
7.2k
Azure AI SearchとPromptFlowではじめるRAG
tkikuchi1002
2
1.5k
法人向けChatGPTにおける Azure OpenAI Serviceの課題解決の過程と現在
tkikuchi1002
2
2.2k
Featured
See All Featured
Being A Developer After 40
akosma
90
590k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
1.4k
Why Our Code Smells
bkeepers
PRO
338
57k
KATA
mclloyd
32
14k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
46
7.6k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
18
1.1k
Fashionably flexible responsive web design (full day workshop)
malarkey
407
66k
The Pragmatic Product Professional
lauravandoore
36
6.8k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
44
2.4k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
30
9.6k
Intergalactic Javascript Robots from Outer Space
tanoku
272
27k
Optimising Largest Contentful Paint
csswizardry
37
3.4k
Transcript
confidential WebAPIのバリデーションを、型の力でいい感じにする Web API LT会 - vol.3 takuya kikuchi /
Showcase Gig
confidential ©Showcase Gig 自己紹介 • takuya kikuchi • twitter: @_pochi
• Engineer Group Manager @ Showcase Gig • モバイルオーダープラットフォームを作っています • たまに実店舗も作ります
confidential ©Showcase Gig APIが受け取るパラメータのバリデーション ちゃんとできてますか? SDK, OpenAPI, gRPC, etc… 「そんな入力予期してなかった・・・」
「そんな使い方しないで・・・」
confidential ©Showcase Gig 素直なアプローチ紹介 gRPCの例 protoc-gen-validate を使ってバリデータの自動生成ができるよ 🐔 参考: 【Go】gRPCのリクエストバリデータを自動生成する
https://note.com/scg_tech/n/nb12a33bfd391 import "github.com/envoyproxy/protoc-gen-validate/validate/validate.proto"; ~~~~~~~~~~~ service TestServer { rpc Test(TestMessage) returns (Result) {} } ~~~~~~~~~~~ message TestMessage { // 0~100間の整数 int32 seisuu = 1 [(validate.rules).int32 = {gte:0, lt: 100}]; // floatで0~1の値 double fudou = 2 [(validate.rules).double = {gte: 0, lte: 1}]; // アルファベットと数値で、5〜30文字のrepeated repeated string mojiretsu = 3 [(validate.rules).repeated.items.string = {pattern: "^[a-z0-9]{5,30}$", min_len: 5, max_len:30}]; // RFC 1034で解釈可能なメールアドレス string mail_address = 4 [(validate.rules).string.email = true]; }
confidential ©Showcase Gig 型の話をします
confidential ©Showcase Gig 型はいいぞ • 静的型付け言語では、コンパイル時に型チェックをしてくれる • 型の明らかな渡し間違いはすぐ気づくことができる import "fmt"
func main() { var intValue int = 12345 printString (intValue) } func printString (str string) { fmt.Printf("%s\n", str) } import "fmt" func main() { var str string = "文字列だよ" printString (str) } func printString (str string) { fmt.Printf("%s\n", str) } コンパイルOK コンパイルエラー
confidential ©Showcase Gig しかし基本データ型には限界がある • メールアドレスのみを受け入れたいのだけど、コンパイラさん気づいてくれない import "fmt" func main()
{ var mailAddress string = "メアドではないよ" printMailAddress (mailAddress) } func printMailAddress (mailAddress string) { fmt.Printf("%s\n", mailAddress) } import "fmt" func main() { var mailAddress string = "
[email protected]
" printMailAddress (mailAddress) } func printMailAddress (mailAddress string) { fmt.Printf("%s\n", mailAddress) } コンパイルOK 🤗 コンパイルOK 😢
confidential ©Showcase Gig しかし基本データ型には限界がある • そもそもユーザーから任意の値が入力されるものはどうしようもない import "fmt" func main()
{ var str string // ユーザー入力を受け取る _, err := fmt.Scan(&str) if err != nil { // 読み取り失敗 return } printMailAddress(str) } コンパイルOK。ただしメアド以外も printできてしまう。
confidential ©Showcase Gig さてどうする
confidential ©Showcase Gig 「メールアドレス」型を作ろう • 正しいメアド入力以外はオブジェクトを生成できないようにする type MailAddress string func
NewMailAddress (mailAddress string) (MailAddress , error) { if !isValidMailAddress (mailAddress) { return "", errors.New("メアドではないので NG") } return MailAddress (mailAddress) , nil } func printMailAddress(mailAddress MailAddress) { fmt.Printf("%s\n", mailAddress) } 型とコンストラクタを定義。コンストラクタで、「正しいメールアド レスかどうか」をチェックする printMailAddressメソッドの引数も、その型を受け取るようにする
confidential ©Showcase Gig 「メールアドレス」型を活用する • コンパイル時チェックはできないが、「メールアドレスじゃない文字列が渡された場合」のエラーハンドリング実装が強制さ れる ◦ なので、「うっかりメアドじゃない文字列が渡されてクラッシュ!」みたいなことが回避できる func
main() { var str string // ユーザー入力を受け取る _, err := fmt.Scan(&str) if err != nil { // 読み取り失敗 return } // 入力された文字列から、 MailAddressオブジェクト生成 mailAddress, err := NewMailAddress (str) if err != nil { // メールアドレスではない文字列が渡された return } // MailAddress を出力する printMailAddress (mailAddress) }
confidential ©Showcase Gig 本題。WebAPIの話
confidential ©Showcase Gig メールアドレスを登録するWebAPI • メールアドレスを受け取ってDBに記録するエンドポイントのコントローラをイメージしてください • MailAddressRepositoryは単にSQLを呼ぶだけの実装だと思ってください // メールアドレスを登録するエンドポイントの実装
func updateMailAddressController (userID int, mailAddress string) error { // DBアクセス用のリポジトリ取得 repository := newMailAddressRepository () // DBに書き込み err := repository. Update(userID, mailAddress) if err != nil { // DB書き込みエラー return errors.New("internal error" ) } // 正常終了 return nil } type MailAddressRepository interface { Update(userID int, mailAddress string) error }
confidential ©Showcase Gig 問題点 • mailAddress のバリデーションをしていない。 ◦ 渡されてくるmailAddress次第でいろんなエラーが起きてしまう ▪
メアドじゃなくても登録できる(それはダメ!) ▪ 文字列が長すぎたらDBへのクエリでエラーが起きて internal error (カッコ悪い!) • メアドじゃない文字列が渡されたらクライアントエラーにしてあげたい // メールアドレスを登録するエンドポイントの実装 func updateMailAddressController (userID int, mailAddress string) error { // DBアクセス用のリポジトリ取得 repository := newMailAddressRepository () // DBに書き込み err := repository. Update(userID, mailAddress) if err != nil { // DB書き込みエラー return errors.New("internal error" ) } // 正常終了 return nil }
confidential ©Showcase Gig 先程作ったメールアドレス型を使います • MailAddressRepositoryの引数を、string から MailAddress型に変更 type MailAddressRepository
interface { Update(userID int, mailAddress MailAddress) error } type MailAddressRepository interface { Update(userID int, mailAddress string) error }
confidential ©Showcase Gig するとどうなるか • 元のコードに戻ります。 • mailAddressが string なのでコンパイルエラーになります
// メールアドレスを登録するエンドポイントの実装 func updateMailAddressController (userID int, mailAddress string) error { // DBアクセス用のリポジトリ repository := newMailAddressRepository () // DBに書き込み err := repository. Update(userID, mailAddress) if err != nil { // DB書き込みエラー return errors.New("internal error" ) } // 正常終了 return nil }
confidential ©Showcase Gig あとはコンパイルエラーを解消する • コンパイルエラーを解消するために、引数で渡された文字列をMailAddress型に変換します • その際にエラーハンドリングがごく自然に実装されます // メールアドレスを登録するエンドポイントの実装
func updateMailAddressController (userID int, mailAddressStr string) error { // DBアクセス用のリポジトリ repository := newMailAddressRepository () mailAddress, err := NewMailAddress(mailAddressStr) if err != nil { // 渡された文字列がメールアドレスではない return errors.New("client error") } // DBに書き込み err = repository. Update(userID, mailAddress) if err != nil { // DB書き込みエラー return errors.New("internal error" ) } // 正常終了 return nil }
confidential ©Showcase Gig まとめ リクエストパラメータをバリデーションしたいあなたへ → バリデーションしたいということは、その値には何らかのルールがある → そのルールを表現する、専用の型を用意してみよう →
専用の型の利用を強制しよう → すると、パラメータのバリデーションは自然に実装される
confidential ©Showcase Gig おしまい
confidential ©Showcase Gig 宣伝 • APIが好きな人 • 型が好きな人 • Goが好きな人
• gRPCが好きな人 • エンジニアリングが好きな人 • POSが好きな人 • 次世代店舗を作りたい人 https://www.showcase-gig.com/recruit/engineer/