1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AzureのVMにGolangとMySQLでバックエンドサービスを作る その2

Last updated at Posted at 2016-12-05

Microsoft AzureのVMにMySQLを入れて、GolangでWebAPIサーバを作りました。
その2ではGolangのソースについて説明します。
サーバ環境の構成、構築についてはその1を参照ください。

ベース

echoフレームワークを使うことも考えたのですが、今回はpottavaさんのマイクロサービス用のソースをベースにしました。

全体的に大きなソースではないので、ざっと読んでいて勉強になります。
最終的な構成は以下のようになりました。
実際の業務に乗せているファイルはいくつか省略しています。
(myprojectの上にsrcがあり、そこに$GOPATH通している。)

myproject
    ├─ main.go
    └─ app
        ├─ config
        │    ├─ config.go
        │    └─ types.go
        ├─ controllers
        │    └─ user.go
        ├─ database
        │    └─ dbaccess.go
        ├─ http
        │    ├─ http.go
        │    └─ restapi.go
        ├─ logs
        │    └─ logger.go
        ├─ misc
        │    └─ functions.go
        └─ models
            └─ user.go

想像はつくと思いますが、自分で書くのは基本的にcontrollersとmodelsです。

リクエストハンドリング

リクエストのハンドリングは、main.goでcontrollersを読み込み、各contorollerのinit()でハンドリングする感じです。

main.go
package main

import (
	"fmt"
	"net/http"

	"myproject/app/config"
	_ "myproject/app/controllers"  // ここでController読み込み
	"myproject/app/logs"
)

func main() {
	// configから設定取得。ポート番号はconfigで管理。
	cfg := config.NewConfig()
	logs.Debug.Print("[config] " + cfg.String())
	logs.Info.Printf("[service] listening on port %v", cfg.Port)
	logs.Fatal.Print(http.ListenAndServe(":"+fmt.Sprint(cfg.Port), nil))
}

コントローラの実装。
ユーザ取得、更新の処理です。

app/controllers/user.go
package controllers

import (
	"io"
	"net/http"
	"net/url"

	util "myproject/app/http"
	"myproject/app/logs"
	"myproject/app/misc"
	"myproject/app/models"
)

func init() {
	// /users/のリクエストをハンドリング
	http.Handle("/users/", util.Chain(util.APIResourceHandler(users{})))
}

type users struct {
	util.APIResourceBase
}

// Get
func (c users) Get(url string, queries url.Values, body io.Reader) (util.APIStatus, interface{}) {
	// retrive a specified user
	if id := url[len("/users/"):]; len(id) != 0 {
		// id指定でユーザを1件取得
		user, found := models.GetUser(id)
		if !found {
			return util.Success(http.StatusOK), models.User{}
		}
		return util.Success(http.StatusOK), user
	}
	// 全ユーザを取得
	users, err := models.GetUsers()
	if err != nil {
		return util.Fail(http.StatusInternalServerError, err.Error()), nil
	}
	return util.Success(http.StatusOK), users
}

// Post
func (c users) Post(url string, queries url.Values, body io.Reader) (util.APIStatus, interface{}) {
	// routing Post but meaning Put
    user := &models.User{}
    if err := misc.ReadMBJSON(body, user, 100); err != nil {
        logs.Error.Printf("Could not decode response body as a json. Error: %v", err)
        return util.Fail(http.StatusInternalServerError, err.Error()), nil
    }

    // ユーザ情報を更新(なぜPutでないかは後述)
    err := user.Update()
    if err != nil {
        return util.Fail(http.StatusInternalServerError, err.Error()), nil
    }

    return util.Success(http.StatusOK), user
}

※更新処理にPUTでなくPOSTを使ってるのは、今回の要件にPepperとの連携があったのですが、どうもPepperからはGETとPOSTのリクエストしか飛ばせないらしいというのが理由です。

※また、ユーザ数が決まっていたので、あらかじめ必要なユーザのidだけをDBに登録した状態からスタートだったので、登録処理が不要でした。

※GET, POSTしか使えないクライアントの場合、リクエストヘッダ「X-HTTP-Method-Override」を使って、PUT, DELETEを区別できるようにするのがよいと思います。

データベースアクセス

モデルの実装の前に、MySQLへの接続方法。

まずはMySQLの接続モジュールをGitHubから go get する。

go get github.com/go-sql-driver/mysql

あとは上記リンク先のREADME通りに実装すればOK。

app/database/dbaccess.go
package database

import (
	"database/sql"

    _ "github.com/go-sql-driver/mysql"
)

const connentInfo string = "user:password@/dbname"

func DbOpen() (*sql.DB, error) {

    db, err := sql.Open("mysql", connentInfo)

    return db, err
}

モデルの実装

  • GetUsers()
    • 全ユーザを取得
  • GetUser(id)
    • idを基にユーザを1件取得
  • Update()
    • リクエストボディの情報を基にユーザ情報更新
app/models/user.go
package models

import (
    dba "myproject/app/database"
    "myproject/app/logs"
)

// User represents user's user
type User struct {
	ID   int `json:"id"`
	Name string `json:"name"`
}

// Users is a type of User slice
type Users []*User

// GetUsers lists all users from MySQL
//  @return users []models.User
func GetUsers() (users *Users, err error) {
    return toUsers()
}

// GetUser retrives a specified user from MySQL
//  @param  id string
//  @return user models.User
func GetUser(id string) (user *User, found bool) {
    return toUser(id)
}

func toUsers() (*Users, error) {
    // MySQL Open
    db, err := dba.DbOpen()
    if err != nil {
        return nil, err
    }
    defer db.Close()

    // Get user information
    rows, err := db.Query("SELECT id, IFNULL(name, '') FROM users")
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    users := Users{}
    for rows.Next() {
        user := User{}
        var id int
        var name string
        if err := rows.Scan(&id, &name); err != nil {
            logs.Fatal.Printf("Fatal: %s", err.Error())
            return nil, err
        }

        user.ID = id
        user.Name = name
        users = append(users, &user)
    }

    return &users, nil
}

func toUser(id string) (*User, bool) {
    // MySQL Open
    db, err := dba.DbOpen()
    if err != nil {
        return nil, false
    }
    defer db.Close()

    // Get user information
    rows, err := db.Query("SELECT id, IFNULL(name, '') FROM users where id = ?", id)
    if err != nil {
        return nil, false
    }
    defer rows.Close()

    user := User{}
    for rows.Next() {
        var id int
        var name string
        if err := rows.Scan(&id, &name); err != nil {
            logs.Fatal.Printf("Fatal: %s", err.Error())
            return nil, false
        }

        user.ID = id
        user.Name = name
    }

    return &user, true
}

// Update update user to MySQL
func (u *User) Update() (error) {
    // MySQL Open
    db, errDb := dba.DbOpen()
    if errDb != nil {
        return errDb
    }
    defer db.Close()

    // update user information
    _, err := db.Exec("UPDATE users SET name = ? WHERE id = ?", u.Name, u.ID)
    if err != nil {
        logs.Fatal.Printf("Fatal: %s", err.Error())
        return err
    }

    return nil
}

実行、確認

以下のコマンドで起動します。

go run main.go

とりあえずリクエストしてみるときは、Chromeのブラウザアプリ「Advanced REST Client」が便利です。

GETするとき
スクリーンショット 2016-11-30 18.02.53.png

POSTするとき
スクリーンショット 2016-11-30 18.06.55.png

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?