Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
WebAPIのバリデーションを、型の力でいい感じにする
Search
takuya kikuchi
September 29, 2023
170
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
WebAPIのバリデーションを、型の力でいい感じにする
Web API LT会 - vol.3 のLT資料です #webapilt
takuya kikuchi
September 29, 2023
More Decks by takuya kikuchi
See All by takuya kikuchi
AIエージェントを支える設計
tkikuchi1002
13
5k
「現場で活躍するAIエージェント」を実現するチームと開発プロセス
tkikuchi1002
8
3.3k
20250708_engineering_bd
tkikuchi1002
0
210
Agentic Workflowという選択肢を考える
tkikuchi1002
1
1.9k
生成AI時代のソフトウェアエンジニアが持つべきケイパビリティを考える
tkikuchi1002
8
6.6k
RAGをテーマに考える、LLMの認知アーキテクチャとソフトウェア設計
tkikuchi1002
3
2k
生成AIの不確実性と向き合うためのオブジェクト指向設計
tkikuchi1002
3
7.9k
Azure AI SearchとPromptFlowではじめるRAG
tkikuchi1002
2
1.7k
法人向けChatGPTにおける Azure OpenAI Serviceの課題解決の過程と現在
tkikuchi1002
2
2.5k
Featured
See All Featured
Neural Spatial Audio Processing for Sound Field Analysis and Control
skoyamalab
0
550
A Soul's Torment
seathinner
8
3.7k
Navigating Weather and Climate Data
rabernat
0
550
The SEO Collaboration Effect
kristinabergwall1
1
590
Producing Creativity
orderedlist
PRO
348
41k
Unsuck your backbone
ammeep
672
58k
Product Roadmaps are Hard
iamctodd
55
13k
Highjacked: Video Game Concept Design
rkendrick25
PRO
1
480
Ruling the World: When Life Gets Gamed
codingconduct
0
400
The Mindset for Success: Future Career Progression
greggifford
PRO
0
530
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
490
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
520
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/