Go言語製WAF GinでWebアプリを作ってみる【準備編】

こんにちは、エウレカWebエンジニアの北里です。
Go言語始めました。

さて、エウレカは、業務以外にも趣味でGo言語で開発を書いてるエンジニアが多い会社です。素敵な会社ですね。
そんな中、私もGoで何か作りたくなってしまい、こっそりGo開発始めました。
Go言語でWebアプリを開発していく過程をブログで報告していこうと思います。

ちなみに、環境はできていて書きたくてウズウズしている方はこちらの記事がオススメです。

つくるもの

本のレビューを共有するWebアプリを作ってみます。

  • ID/Passwordでログインできる
  • レビュー一覧を閲覧できる
  • レビュー対象の本を登録できる
  • 登録した本に対してコメントを追加できる

以上の機能を実装します。

また、技術要件として、今回はSPA(Single Page Application)の設計で開発します。Goアプリ側では、ユーザの情報などをJSONで返却するAPIを提供し、JavaScript側でユーザデータを扱えるようにします。

今回の記事では、「Go言語のWAFとO/Rマッパーを使って、データを返却するAPIを作る」ところまでを実装します。HTMLのレンダリング、JavaScriptによるフロントエンド実装、ログイン機能の実装、などは次回以降の記事で紹介する予定です。

なぜGinとxormなのか

Go言語のWAFはいくつか開発されているものがあります。例えば、RevelはGo言語の中でも比較的フルスタックなフレームワークとして挙げられます。

今回は、軽いWebサービスをサクッと作ることを目指して、「比較的軽量」かつ「シンプルなインタフェース」を備えたフレームワークを採用したいと思います。

こちらのベンチマークランキングを参考にすると、Ginが他のGoのWAFに比べてレスポンスの速度が早いようです。よって今回はGinを採用します。

xormは以前弊社社内でベンチマークを取った際に比較的早いO/Rマッパーだったのと、社内でも経験者が多く知見が溜まっているO/Rマッパーであることから採用しました。
ちなみに、弊社のpairsでもGinとxormを利用しています。

やること

  1. Goのインストール
  2. GOPATH/GOROOTの設定
  3. Ginのインストール
  4. ブラウザでHello worldを表示
  5. xormを組み合わせて本のレビューサイトを実装する

1.Goのインストール

まずはローカルでGo言語を使えるようにするため、Goをインストールしましょう。
/usr/local/goディレクトリを作成してそこにGoをインストールします。
※Mac OSで開発する場合はbrew install goが早いと思います。

$ wget https://storage.googleapis.com/golang/go1.5.3.darwin-amd64.tar.gz
$ mkdir /usr/local/go
$ tar zxvf go1.5.3.darwin-amd64.tar.gz -C /usr/local/go/
$ mv /usr/local/go/go /usr/local/go/1.5.3
$ ls /usr/local/go/1.5.3

2.GOPATH/GOROOTの設定

さて、GoをインストールしたらまずはGOPATHとGOROOTの設定をしていきます。

GOPATH/GOROOTとは

GOPATHは開発をしているときに、go getコマンドで外部パッケージをインストールしたい任意の場所を指定します。
GOROOTはGoをインストールしたディレクトリを.bash_profileに指定します。

export GOPATH="$HOME/repos/gohome"
export GOROOT="/usr/local/go/1.5.3"
export GOBIN="$GOPATH/bin"
export PATH="$GOBIN:$GOROOT/bin:$PATH"

コマンドで

$ go env

すると以下のように表示されます。

GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/mako/dev/gopath1.5.3"
GORACE=""
GOROOT="/usr/local/go1.5.3"
GOTOOLDIR="/usr/local/go1.5.3/pkg/tool/darwin_amd64"
GO15VENDOREXPERIMENT=""
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fno-common"
CXX="clang++"
CGO_ENABLED="1"
$ source ~/.bash_profile

