- 伊藤真彦
- 約 2,600 文字
- 500 View
TIGの伊藤真彦です。
渋川さんが投稿された
やりたいこと
私の最近の仕事はgo-swaggerによるバックエンドAPI開発です。本流はバックエンドですが、必要に応じてクラウドインフラを弄ったり、ちょっとしたフロントエンドアプリケーションを作ったりといった動き方で働いています。
ある時、go-swaggerで作ったバックエンドAPIの資産を使って、ちょっとした開発者向けアプリケーションを作りたくなりました。
ローカル環境でサーバーとフロントエンドアプリケーションを両方起動すると、サーバーがlocalhost:3000 フロントエンドがlocalhost:8080を占拠してしまいます。また、フロントエンドとバックエンドのポートが異なることにより、フロントエンドからのリクエストを処理するためにはCORSの設定が必要になってしまいます。そして単純に2つのアプリケーションを起動するのが面倒だなと感じました。
そこで、go-swaggerが生成するものをハックして、フロントエンドの成果物と今まで作ってきたバックエンドAPIを同じポートで抱えつつ、APIを叩くクライアントサイドのコードもswagger.yamlから自動生成するようなアプリケーション開発に挑戦してみました。
バックエンド開発
説明のために、まずはバックエンドの資産を作ります。
詳しい作り方、説明はgo-swaggerでhello worldをお読みください。
今回はこのようなディレクトリ構成でアプリケーションを作ります。
swagger.yaml |
|
上記記事と同じswagger.yamlを用意して、serverパッケージを生成します。
swagger generate server -a factory -A factory -t server/gen -f ./ swagger.yaml |
get_greeting_handler.goはログ出力だけ少し追加しました。
func GetGreeting(p factory.GetGreetingParams) middleware.Responder { |
localhost:3000/helloでapiが動くことを確認します。
cd server |
フロンエンド開発
バックエンドの用意ができたら、上記のAPIを叩くためのサンプルアプリケーションを作ります。
まずはVue.jsでのHello Worldアプリケーションを生成します。
こちらも過去記事Electronの使い方 Web開発の技術でデスクトップアプリを作ろうで詳しく説明しています。
フロントエンドアプリケーションを下記の構成で生成します。
swagger.yaml |
npm install -g @vue/cli |
アプリケーションを起動し、localhost://8080でフロントエンドアプリケーションが起動することを確認します。
cd frontend |
フロントエンドにAPI Clientを実装する
作成したフロントエンドアプリケーション向けに、TypeScriptのAPIクライアントを自動生成します。
cd app |
/frontend/src/client-axiosディレクトリにコードが生成されます。.eslintignoreに追加するなど、linterの設定を適宜追加してエラーが起きないようにすることを推奨します。
生成したコードを利用するようにapp\frontend\src\App.vueを更新します。
<template> |
app\frontend\src\client-axios\base.tsでAPIの接続先を設定できるので、任意のポート、パスに書き換えます。
このままnpm run serveでアプリケーションを起動すると、存在しないAPIにアクセスし、通信に失敗する状態になります。
この状態のアプリケーションを、バックエンドAPIと繋ぎこみます。
フロントエンドとバックエンドAPIを統合する
まずは作成したフロントエンドアプリケーションをビルドし、アセットファイルを準備します。
cd app/frontend |
app\frontend\distディレクトリに成果物一式が生成されます。
生成したファイルをgo:embedで埋め込み起動するようなmain.goを下記の構成で作成します。
swagger.yaml |
package main |
※そのまま使用するとserverパッケージのimportに失敗します、ご自身の環境での適切なパスに指定するか、go.modでreplaceしてください
このファイルは、自動生成されたserver\gen\cmd\factory-server\main.goをベースに拡張したファイルです。
生成したフロントエンドのコードをgo:embedで埋め込みます。
詳しくはGo 1.16からリリースされたgo:embedとはをお読みください。
//go:embed frontend/dist/* |
埋め込んだファイルを利用できるようにHTTPハンドラを設定します。
// serve frontend HTML. |
一方、go-swaggerで生成したバックエンドAPIのロジックはapi/helloのパスでアクセスできるように退避させつつ読み込みます。
// serve swagger api server. |
本来であればserver.Serve()でポート番号の指定などを解釈しつつ、バックエンドAPIが起動するところを、上記の方法でうまく利用できました。
var proxyRegexp = regexp.MustCompile(`^/api`) |
パスにapi/の文字列が存在するとバックエンドAPIが元々期待しているパスパターンと一致しないため、正規表現を用いて除外しています。
余談ですが正規表現のMustCompileは関数内で行うと、呼び出されるたびに毎回コンパイルが走るため、グローバル変数に持たせる事が推奨されています。
アプリケーションの起動
これでアプリケーションが完成しました。
完成したアプリケーションを起動してみます。
cd app |
無事にアプリケーションが起動し、バックエンドAPIからのレスポンスを表示できました。
ブラウザのデバッグコンソールでバックエンドAPIとの疎通に成功している事が確認できます。
あとは出来上がったファイルをgo buildすれば単一バイナリで動くフルスタックWebアプリケーションの完成です。
まとめ
- go-swaggerで生成したバックエンドサーバーと、任意のフロントエンドWebアプリケーションを同じポートで起動することは可能。
- クライアントサイドからバックエンドに繋ぐためのAPIクライアントも自動生成できる。
go:embedを利用することで、単一バイナリとしてビルドすることが可能。
大規模アプリケーションをこの構成で作成するには若干邪道な雰囲気を感じますが、手早くアプリケーションを開発したいGopherのみなさんにおススメの手法でした。