JSON SchemaからDBにMigrateするツール、migoを作った


# 経緯
弊社の新アプリ開発に際して、JSON Schemaにより仕様を記述し、それらからバックエンド、フロントエンドの両方から使用する方針にした。

特にバックエンドで使用するgolangでは、JSON Schemaからのコードの自動生成をtext/templateライブラリから行う[go-jstmpl](https://github.com/go-jstmpl/go-jstmpl)を弊社CTOの[witz](http://qiita.com/minodisk)が作成し、
これを用いてアプリケーションのcontroller, validater, model, router部分などのgolangのコードをテンプレートを用いて生成している。

この中でgolangのMigration Tool、特に設定記述にJSON Schemaを用いるものが見当たらないので[migo](https://github.com/meta-closure/migo)を作成した。

# migo

## 概要
migoではmigration毎にDBのその時の状態を保持したファイルを作成し、
JSON Schemaファイルの更新時にはその状態ファイルとの差分を見てDBを更新する。

またTerraformのようにplanが可能なので、実際にmigrateする前に動作の確認が可能である。

## Install

```sh:
go get -u github.com/meta-closure/migo
```

でいける。

## 使い方

サンプルのJSON (Hyper) Schemaをまず書いていく

links, definitions以下のテーブルと紐付けたいSchemaにだけtableタグをつけ
table以下のカラムと紐付けたいSchemaにだけcolumnタグをつけていくと、
タグ付けされた項目だけ読み込まれ、DBに反映される

```yaml:schema.yml
title: sample API schema
description: sample API schema
db:
    user: root
    passwd: 
    addr: 127.0.0.1:3306
    dbname: mig

definitions:

...
　他のスキーマの定義
...

    user:
        type: object
        title: User
        description: User
        table: 
            name: users
            primary_key:
                - user_id  // 各keyが指すのはproperty以下のkey（下ならuser_idなど）

        properties:
            user_id:
                column: 
                    name: id
                    type: int
                    not_null: true
                    auto_increment: true
                $ref: '#/definitions/user_id'

            hashed_password:
                column:
                    name: hashed_password
                    type: int
                    not_null: true
                $ref: '#/definitions/password'

            password:
                $ref: '#/definitons/password'

            screen_name:
                column:
                    name: screen_name
                    type: varchar(255)
                $ref: '#/definitions/screen_name'

    post:
        type: object
        title: posts
        description: post
        table:
            name: posts
            primary_key: 
                - post_id
        properties:
            post_id:
                column:
                    not_null: true
                    name: id
                    type: int
                    auto_increment: true
                    foreign_key:
                        target_table: user
                        target_column: user_id
                        name: fk
                $ref: '#/definitions/post_id'
            
            user_id:
                column:
                    name: user_id
                    type: int
                $ref: '#/definitions/post_id'
            
            title:
                column:
                    name: title
                    type: varchar(255)
                $ref: '#/definitions/title'
            body:
                column:
                    name: body
                    type: varchar(255)
                $ref: '#/definitions/body'
    
```

migo initコマンドでdatabase_state.ymlという名前の初期化された状態ファイルが作成されるので、それを指して実行してやればいい

```sh:

migo -y /path/to/schema.yml -s /path/to/database_state.yml run

---------- DATABASE MIGRRATION IS .......


DATABASE CONFIGURE: root@tcp(127.0.0.1:3306)/mig 

OPERATION >>>>>>>>>    ADD TABLE: [users]

OPERATION >>>>>>>>>    ADD TABLE: [posts]

OPERATION >>>>>>>>>    ADD COLUMN TO [users]: [hashed_password]

OPERATION >>>>>>>>>    ADD COLUMN TO [users]: [screen_name]

OPERATION >>>>>>>>>    ADD COLUMN TO [users]: [id]

OPERATION >>>>>>>>>    ADD COLUMN TO [posts]: [body]

OPERATION >>>>>>>>>    ADD COLUMN TO [posts]: [id]

OPERATION >>>>>>>>>    ADD COLUMN TO [posts]: [title]

OPERATION >>>>>>>>>    ADD COLUMN TO [posts]: [user_id]

OPERATION >>>>>>>>>    ADD PRIMARY KEY TO [users]: [[id]]

OPERATION >>>>>>>>>    ADD PRIMARY KEY TO [posts]: [[id]]

OPERATION >>>>>>>>>    ADD FOREIGN KEY TO [posts]: [id] -> [id] IN [users]

>>>>>>>> MIGRATION SUCCEED

```

このときrunではなくplanで実行なしでOperationが見れる
次回以降中間ファイルとの差分を見ながら更新するので、もう一回runをしても

```sh:
migo -y /path/to/schema.yml -s /path/to/database_state.yml run

---------- DATABASE MIGRRATION IS .......


DATABASE CONFIGURE: root@tcp(127.0.0.1:3306)/mig 

>>>>>>>> MIGRATION SUCCEED
```

となり、まともに動いている事がわかる。


## Queryに失敗した時の挙動

内部では各変更をSQLに変換してテーブル操作を行っているが、型変換の失敗など、途中でエラーを起こして止まる事がある。
migoはQueryの失敗時に記録していた今までのテーブル操作から、整合性を保ちながら発行した逆の操作のQueryを発行してテーブル変更を行い、元の状態にリカバリーを行う。


## ロールバックについて

前に実行したJSON Schemaを渡してやればその状態に戻る。