コマンドを実行したらターミナルを再起動でgoコマンドが使用できるようになります。
ターミナルでgoコマンドを使用してコマンドのヘルプが表示されればOKです!

これでGoの設定が完了です。

3.Ginのインストール

先に述べた通り、今回はWAFとしてGinを使います。
go getでGinをインストールしましょう。

$ go get github.com/gin-gonic/gin

これで完了です。簡単ですね。
念のため、$GOPATH/src/github.com/gin-gonic/gin が存在しているかどうか確認してみてください。

4.ブラウザでHello worldを表示

ブラウザで簡単な文字列を表示させてみます。
今回は、以下の2つが表示されるように実装してみます。
1. http://localhost:8080/ へアクセスすると「Hello world」と表示される。
2. http://localhost:8080/hoge へアクセスすると、「fuga」と表示される。

まずは開発用のディレクトリを作成し、main.goを作成しましょう。

$ cd $GOPATH/src
$ mkdir bookshelf; cd $_ # 開発用のディレクトリを作成
$ touch main.go

Hello world の実装

main.goにGinを使ったサンプルアプリを実装していきます。

package main

import (
    "github.com/gin-gonic/gin"
)
// main ...
func main() {
    r := gin.Default()

    // 1. http://localhost:8080/ へアクセスすると「Hello world」と表示する。
    r.GET("/", func(c *gin.Context) {
        c.String(200, "Hello world")
    })

    //2. http://localhost:8080/hoge へアクセスすると、「fuga」と表示する。
    r.GET("/hoge", func(c *gin.Context) {
        c.String(200, "fuga")
    })
    r.Run(":8080")
}

サーバーの起動

$ go run main.go

問題なければ標準出力に以下のように表示され、サーバープロセスが立ち上がっている状態になります。

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code: gin.SetMode(gin.ReleaseMode)

  [GIN-debug] GET    /                         --> main.func·001 (3 handlers)
  [GIN-debug] GET    /hoge                     --> main.func·002 (3 handlers)
  [GIN-debug] Listening and serving HTTP on :8080

サーバープロセスが立ち上がっている状態で、 http://localhost:8080/ と http://localhost:8080/huga にアクセスし、意図した文字列が表示されたら成功です。

5.xormを組み合わせて本のレビューサイトを実装する

MySQLからuser情報を取得してJSONとしてデータを返却するAPIを実装します。

Databaseの用意

MySQLはlocalhostで起動しており、fuga/passでログインできるとします。
bookshelfデータベースを用意し、そこにuserテーブルを作成します。

CREATE DATABASE bookshelf;
use bookshelf;

