yoshikipom Tech Blog

AtCoderで数ヶ月精進して入水した話

はじめに

いわゆる入水記事。2019/06/15からずっと緑帯だったがついに入水した。以前はコンテスト参加がメインの学習だったが、2度の挫折後にいろいろ改善してパフォーマンスが安定してきたのでメモ。

レートの遷移 from AtCoder.jp

  • 2019年あたりから継続的に参加していたが、2021年に緑帯でレートが伸びなくなり挫折
  • 2022年秋から再チャレンジするもむしろレートが下がって挫折
  • 2023年秋から再々チャレンジし2024/01/20に水色レート達成!

2022年まではなぜレートが伸びなかったか

2022年あたりのコンテスト結果 from AtCoder Problems

2022年のデータを見てみると以下のことがわかる。(濃い緑がコンテスト中に解けた問題。右下に時間とペナルティ数)

  • 緑diffが安定していない
  • ペナルティが多すぎる
  • 水色diffは解けていない

緑diffは解説を見たら理解できるが、コンテスト中に正しい解法に到達できていないことが多かった。また、ペナルティは「証明できていない状態で貪欲法を実装した」というのが多かった気がする。これは知識不足ではなく、問題の考察力不足だったと考えている。以下の精選100問は解いていたので知識はある程度あったはず。問題を別の問題に落とし込んだり、正しい解法を選択するためにはある程度問題を問いて考察力を使える必要があった。

qiita.com

改善結果

2023年あたりのコンテスト結果 from AtCoder Problems

2023年のデータを見ると緑diffは安定し、ペナルティは減った。水色diffは試験中に解答に近い方法である程度実装できていることもあったが、相変わらず解けていない。

しかし、スピードが改善されたおかげか(30分程度でD問題を解けていることが多くなった)、直近5回のパフォーマンスはすべて水色以上をキープすることができた。

直近のパフォーマンス from AtCoder.jp

改善のためにやったこと

解法がわからないとき

たまたま見たこの動画がめちゃくちゃ刺さった。以前は全く解法がわからない問題に対してぼんやりしたまま間違った解法を実装することが多かったが、この動画を見たあとは考察段階で間違った解法は間違っていると判断できるようになったり、エッジケースを事前に洗い出すことができるようになった。

www.youtube.com

ここで述べられている全く解法がわからない問題の対策は「解かない」、「具体例で考える」、「問題を分割する」。

  • 「解かない」 -> 基本前から解いているがAC数が少なそうな場合は後回しするなど。
  • 「具体例で考える」 -> 紙とペンでいろいろデータ操作してみる。
  • 「問題を分割する」 -> 範囲による場合分けをしたり愚直な解き方で難しい部分を抽出して考えたり。

以下の記事も問題の性質から解法を考えるときにとても役に立つ。

algo-logic.info

解法を考える順番

間違った貪欲法でよく失敗していたので、「計算量の小さいプログラム」よりも「遅くて正しいプログラム」から始めるようにした。全探索でやったらどうなるか?をまず考える。すると、このクエリに O(log(N)) で答えれたらなぁ とか、このリストの最小値が O(1) で答えれたらなぁという願望が出てくるので、その部分だけ改善すればうまくいくことが多かった。そもそもABCのA、B、C問題は単純に全探索するだけでよいことも多い。

修行

一番大事。正直、コンテストだけ出ていれば水色に到達できるという甘い考えがあったが全く伸びる気がしなかったので以下の記事を参考に「水色到達」 or 「1100問まで問く」にチャレンジすることにした。この再々チャレンジスタート時は解いた問題数が536問。

過去問を解くことはとても重要です。実際に、mencotton さんのツイート によると、800 問解けば 60% 、1100 問解けば 90% の人が水色コーダーになれます。(2020/01/17 時点の統計です) https://qiita.com/e869120/items/eb50fdaece12be418faa

結果としてはAC数850で水色達成となった。(競プロ典型90問の☆5以下 + 緑diffをひたすら解く + コンテスト参加)

緑diffはABCのうち、ABC42以降はすべて埋めることができた。ARCもちょくちょく埋めていたが、ABCと比べて考察が難しいことが多かったため後回しにした。

