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

Microsoft AzureのVMにMySQLを入れて、GolangでWebAPIサーバを作りました。
その２ではGolangのソースについて説明します。
サーバ環境の構成、構築については[その１](http://qiita.com/naoki_koreeda/items/8d4f001ef6bf76d4510a)を参照ください。

# ベース
echoフレームワークを使うことも考えたのですが、今回は[pottavaさんのマイクロサービス用のソース](https://github.com/pottava/golang-microservices/tree/master/app-dbio)をベースにしました。

全体的に大きなソースではないので、ざっと読んでいて勉強になります。
最終的な構成は以下のようになりました。
実際の業務に乗せているファイルはいくつか省略しています。
(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()でハンドリングする感じです。

```Go: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))
}
```

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

```Go: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指定でユーザを１件取得
		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の接続モジュール](https://github.com/go-sql-driver/mysql)をGitHubから go get する。

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

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

```Go: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を基にユーザを１件取得
* Update()
    * リクエストボディの情報を基にユーザ情報更新 

```Go: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](https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo)」が便利です。

GETするとき
<img width="997" alt="スクリーンショット 2016-11-30 18.02.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/92538/b8adeee2-f65a-359c-aa95-994cddb20439.png">

POSTするとき
<img width="998" alt="スクリーンショット 2016-11-30 18.06.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/92538/fc82abc5-a4d8-be76-5e80-535cfc2987cb.png">