これからOSS活動を始める方へ:Goを使用した個人開発の話
Photo by Johannes Plenio
こちらは「ヌーラボブログリレー2024 for Tech Advent Calendar 2024」の24日目の記事です。
この記事ではOpenID ConnectをGoで実装する過程をシェアしたいと思います。OSS活動を始めたいと考えている方の参考になれば幸いです。なお、OpenID Connectの技術的な部分には深く踏み込まず、プロジェクト全体を俯瞰しながら、開発をどのように進めていったかをご紹介します。
プロジェクトの成果
次のリポジトリで公開しています。
なぜ OpenID Connect を Go で実装したのか
私がOpenID ConnectをGo言語で実装しようと決めた背景には、いくつかの具体的な経験と興味がありました。過去に担当したプロジェクトでOpenID Connectを使用する機会があり、その際に「自分でゼロから仕組みを作り上げたら面白そうだ」と感じたことが一番の理由でした。
また、Go言語でWebアプリケーションを作ってみたいという気持ちも大きく影響しました。これまでGoを業務で使用した経験はなく、特に認証やデータベースアクセスを含むアプリケーションをゼロから構築することに魅力を感じていました。
さらに、近年のGo言語の人気上昇も大きな動機の一つでした。多くの企業やプロジェクトがGoを採用している現状を目にし、そのエコシステムやコミュニティが非常に活発であることを実感しています。Goの特徴やベストプラクティスをしっかりと理解することは、今後のキャリアにおいても有益だろうと考えていました。
こうした背景から、OpenID ConnectをGoで実装するプロジェクトを始めることにしました。これからOSS活動を始めようと考えている方々にとって、私の経験が少しでも参考になれば幸いです。
OpenID Connect のごく簡単な説明
OpenID ConnectはOAuth 2.0フレームワーク(IETF RFC 6749および6750)に基づいた相互運用可能な認証プロトコルであり、認可サーバーによって実行される認証に基づいてユーザーのアイデンティティを検証し、RESTライクな方法でユーザープロフィール情報を取得する手段を提供します。
さらに深くOIDCについて知りたい方は次の資料を参照してみてください。
最終的な成果物のイメージを持つ
プロジェクトの具体的な目標は、Go言語を使用してOpenID ConnectのAuthorization Codeフローを実装することでした。この実装を通じて、OpenID ConnectとGo言語によるWebアプリケーション開発に対する理解を深めることを目指しました。当初はImplicitフローやHybridフローの実装も検討していましたが、時間的な制約から断念しました。
退勤後や休日に少しずつ時間を確保して開発を進めるにあたっては、限られた時間をどのように使うかを常に考えることが、目標を達成する上でも、モチベーションを維持する上でも重要でした。
開発環境を整える
定番のツール
このあたりは日常的に使用している方も多いのではないでしょうか。
macOS
Webサーバーとして大きなシェアを占めるLinuxと使用感が似ており、個人的には使いやすいため好んで利用しています。
GoLand
有料のIDEですが、Go言語による開発に必要な機能が一通り揃っています。高度な補完機能や静的解析機能が非常に役立ちます。無料のものだとVisual Studio Codeが有名ですね。
Docker
開発環境の標準化には欠かせないツールです。セットアップの容易さやポータビリティなど、多くのメリットを提供してくれます。
CI環境
GitHubを以前から利用していた関係で、自然とGitHub Actionsを使うようになりました。GitHub Marketplaceからさまざまなアクション(プラグインのようなもの)を入手し、CIに組み込むことも可能です。
プロジェクト固有のツール
今回はOpenAPIを利用してGoのコードを自動生成していたため、openapi-codegenというモジュールを使用しました。さまざまなWebフレームワーク向けにコードを生成でき、go-oidc-exptで採用したchiというルーターにも対応していたため、大変助かりました。
また、一時期はlimaというVMを利用し、Dockerの実行環境として活用していました。しかし、スリープから復帰した際にCPU使用率が100%になってしまうことが稀にあり、最終的には使用を停止しています。Docker Desktop for Macと比較すると、セットアップもやや煩雑でした。
開発プロセスの流れと課題
プロジェクトでは必要最小限の機能を選定して実装を進めていきました。このセクションでは開発過程を時系列に沿って紹介します。
実装する機能を決める
「何をどこまで実装するか」というスコープを定義するのは難しく、特に余暇を活用して開発を進める場合、スコープが広すぎると開発が終わらず、モチベーションが尽きて中途半端に投げ出してしまう危険性がありました。さらに、OpenID Connectの仕様は比較的シンプルであるものの、機能を一通り実装しようとすると膨大な時間が必要となります。こうした課題に対応するため、実装範囲をAuthorization Codeフローに限定し、ImplicitフローやHybridフローなどは時間的制約から今回は見送ることとしました。
ツールを選定する
開発効率やプロダクト品質の向上を目的として、以下のツールを導入しました。
コマンドラインツール
golangci-lint
ソースコードの静的解析ツールです。複数のリンターが含まれているので、Goでの開発に慣れてきたら必要なものだけ有効にするといったカスタマイズをしてみるのも良さそうです。
vacuum
OpenAPI仕様に基づいたAPI定義の静的解析ツールです。API仕様が仕様に準拠しているかチェックします。
air
アプリケーションのライブリロードを行うツールです。コードの変更を検知して、自動的にビルドを行います。
oapi-codegen
API定義からコードを自動生成するツールです。
Go パッケージ
chi
軽量で高速なHTTPルーターです。標準パッケージのnet/httpと100%の互換性があります。
ent
エンティティフレームワークです。スキーマからコードを自動生成できます。ORMやスキーママイグレーションにも対応しています。
gomock
モック生成ツールです。インターフェースに基づき、ユニットテストで使用するモックを生成します。
zerolog
高性能なロギングライブラリです。JSON形式のログを高速に出力します。
その他
MySQL
リレーショナルデータベース管理システムです。go-oidc-exptではユーザーやRelying Partyなどの情報を保存しています。
Redis
高速なインメモリデータベースです。go-oidc-exptではセッション管理に使用しています。
アプリケーションの基本機能を準備する
ツールの選定が終わったら、アプリケーションの下地を整えていきます。
環境変数から設定を読み取る
Beyond the Twelve-Factor App[1]の「5. Configuration, Credentials, and Code」でも語られているように、設定はアプリケーションの外部に持ちます。go-oidc-exptではenvというパッケージを使用して環境変数を読み取ります。envを使うと次のとおり各環境変数にデフォルト値を設定でき、環境変数が定義されていない場合はそれらが使われます。
ログを出力できるようにする
go-oidc-exptではアプリケーション起動時の初期化処理でロガーのインスタンスを作成し、それを再利用する方式を採っています。ログは運用やデバッグに不可欠なので、プロジェクトの早い段階でログ出力の仕組みを整備しておくと安心です。
データベースを操作できるようにする
entを使用して最小限のスキーマ定義やマイグレーションツールを作成します。マイグレーションツールはmakeコマンドで実行できるようにしておくのがおすすめです。これにより、毎回コマンドのオプションを調べ直す手間が省けますし、誰が実行しても同じように動作します。
テスト環境を整える
アプリケーションのテストを実行できるようにしておきます。機能追加やコード修正をしても、テストが通ることで動作保証につながります。GitHub Actionsなどを使ってPushやMergeのタイミングでテストが実行されるようにしておくのがベストです。
ひととおり実装してからだと、テストを記述するモチベーションが下がってしまうことが多いため、開発初期からテストを記述するのが理想です。加えて、カバレッジレポートも出力できるようにしておきます。Visual Studio CodeやGoLandなどを使っている場合、IDE側にカバレッジが反映されるためテスト結果を視覚的に把握することも可能です。
プロファイラーを導入する
Goには「pprof」と呼ばれるプロファイラーが用意されており、CPUやメモリの使用状況を詳細に把握できます。これにより、処理のボトルネックを特定したり、メモリリークが疑われる箇所を調査したりと、パフォーマンス面の問題を効率よく洗い出すことが可能です。また、「継続的プロファイリング」という考え方もあり、開発環境だけでなく、本番環境でもプロファイリングを実施するケースが増えているようです。実際のユーザー負荷がかかった状況でデータを収集できるため、より正確な分析が行え、障害時の迅速な原因特定にも貢献します。
参考資料
Makefileに必要なタスクを定義する
ビルドや静的解析を行うタスクをMakefileに予め定義しておきます。なお、go-oidc-exptでは以下のとおり.PHONYを自動生成したり、Makefile内のコメントからヘルプを生成する仕組みを導入しています。
Authorization Codeフローの実装
ようやくアプリケーション開発の準備が整いました。ここからはAutorhization Codeフローを実装していきます。
仕様を入手する
OpenID Foundationが公開している仕様を元に実装を進めていきます。ドキュメントを入手するために、まずは「What are OpenID Specifications」にアクセスします。続いて同ページの「OpenID Connect specifications」を開き、「OpenID Connect Core」にアクセスすると「OpenID Connect Core 1.0 incorporating errata set 1」というドキュメントを参照できます。go-oidc-exptではこれを元にAutorhization Codeフローを実装しました。なお、日本語版の資料はこちらから入手できます。
API定義の作成
go-oidc-exptではAPI定義をOpenAPI仕様に従って作成しました。この仕様からドキュメントやソースコードを自動生成できるため非常に便利です。
ファイル形式にはYAMLを採用しました。JSON形式も選択肢として存在しますが、YAMLの方が簡素で読みやすいため、好んで使っています。次のコードはAPI定義の一部です。
また、作成したAPI定義の品質を保つため、前述の「vacuum」というツールを使用します。このツールの実行をCIに組み込んでしまうのがおすすめです。
各種エンドポイント用のハンドラーを実装する
続いてエンドポイント用のハンドラーを実装していきます。
まずはAPI仕様からハンドラーのインターフェースを生成します。これには前述の「oapi-codegen」を利用します。oapi-codegenはOpenAPIファイルを解析し、アプリケーションが必要とするソースコードを自動生成するツールです。このツールはechoやchiなどのフレームワークをネイティブにサポートしています。go-oidc-exptではchiを採用していたため、これに適合するコードを生成してくれる点はとても便利でした。なお、CookieやクエリパラメーターなどをパースするコードもAPI定義から生成できます。自前でテンプレートを用意して出力内容をカスタマイズすることも可能です。次のようにGoテンプレート形式を使えます。
なお、oapi-codegenコマンドは次のようにディレクティブコメントに記載しておくと便利です。
あとはプロジェクトのルートで go generate ./...
を実行すれば他の生成系コマンドと併せて実行できます。make gen
のような形で実行できるようにしておくと便利ですね。
コンフォーマンステストを実施する
アプリケーションの実装がひととおり終わったら、OpenID Connectの仕様通りに動作しているかを確認するためにコンフォーマンステストを実施します。OpenID Foundationがテストを支援するツールを提供しているので、これを使います。使い方やセットアップ手順、利用方法の詳細は、次のドキュメントに記載されています。
テストを実行すると次のようにサマリが出力されます(ファイル全体はこちら)。
エラーとなった場合には理由も併記されるので、その内容に従って実装を見直します。
その他、開発中にやっておくと良いこと
ドキュメントを作成する
開発を円滑に進め、後々のメンテナンスを容易にするためには、適切なドキュメンテーションが重要です。
README.md
リポジトリのREADME.mdにリポジトリの概要、環境構築手順、アプリケーションの使い方などをまとめておきます。これにより、時間を置いて作業を再開する際にもスムーズに着手できますし、誰かの目に止まった際にも「リポジトリが何を提供しているのか」が伝わりやすくなります。
シーケンス図
また、Mermaidを用いたシーケンス図の作成も有効です。開発を始めて数ヶ月も経つとプログラムがどのようなフローで動いているかを思い出すのが難しくなりがちです。シーケンス図があると処理の流れを一目で把握できるため、保守・改善作業が楽になります。さらに、GitHub上でプレビューできる点も大きな利点です。以下はgo-oidc-exptのシーケンス図の一部です(全体図はこちら)。
ADR
加えて、ADR(Architectural Decision Record)を作成することで、アーキテクチャに関わる重要な意思決定や、その背景・理由をドキュメントとして残せます。後から「なぜこの実装を選んだのか」といった疑問が生じた場合にも、ADRがあれば判断根拠を振り返ることができます。このようなドキュメンテーションが充実していると、プロジェクトの保守性が向上します。
参考資料
まとめ
本記事ではOpenID ConnectをGo言語で実装する過程を紹介しました。OSS開発をこれから始めようと考えている方にとって一番のハードルは「どこから手をつければいいのか」という点かもしれません。そのような場合、最初は小さな機能やシンプルな目標から始めてみるのがおすすめです。まずは最低限動くものを目標に据え、そこから少しずつ拡張していく中で技術的な課題などのトラブルにも自然と直面すると思います。そのたびに問題点を整理し、ADRで意思決定の経緯を残せば、将来の自分や同じ課題に挑んでいる方を助けることにも繋がると思います。
重要なのはすべてを一度に完璧にこなそうとせず、「実際に動くもの」を手元で作りながら経験を積むことだと思います。そうすれば、小さな成功体験を重ねることで自信をつけ、徐々にスキルを広げていくことができます。類似のOSSのコードを読み、実装の参考にするのも良いと思います。
最後までお読みいただき、誠にありがとうございました。今回紹介した内容が、あなたのOSS活動にとって有益なものであれば幸いです。コツコツと積み重ねた経験が、技術力やキャリアにおいて大きな財産になると思います。OSS開発の世界を楽しみながら、ぜひ一歩ずつ前に進んでいただければと思います。
-
本文中のリンクからPDFファイルを入手できます。 ↩︎
Discussion