精進の記録1 from AtCoder Problems

精進の記録2 from AtCoder Problems

おわりに

ここ数ヶ月かなり競技プログラミングを楽しんでいたのでAtCoderに感謝。青コーダーを目指す前にAtCoder Heuristic Contestや他の競技プログラミングサイトにも興味があるのでチャレンジしたい。

参考

2023年の振り返り(エンジニア6年目)

エンジニアリング

仕事

2022年はバックエンドチームでテックリードをしていたが、2023年はアーキテクトとして働いていた。複数のマイクロサービスを跨いだ設計やシステム全体の品質を上げる取り組み、ビジネスチームとの交渉などたくさんの新たな経験ができて大変良かった。一方で開発に割く時間は減ってしまったので自主学習で補うように努めた。アーキテクトとしてうまくやれる部分がもっとある感じなので来年も精進していく。

競技プログラミング(AtCoder)

以前に水色(レート1200)を目指して1154まで行ったがその後レートが下がって挫折していた。11月あたりから再熱して地道に難易度緑の問題を毎日解き、8回コンテストに出てレート1113まで戻せた。数を解いていると解ける率が上がってきて楽しいので挫折するまで精進+コンテスト参加は続ける予定。今回こそは挫折前に水色まで行きたい、、、。

AtCoderの記録
from https://kenkoooo.com/atcoder/

勉強

エンジニアリング系の本は15冊読んだ。以下のような本たちで少し低レイヤ寄りの学習にも力を入れた。最近の本は難しいことをわかりやすく説明していて本当にスゴい。この辺も評判がいいものを見つけたら積極的に読んでいく。

今年は読みたい本を読んでいるだけで手一杯だったが、来年は少し時間を取ってアーキテクチャ周りの難しい本(Patterns of Enterprise Application Architecture とか Enterprise Integration Patterns とか)にもチャレンジしておきたい。

AWS Solution Architect Professional

夏に一ヶ月くらいがんばって勉強して取った。知識だけでなく、非機能要件を考慮した設計について練習できたのでよかった。 tech-yoshikipom.hatenablog.com

OSS開発

今年中になにかGo言語でコントリビュートしたいと思っていたので今月滑り込みでTiDBへのコントリビュートをがんばった。このままTiDBをがんばるか他にチャレンジするかは来年の気分次第、、、。 tech-yoshikipom.hatenablog.com

英語

前半は仕事が忙しかったのもあってあまり力を入れれていなかった一方、後半は久々のTOEICに向けて勉強、Elsa Speakで発音矯正、ChatGPTで英会話など何かしら継続できている。

TOEICは1,2ヶ月勉強して2回受験し、目標にしていた900点超えを達成できた。集中的に頑張ったおかげで英語を読むのが速くなったり英語Podcastが聴きやすくなった気がする。

TOEICの結果

ゲームも英語でやるようにしていて今年は「ファイアーエムブレム エンゲージ」、「Factorio」、「ゼルダの伝説TotK」あたりをクリアした。今は最近セールで買った「オクトパストラベラーⅡ」をプレイ中。

まとめ

振り返ってみると色々がんばれている感じがする。2023年ギリギリにこれを書いているので来年は目標を決めるところから頑張ります。

NewSQLを学んでTiDBにコミットした話

概要

この12月はNewSQLの学習とTiDBへのコミットに挑戦してみたのでその記録。

得られたこと

Why?

昨年末くらいからちょくちょくGo言語を使うようになり、GoでのOSS開発をしたかったのでGithubで良さそうなリポジトリを検索してみたところ、NewSQLのCockroachDBとTiDBがヒットした。NewSQLについては PodcastのMisreading ChatでCockroachDBについて話す回で今後来そうと思っていたのでチャレンジすることに。 misreading.chat

NewSQLを学んでCockroachDBかTiDBを選ぶ

CockroachDBとTiDBのどっちがいいか知りたかったので何が同じで何が違うかをまず学んだ。 この記事がとてもわかりやすくておすすめ。 qiita.com