CREATE TABLE user (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
nickname varchar(190) NOT NULL COMMENT 'ニックネーム',
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=1002 DEFAULT CHARSET=utf8mb4 COMMENT='テスト';

id:10くらいまでテスト用のデータをいれておきましょう。

model層の実装

DBとの接続処理をmodel層に切り出します。
そこにuser.goファイルを用意しましょう。

$ cd bookshelf
$ mkdir models; cd $_
$ vim user.go

まずは、MySQLとの接続処理を以下のように記述します。
※以降出てくるコードは見易いようにpanicを使用などしています。

package models

import (
    "github.com/go-xorm/xorm"

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

var engine *xorm.Engine


// init ...
func init() {
    var err error
    engine, err = xorm.NewEngine("mysql", "fuga:pass@/bookshelf")
    if err != nil {
        panic(err)
    }
}

_ “github.com/go-sql-driver/mysql”の先頭に付いているアンダースコアは「ブランク識別子」と呼ばれ、importしているpackageの依存関係を解消するためのものです。
また、init関数はuser.goファイルの初期化時に自動的に実行される処理です。MySQLとの接続処理はinit関数内に記述します。

User構造体を定義する

アプリケーション内でUserデータを取り扱うために、User構造体を定義します。
User構造体のフィールドはテーブルのスキーマとほぼ同等になります。


// User is
type User struct {
    ID       int
    Username string
}

このままではJSONのフィールド名がIDとUsernameのままになってしまうので、フィールド名を指定します。
構造体のフィールドとJSON名でマッピングしたい場合は、構造体のフィールドの後ろにアノテーションをつけ、JSONの項目名を指定します。

またJSONと同様にxorm:”‘カラム名'”とアノテーションを入れることで、構造体のフィールドとデータベースのカラムをマッピングできます。


// User is
type User struct {
    ID       int    `json:"id" xorm:"'id'"`
    Username string `json:"name" xorm:"'nickname'"`
}

Modelの設計

Modelの設計は、概ね以下のような流れになります。

  • DBとのやり取りを行うRepository構造体を定義する
  • Repository構造体に「DBからデータを取得する処理」を定義する
  • RepositoryからUser構造体を生成する処理を実装する
// NewUser ...
func NewUser(id int, username string) User {
    return User{
        ID:       id,
        Username: username,
    }
}

// UserRepository is
type UserRepoository struct {
}

// NewUserRepository ...
func NewUserRepository() UserRepository {
    return UserRepository{}
}

// GetByID ...
func (m UserRepository) GetByID(id int) *User {
    var user = User{ID: id}
    has, _ := engine.Get(&user)
    if has {
        return &user
    }

    return nil
}

Controller層の実装

ユーザからのリクエストに応じて出力するデータの整形を行うために、Controller層を実装します。

controllersディレクトリを作成し、その中にuser.goファイルを用意しましょう

$ cd bookshelf
$ mkdir controllers; cd $_
$ vim user.go

ControllerではmodelのNewUserRepositoryからGetByIDの中身を取得して返します。


package controllers

import (
    "bookshelf/models"
)

// User is
type User struct {
}

// NewUser ...
func NewUser() User {
    return User{}
}

// Get ...
func (c User) Get(n int) interface{} {
    repo := models.NewUserRepository()
    user := repo.GetByID(n)
    return user
}

サーバーにリクエストされた値をJSON形式で返却

main.goにhttp://localhost:8080/[user_id]で指定したuser_idユーザー情報を返します。

-
package main

import (
    "bookshelf/controllers"
    "github.com/gin-gonic/gin"
    "reflect"
    "strconv"
)

// main ...
func main() {

func main() {
    router := gin.Default()
    router.GET("/:id", func(c *gin.Context) {
        // Pramを処理する
        n := c.Param("id")
        id, err := strconv.Atoi(n)
        if err != nil {
            c.JSON(400, err)
            return
        }
        if id <= 0 {
            c.JSON(400, gin.H{"status": "id should be bigger than 0"})
            return
        }
        // データを処理する
        ctrl := controllers.NewUser()
        result := ctrl.Get(id)
        if result == nil || reflect.ValueOf(result).IsNil() {
            c.JSON(404, gin.H{})
            return
        }

        c.JSON(200, result)
    })
    router.Run(":8080")
}

ブラウザで確認

ここまで完了したら、サーバーを起動してブラウザで確認します。

$ go run main.go

起動後、http://localhost:8080/1 にアクセスすると、idが1のユーザー情報がJSON形式で返ってきます。

miyako_screen

おわりに

今回はGoでWebアプリを作る準備段階について順を追って書いてみました。
以下に今回のコードが置いてあるので、コードを一気に見たいという方はどうぞ。
https://github.com/kitazato/bookshelf

次回はviewで渡したデータをDBに入れる箇所の実装と、ログインの実装についてのコンテンツを公開します。

  • このエントリーをはてなブックマークに追加

エウレカでは、一緒に働いていただける方を絶賛募集中です。募集中の職種はこちらからご確認ください!皆様のエントリーをお待ちしております!

Recommend

PHPからGoへの大規模マイグレーションの話

入社1ヶ月で感じたエウレカデザイナー3つの強み