GolangでMVC+Sモデルを用いてGET APIを作成する


はじめに

今回はwebアプリを作成するときの基本パターンMVCモデルにserviceを追加したフォルダ構成で簡単なapiの作成を行いたいと思います。
学校授業のグループ開発演習の時に使用した手法のアウトプットを兼ねています。

実行環境:macOS Mojave ver10.14.4
言語:golang ver1.11.2
フレームワーク:gin-gonic, gorm

フォルダ構成

今回は毎食の献立を出力してくれるwebアプリを作ることを前提に進めます。
今回の演習で自分が担当したのはフロント側から送られてくる値に応じたURLをDBから返すという部分です。
そのためGET APIを作成していきます。

 PBL1/
  ┃ ├ controller/
  ┃     └ base.go
  ┃     └ Recipe.go
  ┃     └ type.go
  ┃ ├ migration/
  ┃     └ main.go
  ┃ ├ model/
  ┃     └ db.go
  ┃     └ type.go
  ┃ ├ router/
  ┃     └ api.go
  ┃     └ router.go
  ┃ ├ seed/
  ┃  ┃  ├ createSeeds/
  ┃  ┃      └ recipe.go
  ┃  main.go
  ┃ ├ service/
  ┃     └ recipe.go
  ┃     └ service.go
 main.go

このような構造にします。

構造体、必要なメソッドを作成する

ひとまずは必要になりそうな構造体を作成しておきましょう。
MVCモデルではmodel/type.goにDBのテーブルデータを記述し、json型のデータはcontroller/type.goに記述します。
今回はcontroller/type.goだけ書いておきます。

controller/type.go
package controller

type Recipe struct {
    URL    string `json:"url"`
}

そして、uint型にDBの値を処理したいので、string型をuint型へキャストするGetUintメソッドを定義しておきます。

GetUintメソッドはcontroller/base.goに記述しておきます。

controller/base.go
package controller

import (
    "strconv"

    "github.com/gin-gonic/gin"
)

func GetUint(c *gin.Context, key string) (uint, error) {
    i, err := strconv.Atoi(c.Param(key))
    return uint(i), err
}

さっそくイジっていく

とりあえずもらってきたい値が必要なので"api.go"にURLを定義したいと思います。

router/router.go
package router

import (
    "github.com/gin-gonic/gin"
    "github.com/PBL1/controller"
)

func apiRouter(api *gin.RouterGroup) {
    api.GET("/recipe/:menu_id", controller.GetRecipeByMenuID)
}

こんな具合で記述しておきます。
GETの引数に,URLとcontrollerの中に後ほど記述するGetRecipeByMenuIDメソッドを指定しておきます。
このメソッドでフロント側から受け取れる準備ができました。
あとはもらった値を処理していきましょう。

次にcontrollerの中に入ってGetRecipeByMenuIDメソッドを記述するとします。

controller/Recipe.go
package controller

import (
    "log"
    "net/http"

    "github.com/PBL1/service"
    "github.com/gin-gonic/gin"
)

func GetRecipeByMenuID(c *gin.Context) {
    var menuID uint
    var err error

    recipe := Recipe{}

    menuID, err = GetUint(c, "menu_id")
    if err != nil {
        log.Println(err)
        c.AbortWithStatus(http.StatusBadRequest)
    }

    recipe, err = service.GetRecipeByMenuID(menuID)
    if err != nil {
        log.Println(err)
        c.AbortWithStatus(http.StatusBadRequest)
    }
    c.JSON(http.StatusOK, recipe)

必要な変数を定義し、Recipe構造体を作成します。

そして、フロント側から送られてくるstringの値をuintにキャストします。

controller/Recipe.go
recipe, err = GetUint(c, "menu_id")

エラー処理で

controller/Recipe.go
if err != nil {
    log.Println(err)
    c.AbortWithStatus(http.StatusBadRequest)
}

としているのは今回IDを受け取り、受けとったIDに紐づいているURLをデータベースから値を引っ張って返したいので、仮にDBから値が見つからなかった場合のエラーとして"StatusBadRequest"を指定しています。
httpステータスコード

次にservice.GetRecipeByMenuIDメソッドを記述していきます。

service/recipe.go
package service

import (
    "github.com/PBL1/model"
)

func GetRecipeByMenuID(menuID uint) (string, error) {
    modelRecipe := model.Recipe{}

    err := db.Where("menu_id = ?", menuID).First(&modelRecipe).Error

    return modelRecipe.URL, err
}

このメソッドで取り出したい値をDBから選んでいます。

routerの設定をしていきます。

router/router.go
package router

import (
    "github.com/gin-gonic/gin"
)

func GetRouter() *gin.Engine {
    r := gin.Default()

    api := r.Group("/api/v1")
    apiRouter(api)

    return r
}

golangでapiを作る際などにはもうお馴染みのコードと言ってもいいんじゃないでしょうか。
詳しくはgin-gonicと調べてみたほうがいいかもしれません。

最後にmain.goでrunしましょう

main.go
package PBL1

import "github.com/PBL1/router"

func main() {
    r := router.GetRouter()
    r.Run(":8080")
}

curlコマンドを叩く

あとはmain.goを実行してcurlコマンドを叩いて実行結果をみてみましょう。
するとGETのレスポンスが受け取れるかと思います。

最後に

まだまだgolangを触り始めたばかりで抜けが多くわかりにくい記事になっているかもしれません。ご了承ください。

ここまで読んでくださってありがとうございます。