2021年最終更新なので各プロダクトの細かいところは公式推奨。例えばCockroachDBのストレージエンジンはRocksDBではなくGoで書き直したPebbleというものを使っている模様(RocksDBはC++)。

CockroachDB uses the Pebble storage engine. Pebble is inspired by RocksDB 参考

NewSQLはざっくりいうとRDBSQLインターフェースとトランザクションを残しつつ分散システム化されたデータベースシステム。SQLトランザクションサポートに加えてスケーラビリティが求められる場面使われる。Google Cloud Spannerが最も有名で他のNewSQLはSpannerのアーキテクチャを参考にしている。

話をCockroachDB vs. TiDB に戻すと、主に以下の点に注目することにした。

  • CockroachDB
    • インターフェース: Postgres互換
    • ストレージエンジン: Pebble (Go製、RocksDBを参考にしてる)
    • アーキテクチャ: Spannerベースでクエリエンジンとストレージエンジンが1ノードに同居
    • 開発元: Google出身者が立ち上げたCockroach Labs
  • TiDB
    • インターフェース: MySQL互換
    • ストレージエンジン: TiKV (Rust製、内側でRocksDB利用)
    • アーキテクチャ: クエリエンジン(TiDB)とストレージエンジン(TiKV)が別居
    • 開発元: 日本法人もあるPingCAP
    • 日本で採用事例多数

CockroachDBの方は最近の自分の仕事はPostgresだったり、ストレージエンジンもGoで触れるというメリットがあったが、TiDBのアーキテクチャと日本での採用や情報の多さに惹かれて結局TiDBを深掘ってみることにした。

TiDBを学ぶ

日本語リソース

短く全体感がまとまっていてよかったのはこの動画。 www.youtube.com

PingCAP Japanの動画はLSM Tree、MVCC、Raft(合意アルゴリズム)など内部で使われている技術やその採用理由について学べる。 無料オンライントレーニング (PingCAP Education) - YouTube

英語リソース

公式に十分な情報がある。モダンな感じで読みやすい。 docs.pingcap.com

pingcap/tidbへのコントリビュート

方法は基本的にdev guideの以下の章を読んでおけばOK。 pingcap.github.io

TiDBの関連リポジトリはたくさんある(以下に記事にまとまっていた)が、とっつきやすくIssueもたくさんあったので クエリエンジンの pingcap/tidb (以下アーキテクチャ画像の"TiDB"部分)を選択した。 qiita.com

TiDBのアーキテクチャ https://docs.pingcap.com/ja/tidbcloud/tidb-architecture

最初は label:good first issue、その後はlabel:type/bug で対応できそうなものを探した。

現状ドキュメント修正含めてPull Requestを6つ出して5つはマージ済み、1つはPrivateリポジトリで管理しているCIをPingCapの人が直すらしいので待機中。 対応したIssueなど

最初はチームのルールに慣れるように簡単なコードのリファクタリング的なもの、その後にSQLパーサーや実行計画などが関連する部分を見ていった。また、dev guideを読んでいるとリンクが切れていたり古かったりするものがあったのでドキュメントにもコミットしてみた。

コントリビュートした感想

よかったこと

  • dev guideの説明がわかりやすく、SQLのパース、実行計画の作成、実行、トランザクション管理がどのあたりのコードで行われているのかわかる
  • SQL自体は理解している状態だったので動かしながらdebugしやすいので根気よくやれば調査もなんとかできそうだった
  • PingCAPのメンバーが積極的に動いているので割とすぐにレビューやコメントをしてもらえる
  • Prow という CI/CDシステムが活用されていてIssueやPull Requestの管理の自動化がスゴい
  • コントリビュートする気で学ぶことでTiDBの中にある様々なアルゴリズムやツールについて学習したときに頭に入ってきやすかった。

つらかったこと

  • なんだかんだでコードリーディングはかなり時間がかかる。特にデータ構造(AST、実行計画など)がかなり複雑
  • Unit Testの確認 (ローカルで全部回すと1時間以上かかる、Flaky Test が多い)
  • Privateリポジトリで管理されている Integration Testがあり、自ら修正できない
  • Bazelの経験不足

おわりに

