Beego入門（モデル定義・データベース接続・マイグレーション）

# はじめに
Beegoの
- モデル定義
- データベース接続
- マイグレーション
のに関する基本的な情報をまとめていきます。

# 対象
- Beegoに入門したい方
- Beegoのモデル定義・データベース接続・マイグレーションなどの基本的な使い方が知りたい方

# 依存関係
- [Go](https://golang.org/) version go1.10.2 darwin/amd64
- [Beego](https://beego.me/) v1.10.0
- [Bee](https://github.com/beego/bee) v1.10.0
- [gom - Go Manager](https://github.com/mattn/gom)

# モデル定義・データベース接続・マイグレーション編
Beegoのコマンドラインツールであるbeeで作成したサンプルapiアプリケーションをベースに解説して行きます。

```bash

bee api {appname}
```
で作成可能です。

モデルを定義することでORMを利用したデータベースの操作を可能にするまでの手順を解説します。
またデータベース接続の設定し、マイグレーションするまでを順に解説していきます。
またマイグレーションの管理方法に関しても触れていこうと思います。

## モデル定義

### 基本的なモデル定義方法
まずはモデルの定義方法に関して解説していきます。
モデルは構造体によって定義されます。

サンプルアプリケーション直下の`models/user.go`を確認してみると、以下の記述が見つかるかと思います。

```go:models/user.go

type User struct {
	Id       string
	Username string
	Password string
	Profile  Profile
}
```
構造体の各フィールド名をスネークケースにしたものが対応するテーブルのカラム名になります。
また各フィールドに指定した型から自動的に対応するテーブルのカラムの型も決定されます。
goでの型がDBでのどの型に対応するかは、[こちら（beego公式ドキュメント）](https://beego.me/docs/mvc/model/models.md#model-fields-mapping-with-database-type)で確認することができます。

### 対応するテーブル名の変更
デフォルトでは構造体の名前をスネークケースにした名称のテーブル名と対応することになります。
もし対応するテーブルを変更したい場合は、構造体に対してポインターレシーバー`TableName()`を定義することで変更することが可能です。

```go:models/user.go
func (u *User) TableName() string {
    return "auth_user"
}
```

### 構造体のフィールドへのタグ付けによるモデルカスタマイズ
構造体のフィールドにメタ情報を付与することができるタグ機能を使うことで、

- フィールドと対応するカラム属性の追加・変更
- リレーションの定義
- JSONなどにシリアライズする際の設定

などなど様々な情報を追加することができます。
例えば次のように`models/user.go`の`type User struct`を変更します。

```go:models/user.go
type User struct {
	Id       int       `orm:"unique" json:"id"`
	Username string    `orm:"default(1)" json:"userName"`
	Password string    `json:"-"`
	Profile  Profile   `orm:"null;rel(one);on_delete(set_null)"`
	Created  time.Time `orm:"auto_now_add;type(datetime)" json:"created"`
}
```
新たに`Created`フィールドを追加し、各種フィールドにはタグを付与しました。
それぞれ

- Id \`orm:"unique" json:"id"\` 
→ idカラムにunique属性追加、jsonにシリアライズ時には"id"というkeyでシリアライズ

- Username \`orm:"default(1)" json:"userName"\`
→ usernameカラムはデフォルト値を1に、jsonシリアライズ時には"userName"というkeyでシリアライズ

- Password \`json:"-"\`
→ jsonシリアライズ時には含めない

- Profile \`orm:"null;rel(one)"\`
→ null値許可、UserとProfileの1対1のリレーション定義

- Created \`orm:"auto_now_add;type(datetime)" json:"created"\`
→ 値の自動追加、対応カラムをdatetimeへ、jsonシリアライズ時には"created"というkeyでシリアライズ

というような意味になります。
また複数のタグを付ける際は`スペース区切り`（\`orm:"unique" json:"id"\`）、1つのタグに複数の意味をもたせたい場合は`;区切り`(\`orm:"null;rel(one)"\`)という記述方式になります。

## データベース接続 + ORMで使用するモデルの登録

### データベース接続 + ORMで使用するモデルの登録に必要な処理
データベースに接続するには、アプリケーションを立ち上げる際にどこかで以下のような処理を実行する必要があります。

```go
import (
  _ "github.com/go-sql-driver/mysql" // 1
  "github.com/astaxie/beego/orm"
)

orm.RegisterDriver("mysql", orm.DRMySQL) // 2

orm.RegisterDataBase(  // 3
  "default", "mysql",
  mysqlUser+":"+mysqlPass+"@"+mysqlHost+"/"+mysqldb+"?charset=utf8"
)

orm.RegisterModel(  // 4 
  new(User),
)
```

順に解説していきます。

1:  **import _"github.com/go-sql-driver/mysql"**

ここでは`_`によるimportステートメントにより、importしてきたpackageのinit関数のみ実行させています。サンプルコードではmysqlドライバーのpackageである`"github.com/go-sql-driver/mysql"`をimportしています。ここでimportするドライバーは使用するDBの種類に応じて変更する必要があります。


2: **orm.RegisterDriver()**

ここではormに対して使用するDB driverを登録しています。
第1引数は登録するdriverに対して与えるエイリアス名、第2引数は登録したいdriverです。


3: **orm.RegisterDataBase()**
ここではormが接続するデータベースがどれなのかを登録しています。
第1引数は登録するデータベースに与えるエイリアス名、第2引数はデータベースの接続に使うdriverをエイリアス名で指定、第3引数はデータベース接続情報を指定します。
またbeegoはアプリケーションを立ち上げる際に、***"default"エイリアスでのデータベース登録が必須です。そのため"default"エイリアスでのデータベース登録が済んでいない場合はアプリケーションをたちあげることができません。***

4: **orm.RegisterModel()**
ここではormとして使いたいModelの登録を行います。定義済みのmodelの中で登録したいmodelの構造体を引数として与えましょう。

### DB接続に必要な情報をconfファイルへ記述
先ほど紹介した処理の**orm.RegisterDataBase()**ではDBの接続情報を渡す必要がありました。
これらの接続情報はハードコーディングするべき内容ではないため`conf/app.conf`へと移行するのが良いでしょう。
以下のように接続に必要な情報を`conf/app.conf`へと記述します。

```conf:conf/app.conf
appname = test-app
httpport = 8000
runmode = dev
autorender = false
copyrequestbody = true
EnableDocs = true
mysqluser = root
mysqlpass = root
mysqlurl = localhost
mysqldb = test_db
```

confの内容は、

```go
beego.AppConfig.String("mysqluser")
```
のように`beego.AppConfig.取得したい値の型(confに記載されているkey)`で取得することができるので、これを利用してDB接続情報を取得するよう実装するのが良いと思われます。
詳細は[こちら](https://beego.me/docs/mvc/controller/config.md)になります。

### どこに処理を記述すべきか
beegoの公式ドキュメントではこれらをどこに記述した方が良いかということは明確に言及されていないように感じます（少なくとも自分がサンプルなどを確認した限りでは）。
そのため基本的には以下のようにmain.goに記述してしまうのが良いのかなと感じています。

```go:main.go
package main

import (
	"test-app/models"
	_ "test-app/routers"

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

func main() {
	setupDB()
	if beego.BConfig.RunMode == "dev" {
		beego.BConfig.WebConfig.DirectoryIndex = true
		beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
	}
	beego.Run()
}

func setupDB() {
	orm.RegisterDriver("mysql", orm.DRMySQL)

	mysqlUser := beego.AppConfig.String("mysqluser")
	mysqlPass := beego.AppConfig.String("mysqlpass")
	mysqlHost := beego.AppConfig.String("mysqlurls")
	mysqldb := beego.AppConfig.String("mysqldb")

    // golang的には、文字列の結合を+で行うのはあまり良くない処理です。。
	orm.RegisterDataBase("default", "mysql", mysqlUser+":"+mysqlPass+"@"+mysqlHost+"/"+mysqldb+"?charset=utf8")

	orm.RegisterModel(
		new(models.User),
	)
}
```

## マイグレーションの実行・管理

### 登録済みモデルの内容を自動でマイグレーション実行

beegoでは登録済みモデルの内容を自動的DBに反映することができます。これを実現する手法は2通りあります。

#### 1. アプリケーション立ち上げ時に自動実行

データベース接続と使用するモデルの登録が完了した後に、`orm.RunSyncdb`メソッドを実行する方法です。

```go:main.go
package main

import (
	"test-app/models"
	_ "test-app/routers"

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

func main() {
	setupDB()
	if beego.BConfig.RunMode == "dev" {
		beego.BConfig.WebConfig.DirectoryIndex = true
		beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
	}
	beego.Run()
}

func setupDB() {
	orm.RegisterDriver("mysql", orm.DRMySQL)

	mysqlUser := beego.AppConfig.String("mysqluser")
	mysqlPass := beego.AppConfig.String("mysqlpass")
	mysqlHost := beego.AppConfig.String("mysqlurls")
	mysqldb := beego.AppConfig.String("mysqldb")

	orm.RegisterDataBase("default", "mysql", mysqlUser+":"+mysqlPass+"@"+mysqlHost+"/"+mysqldb+"?charset=utf8")

	orm.RegisterModel(
		new(models.User),
	)
        
	// この処理を追加！
	err := orm.RunSyncdb("default", false, true)
	if err != nil {
		fmt.Println(err)
	}
}
```

`orm.RunSyncdb`メソッドは以下のようなメソッドです。

- 第1引数には、接続したいデータベースエイリアス名をstring型で指定します。
- 第2引数には、関連テーブルを削除してから作り直すか、削除せずに更新のみするかをbool型で指定します。`false`を指定した場合は、更新のみされるためカラムの削除は行われません。また`false`を指定した場合は、「テーブル・カラムが追加されているかどうか」を判別して、実行される内容が確定します。そのため、***カラムの名前・属性を変更のみした場合は反映されません***。
- 第3引数には、実行した内容(追加したテーブル・カラム名・実行SQL文)を標準出力するかどうか、をbool値で指定します。

#### 2. アプリケーション立ち上げ時に、ormコマンドから実行

アプリケーションを立ち上げる際に、ormコマンドというormパッケージング組み込みコマンドを実行する方法です。
まずアプリケーション立ち上げの際に`orm.RunCommand()`を呼び出す処理を追加します。

```go:main.go
package main

import (
	"test-app/models"
	_ "test-app/routers"

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

func main() {
	setupDB()

	orm.RunCommand() // ここを追加！

	if beego.BConfig.RunMode == "dev" {
		beego.BConfig.WebConfig.DirectoryIndex = true
		beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
	}
	beego.Run()
}
```

そして

```bash
go run main.go orm syncdb
```
を実行します。
これを実行することで、1で紹介した`orm.RunSyncdb`メソッドを呼び出した時と同様の挙動が実現できます。

`orm.RunSyncdb`メソッドで指定できた引数は、オプションとして指定可能です。

- -dbオプション：-db="マイグレーションしたデータベースエイリアス名"を指定。
- -forceオプション：-force=bool、で関連テーブルを削除してから作り直すか、削除せずに更新のみするかを指定。
- -vオプション：-v=bool、で実行した内容(追加したテーブル・カラム名・実行SQL文)を標準出力するかどうかを指定。

### マイグレーションファイルを作成してマイグレーションを実行・管理

beegoはマイグレーション管理のために、マイグレーションファイルを作成・実行することも可能です。

```bash
bee generate migration 作成したいマイグレーション名
```
を実行することで、マイグレーションファイルの雛形を生成することができます。

例えば、usersテーブルのマイグレーションファイルを作成する場合は、

```bash
bee generate migration create_users_table
```

のような名称にして実行するといいでしょう。

生成されたファイルの内容は以下のような形になります。

```go:
package main

import (
	"github.com/astaxie/beego/migration"
)

// DO NOT MODIFY
type CreateUsersTable_20181102_225734 struct {
	migration.Migration
}

// DO NOT MODIFY
func init() {
	m := &CreateUsersTable_20181102_225734{}
	m.Created = "20181102_225734"

	migration.Register("CreateUsersTable_20181102_225734", m)
}

// Run the migrations
func (m *CreateUsersTable_20181102_225734) Up() {
	// use m.SQL("CREATE TABLE ...") to make schema update
}

// Reverse the migrations
func (m *CreateUsersTable_20181102_225734) Down() {
	// use m.SQL("DROP TABLE ...") to reverse schema update

}
```

マイグレーションファイルを完成させるには、`migration.Migration`のレシーバーメソッドであるSQLメソッドを使用します。

- Upメソッド内に、マイグレーション実行時に実行したいSQL文をSQLメソッドで記述
- Downメソッド内に、ロールバック実行時に実行したいSQL文をSQLメソッドで記述

```go:
package main

import (
	"github.com/astaxie/beego/migration"
)

// DO NOT MODIFY
type CreateUsersTable_20181102_225734 struct {
	migration.Migration
}

// DO NOT MODIFY
func init() {
	m := &CreateUsersTable_20181102_225734{}
	m.Created = "20181102_225734"

	migration.Register("CreateUsersTable_20181102_225734", m)
}

// Run the migrations
func (m *CreateUsersTable_20181102_225734) Up() {
	// use m.SQL("CREATE TABLE ...") to make schema update
	m.SQL("CREATE TABLE IF NOT EXISTS `users` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,`email` varchar(255) NOT NULL DEFAULT ''  UNIQUE,`password` varchar(255) NOT NULL DEFAULT '' ,`created` datetime NOT NULL,`updated` datetime NOT NULL) ENGINE=InnoDB;")
}

// Reverse the migrations
func (m *CreateUsersTable_20181102_225734) Down() {
	// use m.SQL("DROP TABLE ...") to reverse schema update
	m.SQL("DROP TABLE `users`")
}
```

# 終わりに
Beegoのモデル定義・データベース接続・マイグレーション管理・実行の基本的な実装方法をまとめました。

モデル定義に関しては、
- goの構造体のタグ機能をうまく利用している
- Djangoライクな定義方法
がとても気に入っています。

データベースの登録や接続周りに関しては、他言語のフルスタックフレームワークと比較して抽象度が低レベルなAPIだなと感じました。
ただその分カスタマイズしやすいですし、他言語のフルスタックフレームワークのような抽象度の高いAPIとしてpackageを分けて切り出してしまうことも可能です。
接続先が増えたり、接続前にやりたい処理がある場合は、packageを分けて切り出しをしてしまってもいいかなという印象です。


最後にマイグレーション周りですが、ここだけはまだまだやりにくくいなーという感じです。
定義済みモデルからマイグレーションファイル自動作成 → マイグレーションファイルを実行
というようなことを実現できたらいいなと思います。