TemPlat Consoleでは、ERDを定義してビルドすることで、ERDで定義したテーブルへのアクセスをするためのRestfulなAPIを自動生成することができます。そしてこれらのAPIは開発者がカスタマイズすることができるので、ここではその例を説明します。
標準APIの仕様に関しては 標準APIの仕様に関して を参考にしてください。
目次
APIカスタマイズのパターン
APIをカスタマイズするにもいくつかパターンがありますので、ここではそのパターン別に説明していきたいと思います。サンプルの説明は順次追加していきます。
カスタマイズパターン | |
GET系で別テーブルのデータを返したい | ・親子テーブルで親テーブルと一緒に子テーブルの一覧も取得したい ・親子テーブルで親テーブルの一覧検索時に子テーブルの項目も一緒に取得したい |
複数のテーブルにまとめて登録したい | ・親テーブルと一緒に子テーブルも登録したい ・複数のテーブルを同期を取って更新したい |
パターン1:GET系で別テーブルのデータを返したい
標準APIはテーブル単位の操作のみとなっていますので、まずカスタマイズが必要になってくるのが、他のテーブルのデータも合わせてAPIで取得するというケースがかと思います。
例を見ながら進めていきたいと思います。例えばERDで他のTableと関連線を引いたようなケースで必要になるかと思います。以下のERDの例は商品(Product)と商品詳細(ProductDetail)をTableとして分けて定義した場合となります。
標準APIの確認
ここで、ProductをID指定でGETする際、標準APIではProductテーブルの該当レコードのみが取得されますが、関連するProductDetailの複数レコードも一緒に取得したいケースもあるかと思います。ここではその例で説明していきます。
ID指定でProductを取得するAPIの自動生成のソースコードは以下のようになっています。
ソースファイルは「/src/app/handler/product_handler.go」です。
func (h *productHandler) getProduct(c echo.Context) error {
id := c.Param("id")
if len(id) == 0 {
result := &model.Response{}
result.Error = constant.InvalidRequestParameters
util.Logger.Error(result.Error)
return c.JSON(http.StatusBadRequest, result)
}
intID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
result := &model.Response{}
result.Error = constant.InvalidRequestParameters
util.Logger.Error(result.Error, err)
return c.JSON(http.StatusBadRequest, result)
}
product, err := h.pr.Get(c, intID)
if err != nil {
result := &model.Response{}
result.Error = constant.DataBaseErrorOnSelect
util.Logger.Error(result.Error, err)
return c.JSON(http.StatusBadRequest, result)
}
if product == nil {
result := &model.Response{}
result.Error = constant.DataBaseErrorNoData
util.Logger.Error(result.Error)
return c.JSON(http.StatusNotFound, result)
}
return c.JSON(http.StatusOK, product)
}
ここではProductテーブルからリクエストで指定されたIDで1レコードを取得して返しています。ここをカスタマイズして、親のProductのレコードに加えて、子供となるProductDetailテーブルの関連するレコードを一緒に返せるようにしたいと思います。
APIのレスポンスデータの追加
カスタマイズとしてはまずAPIのレスポンスデータ(戻り値)を追加します。非標準ではProduct1レコードを返すようになっていますが、それにに加えてProductDetail複数レコードを返せるように拡張します。
以下が標準APIで返しているデータ構造のモデル(product.go)のソースとなります。
ソースパス: /src/app/model/product.go
ここで見て頂きたいのが、テーブルの項目に加えて「ProductEmbed」というデータが含まれています。この「ProductEmbed」を使って、返却死体データを拡張できます。
type Product struct {
Kind string `datastore:"-" boom:"kind,Product" json:"-" example:""`
ID int64 `datastore:"-" boom:"id" json:"id" example:""`
ProductName *string `datastore:"ProductName" json:"productName" example:""`
ProductCode *string `datastore:"ProductCode" json:"productCode" example:""`
Description *string `datastore:"Description" json:"description" example:""`
ProductType *string `datastore:"ProductType" json:"productType" example:""`
ProductEmbed
Entity
}
この「ProductEmbed」は以下のソースに定義されています。
ソースパス: /src/app/model/product_condition.go
このソースは自動生成の上書き対象外となっているので、開発者が修正しても問題ありません。逆に「product.go」の方は基本自動生成で上書きされますので修正しないでください。このあたりの説明は APIカスタマイズと自動生成での上書き禁止設定 にて説明しているので参考にしてください。
では「ProductEmbed」を使って、ProductDeitalを複数返せるようにしましょう。初期のソースは以下のようにtypeのみ定義されており中身は空になっています。
type ProductEmbed struct {
}
ここに追加したいProductDetailを配列(実際はスライス、Slices)を定義します。以下が定義したソースになります。「ProductDetails」という変数名で、型はProductDeitalモデル(ポインター)のスライス型としています。変数名は「パスカルケース(またはアッパーキャメルケース)」で指定してください。
コメントの部分は、データベースである、GCP Datastoreおよび、RESTで返却する際のJSONの項目名との関連を紐付けています。ここではDBアクセスでは使わないので、JSON項目名のみ定義しています。基本は変数名の先頭を小文字にした「キャメルケース」で指定してください。
type ProductEmbed struct {
ProductDetails []*ProductDetail `datastore:"-" json:"productDetails" example:""`
}
標準APIに子テーブルの取得処理を追加する
APIのレスポンスに項目が追加できたら、続いてはその追加項目にProductDetailのデータを詰める処理をHandlerに追加していきます。Handlerも自動生成処理では上書きされないソースファイルなので開発者が修正して問題無いソースです。
ソースの修正は簡単です。APIで受け取ったProductIDを使って、ProductDetailを検索すればいいのです。この検索は「productdeital_handler.go」の「searchProductDeital」関数を参考になります。以下ProductDetailを検索する部分のコードになります。
// Productに紐づくProductDetailを取得する
params := &model.ProductDetailSearchCondition{}
params.ProductID = &intID
productDetails, err := h.rm.ProductDetail.GetAll(c, params)
if err != nil {
result := &model.Response{}
result.Error = constant.DataBaseErrorOnSelect
util.Logger.Error(result.Error, err)
return c.JSON(http.StatusInternalServerError, result)
}
product.ProductDetails = productDetails
検索に使うProductID(int64型)は、intIDという形で既にProductのGETで使われていますので、これをProductDetailの検索用の定義にセットしてあげて、ProductDeitalリポジトリのGetAll()関数を呼んであげるだけです。
Handlerにて他のテーブル用のリポジトリを使用したい場合は「h.rm」からアクセスできます。このrmは「Repository Master」という意味で全テーブルのリポジトリへのアクセスができるようになっています。
一応、関数全体のソースも載せておきます。
func (h *productHandler) getProduct(c echo.Context) error {
id := c.Param("id")
if len(id) == 0 {
result := &model.Response{}
result.Error = constant.InvalidRequestParameters
util.Logger.Error(result.Error)
return c.JSON(http.StatusBadRequest, result)
}
intID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
result := &model.Response{}
result.Error = constant.InvalidRequestParameters
util.Logger.Error(result.Error, err)
return c.JSON(http.StatusBadRequest, result)
}
product, err := h.pr.Get(c, intID)
if err != nil {
result := &model.Response{}
result.Error = constant.DataBaseErrorOnSelect
util.Logger.Error(result.Error, err)
return c.JSON(http.StatusBadRequest, result)
}
if product == nil {
result := &model.Response{}
result.Error = constant.DataBaseErrorNoData
util.Logger.Error(result.Error)
return c.JSON(http.StatusNotFound, result)
}
// ---- ★★★★start customize★★★★ ---- //
// Productに紐づくProductDetailを取得する
params := &model.ProductDetailSearchCondition{}
params.ProductID = &intID
productDetails, err := h.rm.ProductDetail.GetAll(c, params)
if err != nil {
result := &model.Response{}
result.Error = constant.DataBaseErrorOnSelect
util.Logger.Error(result.Error, err)
return c.JSON(http.StatusInternalServerError, result)
}
product.ProductDetails = productDetails
// ---- ★★★★★end customize★★★★★ ---- //
return c.JSON(http.StatusOK, product)
}
ソース修正終了後の進め方
ソースの修正が完了したら、CSR(ソースリポジトリ)に反映させます。ここもgitによる操作としては以下の流れとなります。
① 開発PCのローカルリポジトリに修正をcommitする
基本ブランチはmasterのままで問題ありません。
② GCPのリモートリポジトリ(GSRのこと)へpushする
cloneしてきた時点でリモートリポジトリとしてGSRが登録されていますので、そこへgit pushします。
以下修正後のgitコマンドでの操作サンプルを載せておきます。
まずは、修正後のローカルリポジトリのステータスを確認します。修正した2つのソースが未コミット状態になっています。
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: src/app/handler/product_handler.go
modified: src/app/model/product_condition.go
これらのソースをまずはローカルリポジトリにコミットして、リモートリポジトリへpushします。
$ git commit -M "getProduct時にProductDeitalも取得"
$ git push origin master
ソースがGSRにpushされると、GCP側で自動的に修正されたソースでCloudRun環境へのデプロイを実行してくれます。これはCloudBuildという自動ビルドの設定がされているからです。
ただ、今回はAPIのレスポンスに項目を追加しているので、上記デプロイとは別に、TemPlat Consoleでのサーバビルドを実施しておく必要があります。これはAPIの仕様が変更されているので、TemPlat Consoleが自動生成しているSwaggerファイルや、そのSwaggerファイルから自動生成されるクライアント用のソースコードに変更内容を反映しておきたいためです。
TemPlat Consoleからのビルドは以下から実施できます。手順は TemPlatのビルド方法 を参考にしてください。
Swagger UIでの確認
カスタマイズして、TemPlat Consoleでビルドの完了したら、早速SwaggerUI経由でAPIの確認をしてみましょう。TemPlat Consoleでビルドが完了するとプロジェクトのトップページに画面が戻ります。そこにSwaggerUIページへのリンクがありますので、早速開いてみましょう。

Swagger UIページに移動したら今回カスタマイズした「ProductのgetProduct」を試したいですが、その前にテスト用のデータを登録しておきましょう。SwaggerUIから登録用のAPIも呼び出せます。
確認手順は以下となります。
①Productを1件登録する
②登録したProductに紐づくProductDetailレコードを数件登録する
③ProductのgetProductを呼び出せてProductDetailも合わせて取得できるか確認する
SwaggerUIの基本的な操作に関しては SwaggerUIの使い方 を参考にしてください。

まずはProductの登録からです。bodyとして登録するデータを設定しますが、初期で表示される項目にはTableの全項目と、カスタマイズで追加した項目が表示されますが、以下の項目は登録時には不要なので項目毎消してしまいます。

登録が成功すると、レスポンスにProductの「id」が返ってくるので、それを記録しておいて、ProductDetailの登録で使います。
続いてProductDetailも登録します。要領はProductと一緒です。先程記録したProductIDをここで指定します。

ProductDetailは複数件登録しておきましょう。最後にProductのGETをしてみましょう。

以下のような結果が返ってくれば成功です。