OSS開発、楽しいしかなり学びが多いので続けていきたい。

TOEICのリーディングを5分残しで終えられるようになった方法

学習前の状況

最後にTOEICを受けたのは3年ほど前でそのときは最後の文章まで辿り着けなかった。つまり191-200の大半は塗り絵。900点を目標とした受験に向けていくつか模試を解いたが時間内に解き終わることがなかったので対策することにした。やったことは3点。

  • 速読力の強化
  • 語彙力の強化
  • 時間配分を意識

学習結果

1ヶ月程度で大体 130wpm(word per minute) -> 160wpm くらいには成長した感覚。TOEICの公式問題集や本番ではリーディングで5分程度残すことができ、リスニングのPart3,4で全選択肢に目を通してから聴けるようになった。10月のスコアは935点で無事目標達成。

やったこと

wpmの計測 + 音読

きっかけは以下の動画。同じ教材を繰り返して読む理由のところに納得感があった。計測が改善の第一歩なのはどの世界でも同じ。

www.youtube.com

教材は以下の毎日の英速読という本にした。英速読が目標になっているため各文章のワード数が大体同じで、ワード数やwpmの変換表がある。文章の内容がさまざまで飽きにくいのもよかった。

やり方は上記の動画の内容を少し簡略化して以下の流れにした。

  1. 初回の黙読でwpm記録
  2. 満足いくまで繰り返し音読
  3. 黙読で200wpmで読めるか確認

20セクションあったので、1日1,2個ずつのペースで1-10を2周してから11-20を2周した。 最後のほうは初回で150wpmを超えるようになっていたので負荷は下がっていった。 この方法は音読を通してwpmという数値が伸びていくところが楽しいのでモチベも保ちやすくおすすめ。

語彙力の強化

当初は単語学習だけで十分だと考えていたが、TOEICによくある表現に瞬時に反応できるほうが楽だと理解したので 「TOEIC L&R TEST 出る単特急 金のセンテンス」と「TOEIC L&R TEST 出る単特急 金の熟語」を学習した。

答えに直接掛かってくる場合もあるが、そうでなくても数単語をまとめて理解することでスピードアップできるのでリーディングの時間に不安があるならやっておいて損はない。

時間配分を意識

満点を目指しているわけではないので分からない問題や知識が不足している問題は諦めるのも大事。また、時間に応じてスピード重視の解き方(正解を見つけたら他の選択肢は見ない 等)に切り替えるスキルも大事。とにかく目標時間で解き終わるのを第一目標とした。

リーディングは75分。最終的な自分の時間配分は以下。

  • Part 5: 10分
  • Part 6: 8分
  • Part 7: 52分 (SP:27, MP:25) ※ SP=シングルパッセージ、 MP=マルチパッセージ

Part5,6は精度重視で文脈も考慮して全部読んでいるためこれ以上高い目標にするのは現状難しかった。逆にPart7は確認のための読み返しで時間を浪費することが多かったので時間がないときはすぐ次に進むことで平均的に早くなった。

終わりに

TOEICは英語を「速く」読む試験としてとても良かった。現状でもネイティブの半分程度でしか読めていないようなので普段から英語で情報を入れるようにして研鑽を積みたい。

「アレクサ、XXXをつけて」-> 事前指定したURLのYouTube動画を再生

Overview

最近、YouTubeでワンピースのアニメが24時間放送されている(参考)。我が家ではテレビで頻繁にこの放送を見ているが、開くのが少し面倒だったので自動化してみた。

  • As-Is: リモコンでYouTubeアプリを起動(ショートカットで1ボタンではある) → ログイン(ログイン済みなら不要) → ワンピース動画を探してクリック → 動画再生
  • To-Be: 「アレクサ、ワンピースをつけて」 → 動画再生

環境

ハードウェア

この記事の実装で必要なハードウェアは以下。

  • アレクサが使えるデバイス
  • ローカルにあるサーバ
  • webOS搭載のテレビ
  • 開発機

我が家で使ったのは以下。

  • セールで安く買えた Alexa Echo Pop
  • Home Bridgeしか役目ののなかった Raspberry Pi 3
  • LGのテレビ 55UN8100PJA
  • MacBook

