Go、PostgreSQL、Google Cloud、CockroachDB を使用した API の構築
Go と PostgreSQL で API を構築し、Google Cloud Run、Cloud Build、Secret Manager、Artifact Registry で CI/CD パイプラインを設定し、Cloud Run インスタンスを CockroachDB に接続しました。
API はゲーム「クライシ コア: ファイナルファンタジー VII」に基づいており、「マテリア フュージョン」をシミュレートします。この記事の対象読者は、API を構築してデプロイする方法を知りたい開発者です。このプロジェクトに取り組んでいる間に学んだこと、うまくいかなかったこと、ゲームのマテリア融合ルールの理解と翻訳について話す別の記事があります (リンクは近日公開予定)。
簡単に参照できるリンク
- GitHub リポジトリと README
- Swagger (OpenAPI) のドキュメントとテスト
- 公共郵便配達員コレクション
- ドメインモデルのソース
APIの目的
3 つのエンドポイント — ヘルスチェック (GET)、全マテリアのリスト (GET)、マテリア融合のシミュレーション (POST)
ドメインモデル
マテリア (単数形と複数形の両方) は、魔法の源として機能するクリスタル オーブです。ゲーム内には 144 種類のマテリアが存在し、大きく「魔法」、「コマンド」、「サポート」、「独立」の 4 つのカテゴリに分類されます。ただし、マテリア融合のルールを理解する目的では、融合動作に基づいて 32 の内部カテゴリ と、それらのカテゴリ内に 8 つのグレード を用意する方が簡単でした (参照を参照) .
マテリアは一定時間使用すると「マスター」になります。ここでは期間は重要ではありません。
最も重要なのは、2 つのマテリアを融合して新しいマテリアを生成できることです。フュージョンを管理するルールは次の影響を受けます:
- どちらかまたは両方のマテリアを習得しているかどうか。
- どのマテリアが最初に来るか (X のように、Y は必ずしも Y X と等しいわけではありません)。
- マテリア内部カテゴリ。
- マテリアグレード
そして、いくつかのルールには 3 レベルの入れ子になった if-else ロジックがあるなど、多くの例外があります。これにより、DB に単純なテーブルを作成してそこに 1000 個のルールを永続化したり、すべてをルール化する 1 つの式を考え出したりする可能性が排除されます。
要するに、次のものが必要です:
- 列 name(string)、materia_type(ENUM) (32 の内部カテゴリ)、grade(integer)、display_materia_type(ENUM) (ゲームで使用される 4 つのカテゴリ)、description(string) および id(整数) を自動インクリメント主キーとして使用します。
- 基本ルール形式 MateriaTypeA MateriaTypeB = MateriaTypeC をカプセル化するデータ構造。
- 基本的かつ複雑なルールを使用して、内部カテゴリとグレードの観点から出力マテリアを決定するコード。
1. ローカル PostgreSQL DB のセットアップ
理想的には、Web サイト自体から DB をインストールできます。ただし、pgAdmin ツールは何らかの理由で DB に接続できなかったので、Homebrew を使用しました。
インストール
brew install postgresql@17
これにより、DB の使用に役立つ一連の CLI バイナリ ファイルがインストールされます。
オプション: /opt/homebrew/opt/postgresql@17/bin を $PATH 変数に追加します。
# create the DB createdb materiafusiondb # step into the DB to perform SQL commands psql materiafusiondb
ユーザーと権限を作成する
-- create an SQL user to be used by the Go server CREATE USER go_client WITH PASSWORD 'xxxxxxxx'; -- The Go server doesn't ever need to add data to the DB. -- So let's give it just read permission. CREATE ROLE readonly_role; GRANT USAGE ON SCHEMA public TO readonly_role; -- This command gives SELECT access to all future created tables. ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly_role; -- If you want to be more strict and give access only to tables that already exist, use this: -- GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_role; GRANT readonly_role TO go_client;
テーブルを作成する
CREATE TYPE display_materia_type AS ENUM ('Magic', 'Command', 'Support', 'Independent'); CREATE TYPE materia_type AS ENUM ('Fire', 'Ice', 'Lightning', 'Restore', 'Full Cure', 'Status Defense', 'Defense', 'Absorb Magic', 'Status Magic', 'Fire & Status', 'Ice & Status', 'Lightning & Status', 'Gravity', 'Ultimate', 'Quick Attack', 'Quick Attack & Status', 'Blade Arts', 'Blade Arts & Status', 'Fire Blade', 'Ice Blade', 'Lightning Blade', 'Absorb Blade', 'Item', 'Punch', 'SP Turbo', 'HP Up', 'AP Up', 'ATK Up', 'VIT Up', 'MAG Up', 'SPR Up', 'Dash', 'Dualcast', 'DMW', 'Libra', 'MP Up', 'Anything'); CREATE TABLE materia ( id integer NOT NULL, name character varying(50) NOT NULL, materia_type materia_type NOT NULL, grade integer NOT NULL, display_materia_type display_materia_type, description text CONSTRAINT materia_pkey PRIMARY KEY (id) ); -- The primary key 'id' should auto-increment by 1 for every row entry. CREATE SEQUENCE materia_id_seq AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; ALTER SEQUENCE materia_id_seq OWNED BY materia.id; ALTER TABLE ONLY materia ALTER COLUMN id SET DEFAULT nextval('materia_id_seq'::REGCLASS);
データを追加する
テーブルヘッダーとデータを含む Excel シートを作成し、CSV ファイルとしてエクスポートします。次に、次のコマンドを実行します:
COPY materia(name,materia_type,grade,display_materia_type,description) FROM '<path_to_csv_file>/materiadata.csv' DELIMITER ',' CSV HEADER;
2. Go サーバーの作成
autostrada.dev を使用して定型コードを作成します。 api、postgresql、httprouter、env var config、tintedlogging、git、live reload、makefile のオプションを追加します。最終的には次のようなファイル構造になります:
? codebase ├─ cmd │ └─ api │ ├─ errors.go │ ├─ handlers.go │ ├─ helpers.go │ ├─ main.go │ ├─ middleware.go │ └─ server.go ├─ internal │ ├─ database --- db.go │ ├─ env --- env.go │ ├─ request --- json.go │ ├─ response --- json.go │ └─ validator │ ├─ helpers.go │ └─ validators.go ├─ go.mod ├─ LICENSE ├─ Makefile ├─ README.md └─ README.html
.env ファイル
ボイラープレート ジェネレーターは、環境変数を取得してコードに追加するコードを作成しましたが、値の追跡と更新を簡単に行うことができます。
/.env ファイルを作成します。次の値を追加します:
HTTP_PORT=4444 DB_DSN=go_client:<password>@localhost:5432/materiafusiondb?sslmode=disable API_TIMEOUT_SECONDS=5 API_CALLS_ALLOWED_PER_SECOND=1
godotenv ライブラリを追加します:
go get github.com/joho/godotenv
以下を main.go に追加します:
// At the beginning of main(): err := godotenv.Load(".env") // Loads environment variables from .env file if err != nil { // This will be true in prod, but that's fine. fmt.Println("Error loading .env file") } // Modify config struct: type config struct { baseURL string db struct { dsn string } httpPort int apiTimeout int apiCallsAllowedPerSecond float64 } // Modify run() to use the new values from .env: cfg.httpPort = env.GetInt("HTTP_PORT") cfg.db.dsn = env.GetString("DB_DSN") cfg.apiTimeout = env.GetInt("API_TIMEOUT_SECONDS") cfg.apiCallsAllowedPerSecond = float64(env.GetInt("API_CALLS_ALLOWED_PER_SECOND")) // cfg.baseURL = env.GetString("BASE_URL") - not required
ミドルウェアとルート
ボイラープレートには、パニックから回復するためのミドルウェアがすでに組み込まれています。さらに 3 つを追加します: Content-Type チェック、レート制限、API タイムアウト保護。
料金所ライブラリを追加:
go get github.com/didip/tollbooth
func (app *application) contentTypeCheck(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Content-Type") != "application/json" { app.unsupportedMediaType(w, r) return } next.ServeHTTP(w, r) }) } func (app *application) rateLimiter(next http.Handler) http.Handler { limiter := tollbooth.NewLimiter(app.config.apiCallsAllowedPerSecond, nil) limiter.SetIPLookups([]string{"X-Real-IP", "X-Forwarded-For", "RemoteAddr"}) return tollbooth.LimitHandler(limiter, next) } func (app *application) apiTimeout(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { timeoutDuration := time.Duration(app.config.apiTimeout) * time.Second ctx, cancel := context.WithTimeout(r.Context(), timeoutDuration) defer cancel() r = r.WithContext(ctx) done := make(chan struct{}) go func() { next.ServeHTTP(w, r) close(done) }() select { case <-done: return case <-ctx.Done(): app.gatewayTimeout(w, r) return } }) }
ミドルウェアをルートに追加する必要があります。これらはすべてのルートに追加することも、特定のルートに追加することもできます。この例の場合、Content-Type チェック (つまり、入力ヘッダーに Content-Type: application/json を含めることを義務付ける) は、POST リクエストの場合にのみ必要です。したがって、routes.go を次のように変更します。
func (app *application) routes() http.Handler { mux := httprouter.New() mux.NotFound = http.HandlerFunc(app.notFound) mux.MethodNotAllowed = http.HandlerFunc(app.methodNotAllowed) // Serve the Swagger UI. Uncomment this line later // mux.Handler("GET", "/docs/*any", httpSwagger.WrapHandler) mux.HandlerFunc("GET", "/status", app.status) mux.HandlerFunc("GET", "/materia", app.getAllMateria) // Adding content-type check middleware to only the POST method mux.Handler("POST", "/fusion", app.contentTypeCheck(http.HandlerFunc(app.fuseMateria))) return app.chainMiddlewares(mux) } func (app *application) chainMiddlewares(next http.Handler) http.Handler { middlewares := []func(http.Handler) http.Handler{ app.recoverPanic, app.apiTimeout, app.rateLimiter, } for _, middleware := range middlewares { next = middleware(next) } return next }
エラー処理
ミドルウェア機能を支援するために、次のメソッドを
func (app *application) unsupportedMediaType(w http.ResponseWriter, r *http.Request) { message := fmt.Sprintf("The %s Content-Type is not supported", r.Header.Get("Content-Type")) app.errorMessage(w, r, http.StatusUnsupportedMediaType, message, nil) } func (app *application) gatewayTimeout(w http.ResponseWriter, r *http.Request) { message := "Request timed out" app.errorMessage(w, r, http.StatusGatewayTimeout, message, nil) }
リクエストおよびレスポンス構造ファイル
/api/dtos.go :
package main // MateriaDTO provides Materia details - Name, Description and Type (Magic / Command / Support / Independent) type MateriaDTO struct { Name string `json:"name" example:"Thunder"` Type string `json:"type" example:"Magic"` Description string `json:"description" example:"Shoots lightning forward dealing thunder damage."` } // StatusDTO provides status of the server type StatusDTO struct { Status string `json:"Status" example:"OK"` } // ErrorResponseDTO provides Error message type ErrorResponseDTO struct { Error string `json:"Error" example:"The server encountered a problem and could not process your request"` }
<ルートフォルダ>/api/requests.go :
brew install postgresql@17
生成されたコードからのバリデーターは、後で Fusion エンドポイントの入力フィールドを検証するために使用されます。
組み合わせルールのデータ構造
ファイル
以下を追加します:
# create the DB createdb materiafusiondb # step into the DB to perform SQL commands psql materiafusiondb
32 個の MateriaType の完全なリストはここにあります。
ファイル
以下を追加します:
-- create an SQL user to be used by the Go server CREATE USER go_client WITH PASSWORD 'xxxxxxxx'; -- The Go server doesn't ever need to add data to the DB. -- So let's give it just read permission. CREATE ROLE readonly_role; GRANT USAGE ON SCHEMA public TO readonly_role; -- This command gives SELECT access to all future created tables. ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly_role; -- If you want to be more strict and give access only to tables that already exist, use this: -- GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_role; GRANT readonly_role TO go_client;
ルールの完全なリストはここにあります。
api/handlers.go のマテリアのハンドラー
CREATE TYPE display_materia_type AS ENUM ('Magic', 'Command', 'Support', 'Independent'); CREATE TYPE materia_type AS ENUM ('Fire', 'Ice', 'Lightning', 'Restore', 'Full Cure', 'Status Defense', 'Defense', 'Absorb Magic', 'Status Magic', 'Fire & Status', 'Ice & Status', 'Lightning & Status', 'Gravity', 'Ultimate', 'Quick Attack', 'Quick Attack & Status', 'Blade Arts', 'Blade Arts & Status', 'Fire Blade', 'Ice Blade', 'Lightning Blade', 'Absorb Blade', 'Item', 'Punch', 'SP Turbo', 'HP Up', 'AP Up', 'ATK Up', 'VIT Up', 'MAG Up', 'SPR Up', 'Dash', 'Dualcast', 'DMW', 'Libra', 'MP Up', 'Anything'); CREATE TABLE materia ( id integer NOT NULL, name character varying(50) NOT NULL, materia_type materia_type NOT NULL, grade integer NOT NULL, display_materia_type display_materia_type, description text CONSTRAINT materia_pkey PRIMARY KEY (id) ); -- The primary key 'id' should auto-increment by 1 for every row entry. CREATE SEQUENCE materia_id_seq AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; ALTER SEQUENCE materia_id_seq OWNED BY materia.id; ALTER TABLE ONLY materia ALTER COLUMN id SET DEFAULT nextval('materia_id_seq'::REGCLASS);
サーバー内キャッシュ
次の理由によりサーバー内キャッシュを使用しています:
- DB から取得したデータは変更されません。
- マテリアとフュージョンの両方のエンドポイントで同じデータが使用されます。
main.go を更新します:
COPY materia(name,materia_type,grade,display_materia_type,description) FROM '<path_to_csv_file>/materiadata.csv' DELIMITER ',' CSV HEADER;
api/helpers.go を更新します:
? codebase ├─ cmd │ └─ api │ ├─ errors.go │ ├─ handlers.go │ ├─ helpers.go │ ├─ main.go │ ├─ middleware.go │ └─ server.go ├─ internal │ ├─ database --- db.go │ ├─ env --- env.go │ ├─ request --- json.go │ ├─ response --- json.go │ └─ validator │ ├─ helpers.go │ └─ validators.go ├─ go.mod ├─ LICENSE ├─ Makefile ├─ README.md └─ README.html
api/handlers.go の融合用ハンドラー
HTTP_PORT=4444 DB_DSN=go_client:<password>@localhost:5432/materiafusiondb?sslmode=disable API_TIMEOUT_SECONDS=5 API_CALLS_ALLOWED_PER_SECOND=1
完全なハンドラー コードはここにあります。
Swagger UI と OpenAPI の定義ドキュメント
Swagger ライブラリを追加します:
go get github.com/joho/godotenv
routes.go で Swagger 行のコメントを解除し、インポートを追加します。
// At the beginning of main(): err := godotenv.Load(".env") // Loads environment variables from .env file if err != nil { // This will be true in prod, but that's fine. fmt.Println("Error loading .env file") } // Modify config struct: type config struct { baseURL string db struct { dsn string } httpPort int apiTimeout int apiCallsAllowedPerSecond float64 } // Modify run() to use the new values from .env: cfg.httpPort = env.GetInt("HTTP_PORT") cfg.db.dsn = env.GetString("DB_DSN") cfg.apiTimeout = env.GetInt("API_TIMEOUT_SECONDS") cfg.apiCallsAllowedPerSecond = float64(env.GetInt("API_CALLS_ALLOWED_PER_SECOND")) // cfg.baseURL = env.GetString("BASE_URL") - not required
ハンドラー、DTO、およびモデル ファイルに、Swagger ドキュメントのコメントを追加します。すべてのオプションについては、これを参照してください。
ターミナルで次を実行します:
go get github.com/didip/tollbooth
これにより、Go、JSON、YAML で使用できる定義を含む api/docs フォルダーが作成されます。
テストするには、ローカル サーバーを起動し、http://localhost:4444/docs を開きます。
最終的なフォルダー構造:
func (app *application) contentTypeCheck(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Content-Type") != "application/json" { app.unsupportedMediaType(w, r) return } next.ServeHTTP(w, r) }) } func (app *application) rateLimiter(next http.Handler) http.Handler { limiter := tollbooth.NewLimiter(app.config.apiCallsAllowedPerSecond, nil) limiter.SetIPLookups([]string{"X-Real-IP", "X-Forwarded-For", "RemoteAddr"}) return tollbooth.LimitHandler(limiter, next) } func (app *application) apiTimeout(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { timeoutDuration := time.Duration(app.config.apiTimeout) * time.Second ctx, cancel := context.WithTimeout(r.Context(), timeoutDuration) defer cancel() r = r.WithContext(ctx) done := make(chan struct{}) go func() { next.ServeHTTP(w, r) close(done) }() select { case <-done: return case <-ctx.Done(): app.gatewayTimeout(w, r) return } }) }
3. CockroachDB でのリモート PostgreSQL インスタンスのセットアップ
- ここからの手順を使用してください。
- 証明書を作成した後、プロジェクトに
/certs/root.crt を作成し、そこに証明書を追加します。このファイルは、後の Google Run 設定で参照します。 - 注意! このフォルダーをリモート リポジトリにプッシュすることはしません。 certs/ フォルダーを .gitignore に追加します。必要に応じて、接続をテストするためだけにローカルで証明書を作成します。
- CockroachDB → ダッシュボード → 左側のメニュー → データベースに移動すると、作成した DB が表示されるはずです。
移住
ローカル DB インスタンスから、次のコマンドを実行します。
brew install postgresql@17
ログイン後にコピーログイン後にコピーログイン後にコピー- CockroachDB → 左側のメニュー → Migrations → Add Schema → 取得した SQL ファイルをドラッグします。テーブル データの挿入を除くすべてのステップが実行されます。実行されたステップのリストも表示されます。
- この記事の執筆時点では、CockroachDB の PostgreSQL インスタンスは IMPORT INTO のようなステートメントをサポートしていません。そのため、ローカル SQL ファイルに 270 行の INSERT ステートメントを作成する必要がありました (これは、先ほど取得した pg_dump 出力から導き出すことができます)。
- リモート インスタンスにログインし、SQL ファイルを実行します。
リモート インスタンスへのログイン:
# create the DB createdb materiafusiondb # step into the DB to perform SQL commands psql materiafusiondb
ログイン後にコピーログイン後にコピーログイン後にコピー4. Google Cloud Run インスタンスをデプロイする
- 次のような Dockerfile を作成します。
- Google Cloud Run に移動し、API の新しいプロジェクトを作成します。
- サービスの作成 → リポジトリから継続的にデプロイ → クラウドビルドでセットアップ → リポジトリプロバイダ = Github → リポジトリを選択 → ビルドタイプ = Dockerfile → 保存。
- 認証 = 認証されていない呼び出しを許可します.
- ほとんどのデフォルトはそのままで問題ありません。
- コンテナ → コンテナ ポート = 4444. まで下にスクロールします。
- 変数とシークレット タブを選択し、ローカル .env ファイルにあるものと同じ環境変数を追加します。
値:
- HTTP_PORT = 4444
- DB_DSN =
?sslmode=verify-full&sslrootcert=/app/certs/root.crt - API_TIMEOUT_SECONDS = 5
- API_CALLS_ALLOWED_PER_SECOND = 1
証明書に Google Secret Manager を使用する
パズルの最後のピース。
- Secret Manager を検索 → シークレットを作成 → 名前 = ‘DB_CERT’ → CockroachDB の .crt 証明書をアップロードします。
- Cloud Run → (サービス) → 継続的デプロイの編集 をクリック → 構成 まで下にスクロール → エディタを開きます。
- これを最初のステップとして追加します:
-- create an SQL user to be used by the Go server CREATE USER go_client WITH PASSWORD 'xxxxxxxx'; -- The Go server doesn't ever need to add data to the DB. -- So let's give it just read permission. CREATE ROLE readonly_role; GRANT USAGE ON SCHEMA public TO readonly_role; -- This command gives SELECT access to all future created tables. ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly_role; -- If you want to be more strict and give access only to tables that already exist, use this: -- GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_role; GRANT readonly_role TO go_client;
ログイン後にコピーログイン後にコピーログイン後にコピーこれにより、Cloud Build はビルドの開始前にプロジェクト内にファイル certs/root.crt を作成するため、Github リポジトリにプッシュしていなくても、Dockerfile はそのファイルにアクセスできるようになります。
それで終わりです。コミットをプッシュして、ビルドがトリガーされるかどうかを確認してください。 Cloud Run ダッシュボードには、ホストされている Go サーバーの URL が表示されます。
「なぜ Y ではなく X を実行したのですか?」に関する質問については、これをお読みください。
他に知りたいことや議論したいことがある場合は、ここにアクセスするか、以下にコメントしてください。
以上がGo、PostgreSQL、Google Cloud、CockroachDB を使用した API の構築の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











OpenSSLは、安全な通信で広く使用されているオープンソースライブラリとして、暗号化アルゴリズム、キー、証明書管理機能を提供します。ただし、その歴史的バージョンにはいくつかの既知のセキュリティの脆弱性があり、その一部は非常に有害です。この記事では、Debian SystemsのOpenSSLの共通の脆弱性と対応測定に焦点を当てます。 Debianopensslの既知の脆弱性:OpenSSLは、次のようないくつかの深刻な脆弱性を経験しています。攻撃者は、この脆弱性を、暗号化キーなどを含む、サーバー上の不正な読み取りの敏感な情報に使用できます。

バックエンド学習パス:フロントエンドからバックエンドへの探査の旅は、フロントエンド開発から変わるバックエンド初心者として、すでにNodeJSの基盤を持っています...

Beegoormフレームワークでは、モデルに関連付けられているデータベースを指定する方法は?多くのBEEGOプロジェクトでは、複数のデータベースを同時に操作する必要があります。 Beegoを使用する場合...

Go Crawler Collyのキュースレッドの問題は、Go言語でColly Crawler Libraryを使用する問題を調査します。 �...

Golandのカスタム構造ラベルが表示されない場合はどうすればよいですか?ゴーランドを使用するためにGolandを使用する場合、多くの開発者はカスタム構造タグに遭遇します...

redisstreamを使用してGo言語でメッセージキューを実装する問題は、GO言語とRedisを使用することです...

Go言語での文字列印刷の違い:printlnとstring()関数を使用する効果の違いはGOにあります...