ソフトウェア

全体像

アレクサを使ってテレビでYouTube動画再生

使わなかった技術たち

Chrome castや既存のAlexa Skill

フレーズが長くなるのが嫌なので避けた(アレクサ、YouTubeでXXXの動画を再生して 等)

Alexa + Home Bridge

別の選択肢としてAlexa → Home Bridge の連携をできるスキルがあった。しかし、有料化していたので今回は見送った。

After the initial 7 day trial period for new users, a monthly subscription costs $2.00 USD per month or an annual subscription costs $22.00 USD per year. Basically buying me a coffee once a month ( cheaper than a beer ). https://www.homebridge.ca/faq

CMD4 という特定のスクリプトを Home Bridge のアクセサリとして登録できるいいソフトウェアがあったので、Alexa + Home Bridge を使うかHomePodを手にいれることがあれば実践投入したい。 CMD4 https://ztalbot2000.github.io/homebridge-cmd4/autoGenerated/CMD4_AccessoryDescriptions.html

公式のwebOSライブラリ

ライブラリでやることはローカルネットワークにあるテレビのIPアドレスを指定して接続し、WebSocketを通してJSONでwebOSと通信する。ただ、公開されているのはWebSocketのAPIではなく、JSのライブラリとリファレンスっぽい。

最近はGo言語の勉強をしているのでGo言語での実装を見つけて使うことにした。4年ほど更新されていないが動作する。 https://github.com/kaperys/go-webos

スクリプト→webOS上でのYouTube起動の実装

上記のリポジトリそのままだと以下のことが自動化できなかった。

  • YouTubeで特定URLを開く (YouTubeアプリを開くことはできる)
  • アプリ起動後のログイン画面で自動ログイン

そのため、フォークして一部拡張した。 https://github.com/yoshikipom/go-webos

スクリプト1: Client ID取得

https://github.com/yoshikipom/go-webos/blob/master/cmd/auth/main.go

以下のようにIPアドレスを渡して実行するとテレビにプロンプトが出るのでOKを押せばClient IDが返ってくる。

go run cmd/auth/main.go 192.168.3.10 > client-id

これを次のスクリプトに渡せばプロンプトの表示なしにスクリプトが実行できる。この Client IDは長期間使えるため(具体的な時間は不明)、このスクリプトは一度だけ実行すればよい。

スクリプト2: YouTuvbeで動画を開く

https://github.com/yoshikipom/go-webos/blob/master/cmd/launch_youtube/main.go

以下のように IPアドレス、Client ID、YouTubeのURLを渡せば対象の動画が再生される。

go run cmd/launch_youtube/main.go 192.168.3.10 $(cat client-id) https://www.youtube.com/watch?v=79XaA_4CYj8

main.go のメイン部分。tv.Command(... の部分で動画を開くことができる。コマンド実行時のテレビの状態によってはうまく開かなかったりログイン状態で止まったりしてしまうため、追加の処理(tv.OpenAppやsleep)が入っている。tv.KeyEnter() はログイン画面から進むため。画面の状態を取得する方法を調べればこの辺は改善できそうだが、不便していないのでこのままにした。

   a, err := tv.CurrentApp()
    if err != nil {
        fmt.Printf("Error: %+v\n", err)
    }
    currentAppIsYoutube := a.AppID == youtubeAppID

    if !currentAppIsYoutube {
        tv.OpenApp(youtubeAppID)
    }

    _, err = tv.Command(webos.SystemLauncherLaunchCommand, webos.Payload{"id": youtubeAppID, "params": map[string]interface{}{"contentTarget": videoURL}})
    if err != nil {
        fmt.Printf("Error: %+v\n", err)
    }

    sleepBeforeLoginClick(currentAppIsYoutube)

    tv.KeyEnter()

シェルスクリプト

Goに渡す引数が地味に長いのでNode-REDに渡す実行コマンドを短くするためにシェルスクリプトを作っておいた。引数の変更時にいちいちNode-REDの管理画面を開かなくてよいのもメリット。 https://github.com/yoshikipom/go-webos/tree/master/server

ビルドとか

ラズパイ用にビルドして、scpで送った。

GOOS=linux GOARCH=arm GOARM=7 go build -o bin/auth cmd/auth/main.go
GOOS=linux GOARCH=arm GOARM=7 go build -o bin/launch_youtube cmd/launch_youtube/main.go
scp bin/* ${user}@${ip}:${path_in_server}
scp server/command.sh ${user}@${ip}:${path_in_server}
scp client-id ${user}@${ip}:${path_in_server}

アレクサ→Node-REDでのスクリプト実行

この記事を参考にさせていただいたところ問題なく動いた。 https://qiita.com/g-iki/items/a5d4d4674a30de7ed124

変えたところ - このユースケースだと「XXXつけて」しか使わないので Node-REDの switch は is true 側にだけスクリプトに繋げれば十分。 - 管理画面でのデバイス追加も On だけ使えるようにして Off は使っていない。

Node-REDのGUI
Node-REDのデバイス追加

ここまでやったあとに「アレクサ、ワンピースつけて」というと無事再生できた。

感想

  • GUIで設定するのに抵抗感があってNode-REDは使ったことがなかったが、シンプルで拡張性も高そうなので良い
  • こういうタスクは保守性が気にならないのでPythonでやったほうがよかった

AWS Solution Architect Professional に合格! (SAP-C02, 2023)

受験動機

2021年に合格した AWS Certified Solutions Architect - Associate (SAA) の有効期限(3年)が切れる前に AWS Certified Solutions Architect - Professional (SAP) に合格しておきたかった。 SAP に合格すると SAA の期限も自動延長される。

結果

  • 受験科目: AWS Certified Solutions Architect - Professiona (SAP-C02)
  • 学習期間: 26日 (08/12-09/06)
  • 受験回数: 1回
  • スコア: 829点 (合格点は750/1000)
    Result

選んだ教材

AWS認定資格試験テキスト&問題集 AWS認定ソリューションアーキテクト - プロフェッショナル 改訂第2版

メイン教材1。解説+簡単な章末問題+模試1回。最初の1週間くらいはこの教材のみを以下のステップで進めた。

  1. 各章を読み終わったら章末の問題を解く
  2. 巻末の模試
  3. 小松の問題と巻末の模試2周目

Kindleで購入したが、問題<->解答の移動やよく見るページへのブックマークが使いづらかった。次にこのシリーズを買うなら紙にしたい。

AWS WEB問題集で学習しよう

https://cloud-license.com/

メイン教材2。7問1セクションの問題集。残りの2,3週間はこれを毎日やっていた。解いたのは以下。

  • #30-#83 (7問/セクション * 54セクション = 378問)
  • 2周

最初は半分くらいしか解けなかったが、最後の方は初見の問題でも7-8割くらい取れるようになっていて成長を実感できた。 また、Webなのでタブレットでもスマホでも実施できたのがよかった。文章が長い試験なので基本はタブレットでやっていたが、隙間時間にスマホで手軽に進めることも多々あった。

Practice Exam AWS Certified Solutions Architect Professional

https://www.udemy.com/course/practice-exam-aws-certified-solutions-architect-professional/

SAP-C02対応の模試。WEB問題集にも模試機能があるが、新しい問題で力試ししておきたかったので購入した。セールで1800円。日本語のものよりレビューが良さそうで、Google翻訳のブラウザ拡張でなんとかなるとの意見もあったので試してみた。30問/75問/75問の全3セット。

結果としては、Google翻訳では理解できない問題がいくつかあった。また、難易度が高めに感じた(翻訳のせいかもしれない)。受験2日前に受けた3セット目ですら68%しか正解できず、自信を失った(ので最後の2日は焦ってたくさん勉強した)。

AWSの基本・仕組み・重要用語が全部わかる教科書 (見るだけ図解)

サブ教材。ビジュアルでサービスの概要を理解できる。ネットワーク系や権限管理周りはメイン教材1よりわかりやすかった。また、AI系やIoT系のサービス理解にも役立った。注意点としては試験用の教材ではないのでSAPの範囲内のサービスでも全く説明されていないことはある。いくつかの間違った理解をビジュアルを通して正せたので総合的には買ってよかった。

個人的Tips

全選択肢を検討する

事前の調査で良質な教材は限られているような印象を持った。そのため、各問題からできるだけ知識をつけておくことが大事だと考え、正解でない選択肢はなぜ正解でないか考えてから解答を見るようにした。その点に関しては解説が十分でないこともあったのでググりながら自分の考えを補正していった。

Anki

https://ja.wikipedia.org/wiki/Anki

WEB問題集2週目でも覚えられないサービス概要や数値(バケット数の制限、特性サービスの処理速度制限など)、ググってわかった結果などはAnkiにQ&Aとして登録しておいて時々復習した。入力はやや面倒だが、シンプルなクイズに変換しておくことで復習の時間はかなり少なく済んだと思う。暗記がメインの試験ではないので登録した項目は100以下で済んだ。PCで入力、スマホで復習。

おわりに

知識だけでなくアーキテクトとしての考え方を問われる面白い試験だったのでいい勉強になった。自費で受けているので落ちたら再受験に30,000円かかるのはプレッシャーだった。一発合格できたものの3年後に再受験費用がかかるのはちょっと辛い。

oapi-codegenを使ってクリーンで仕様ズレの起きないGo言語API開発

解決したい課題

OpenAPIでのspecとコード開発が分離されていると以下のような課題がある

  • 利用者が見ると仕様と実装のズレが起きる可能性
  • ズレが起きないとしても開発時の仕様確認や慎重なレビューが面倒

oapi-codegenを使うことでspec通りの実装を強制できる。 https://github.com/deepmap/oapi-codegen/tree/master

oapi-codegenを使ったAPI開発のステップ

  • (A) OpenAPI定義を作成
  • (B) oapi-codegen で (A) からGo言語APIのコード生成
    • 生成されたコードは、APIのリクエストとレスポンスの型安全性を保証
  • (C) (B)に含まれるサーバのインターフェース(StrictServerInterface)を実装
  • (D) main関数から(C)をリクエストハンドラとして利用しているWebフレームワークに登録

ここでは以下の条件に沿って例を示す。

  • oapi-codegenのexampleにあるOpenAPI仕様を利用
  • Webフレームワークはechoを使用

今回の完成品の全体感は以下。

.
├── api // API controllers
│   ├── api.gen.go // (B)generated API handler + server interface
│   ├── api.go // (C) server implementation
│   ├── server.yml // (B)config to generate api.gen.go
│   ├── type.gen.go // (B) generated types
│   └── type.yml // (B) config to generate type.gen.go
├── go.mod
├── go.sum
├── main.go (D)
└── openapi.yaml // (A) source of api.gen.go & type.gen.go

コードは以下に置いてある。 https://github.com/yoshikipom/go/tree/main/oapi-codegen

(A) OpenAPI定義を作成

今回は以下をそのまま使う。自分で書くならVS Codeの拡張やStoplight Studioを使う。 https://github.com/deepmap/oapi-codegen/blob/master/examples/petstore-expanded/petstore-expanded.yaml

(B) oapi-codegen で (A) からGo言語APIのコード生成

yamlファイルでoapi-codegenの生成ルールを定義 server.yml

package: api
generate:
  echo-server: true
  strict-server: true
  embedded-spec: true
output: api/api.gen.go

type.yml

package: api
generate:
  models: true
output: api/type.gen.go

これらをoapi-codegenで読み込んで API関連のコードであるapi/api.gen.go と リクエスト・レスポンスなどの型情報であるapi/type.gen.go を生成。

go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@latest
oapi-codegen -config api/type.yml openapi.yaml
oapi-codegen -config api/server.yml openapi.yaml

(C) (B)に含まれるサーバのインターフェース(StrictServerInterface)を実装

自分はVSCodeの拡張でコードを生成してから中身を埋めている。

VSCodeのInterface Stubを生成する機能

receiver名、構造体の型、実装したいinterfaceを渡す

生成されるコード

// Returns all pets
// (GET /pets)
func (ss *MyStrictServer) FindPets(ctx context.Context, request api.FindPetsRequestObject) (api.FindPetsResponseObject, error) {
    panic("not implemented") // TODO: Implement
}

// Creates a new pet
// (POST /pets)
func (ss *MyStrictServer) AddPet(ctx context.Context, request api.AddPetRequestObject) (api.AddPetResponseObject, error) {
    panic("not implemented") // TODO: Implement
}

...

中身の実装例。RequestBodyがAddPetRequestObject.Bodyにbindingされていることや、Pet型を戻り値として渡せることから型安全かつ仕様通りに実装できることがわかる。また、このレイヤですでにWebフレームワークに依存しない実装になっているのも良い。

func (ss *MyStrictServer) AddPet(ctx context.Context, request AddPetRequestObject) (AddPetResponseObject, error) {
    var pet Pet
    pet.Name = request.Body.Name
    pet.Tag = request.Body.Tag
    pet.Id = ss.NextId
    ss.NextId = ss.NextId + 1

    ss.Pets[pet.Id] = pet

    return AddPet200JSONResponse(pet), nil
}

(D) main関数から(C)をリクエストハンドラとして利用しているWebフレームワークに登録

  • e.Use(middleware.OapiRequestValidator(swagger)) でリクエストのバリデーションを行うmiddlewareを登録
  • api.RegisterHandlers(e, h) でechoに(C)で実装したハンドラを登録
package main

import (
    "flag"
    "fmt"
    "os"

    "github.com/deepmap/oapi-codegen/pkg/middleware"
    "github.com/labstack/echo/v4"
    echomiddleware "github.com/labstack/echo/v4/middleware"
    "github.com/yoshikipom/go/oapi/api"
)

func main() {
    var port = flag.Int("port", 8080, "Port for test HTTP server")
    flag.Parse()

    swagger, err := api.GetSwagger()
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err)
        os.Exit(1)
    }
    swagger.Servers = nil

    e := echo.New()
    e.Use(echomiddleware.Logger())
    e.Use(middleware.OapiRequestValidator(swagger))

    ss := api.NewMyStrictServer()
    h := api.NewStrictHandler(ss, []api.StrictMiddlewareFunc{})
    api.RegisterHandlers(e, h)

    e.Logger.Fatal(e.Start(fmt.Sprintf("0.0.0.0:%d", *port)))
}

動作確認

サーバサイド

yoshiki@yoshiki-mbp:go/oapi-codegen ‹main›$ go run main.go                                    

   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.10.2
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:8080
{"time":"2023-07-04T23:39:23.773179+09:00","id":"","remote_ip":"127.0.0.1","host":"localhost:8080","method":"POST","uri":"/pets","user_agent":"vscode-restclient","status":200,"error":"","latency":121657,"latency_human":"121.657µs","bytes_in":43,"bytes_out":41}

クライアント

yoshiki@yoshiki-mbp:go/oapi-codegen ‹main›$ curl --request POST \
  --url http://localhost:8080/pets \
  --header 'content-type: application/json' \
  --header 'user-agent: vscode-restclient' \
  --data '{"name": "Alice","tag": "test2"}'
{"id":1001,"name":"Alice","tag":"test2"}

OpenAPI Generator はどうか

OpenAPI Generator https://github.com/OpenAPITools/openapi-generator これ一つでOpenAPI定義から多様な言語のサーバー側、クライアント側のコードを生成できる優れもの。 ただ、保守性の面で不安がある。oapi-codegen の場合は生成されたファイルに手を加える必要がないが、OpenAPI Generatorの場合は生成されたファイルに手を加えて拡張する必要がある。例えば、main.goも生成されるが、middlewareの追加などを手動で行うことはよくあるので手動での変更は必須である。これだと仕様変更時にファイルを再生成場合、手動でマージする必要が出てしまう。 Go言語のAPI開発だけ考えるのであれば oapi-codegen を選択する。

感想

クライアントサイドの生成と違って、サーバサイドの生成はメンテが難しそうで抵抗があったが、このツールで生成したコードはロジックの実装と完全に分離されているので非常に使いやすそう。REST APIを新規開発する機会があれば導入してみたい。