2019-12-20に更新

MinQ開発日記 (6) ローカルのGoの開発環境の構築とスキーマ・マイグレーション

Echoによるエコー・サーバー

とりあえずEchoサーバーを立てる. またローカルに開発環境を構築していなかったことを思い出す. やり方はMinQ開発日記 (3) Docker上にGo開発環境を構築と同じと思ったのですがGo Modulesというのがあるようです.

構成

  • Echo (Webサーバー兼アプリケーション)
  • MariaDB Docker Container
  • React (認証画面とCRUDインターフェース)

goenvとGo Modules

Goのバージョン1.13以降は何もしなくてもGo Modulesが使えるようです.

その前にGoのバージョン管理ができるようにしましょう. pyenvやnodeenvのようにgoenvというのがあります. 公式のInstallationガイドを読むと大体わかります. GOENV_ROOTがgoenvバイナリのロケーションです.

export GOENV_ROOT="$HOME/.goenv"
export PATH="$GOENV_ROOT/bin:$PATH"
eval "$(goenv init -)"
export GOPATH="$HOME/go"
export PATH="$PATH:$GOPATH/bin"

goenvでインストール可能なバージョンを列挙するには以下のようにします.

goenv install -

バージョンがずらっと並ぶので, この中から一つ選んでインストールします. 最新版(2019/12/14時点)である1.13.4をインストールします.

goenv install 1.13.4
goenv global 1.13.4

これでgoのバージョンは1.13.4です. Go Modulesは好きなところにフォルダを作れます.

cd ~/your_folder
mkdir minq && cd minq
go mod init

これでgo.modというファイルができました. 適当にserver.goというファイルを作ります(多分main.goの方が良い気もする).

package main

import (
    "net/http"

    "github.com/labstack/echo"
)

func main() {
    e := echo.New()
    e.GET("/", func(c echo.Context) error {
        return c.String(http.StatusOK, "Hello, World!")
    })
    e.Logger.Fatal(e.Start(":1323"))
}

minqフォルダ内部で以下を実行すると必要なパッケージ(例えばecho)なんかを勝手にダウンロードしてきて, ビルドしてくれます.

go build

minqフォルダの生成物はフォルダ名と同じになるのでこの場合はminqです.

./minq

サーバーが起動したらオッケーです. 特別依存性を指定しなくてもimportから解決してくれるっぽいのは良いですが, 反面githubのレポジトリを直接指定する必要があるのは少し面倒でもあります.

MariaDB

brewで入れます.

brew install mariadb

起動もbrewを使います.

brew services start mariadb

起動したデータベースへアクセスします.

sudo mariadb -uroot

GORMで必要になる情報

GORMからMariaDBにアクセスするには以下の情報が必要になります.

  • username
  • password
  • IPアドレス/ホスト名
  • ポート番号
  • データベース名

username & password

上の例でmysqlというデータベースがあると思います. このデータベースにuserというテーブルがあります.

SELECT user FROM mysql.user;

名前が被らないように, ここに新しいユーザーを登録します. CREATE USERを使います.

CREATE USER 'new_name'@'localhost' IDENTIFIED BY 'your_password';

new_nameというユーザーにyour_passwordでアクセスできます. ホストはlocalhostです.

ポート番号

システム変数を指定すると以下のように表示できるようです.

show variables like 'port';

SHOW VARIABLES
port

ちなみに私の環境では3306でした(多分誰でも同じ).

データベース名

MariaDBがアクセスしたユーザー用に管理してるデータベースを表示するのは簡単です.

SHOW DATABASES;

testというテスト用のデータベースが存在するはずです. とりあえずこれを使います.

USE test;

これでデータベースがtestに切り替わります. 後でGOMRでちゃんとスキーマが設定できたかを確認しましょう.

GORM

GORMでtestデータベースにアクセスしてみましょう. ハンドラを作っておきます. ステータス・コードなんかは現状適当です(DBサーバーでエラーが出たらどうしたら良いのかわからないので).

func accessDB(context echo.Context) error {
    db, err := gorm.Open("mysql", "root:@(127.0.0.1:3306)/test?charset=utf8&parseTime=true")
    defer db.Close()

    if err != nil {
        return context.String(http.StatusOK, err.Error())
    }

    return context.String(http.StatusOK, "Connect to DB")
}

この時点では以下のようなエラーが出力されます. これはrootユーザーでアクセスする場合sudoer出ないと実行できないからのようです.

Error 1698: Access denied for user 'root'@'localhost'

Connect to mysql server without sudo

ここで上で作ったnew_nameユーザーを使いましょう(名前は適宜読み替えてください). testというデータベースがデフォルトで存在するはずなのでそれを指定しましょう.

db, err := gorm.Open("mysql", "new_name:@(localhost:3306)/test?charset=utf8&parseTime=true")

該当箇所を変更してビルド&リスタートするとConnect to DBと表示されるはずです(末尾のクエリ文字列的なのは一旦無視します).

e.GET("/v1/example/mariadb", accessDB)

スキーマ・マイグレーション

データベースのスキーマの更新を行う作業をスキーマ・マイグレーション, あるいは単にマイグレーションと言うようです. ORM(Object Relational Mapping/Mapper)ではオブジェクト(Goの構造体)のフィールドからスキーマを決めるようです. つまり個々のテーブル上のレコードと構造体のインスタンスが対応関係にあるわけです.

type User struct {
    ID   int
    Name string
}

テーブル名は自動的に雛形となる構造体名の複数形になります. この場合だとusersというテーブルがtestデータベースに追加されます.


if db.HasTable("users") { return context.JSON(http.StatusCreated, "User table is already existed") } db.AutoMigrate(&User{}) return context.JSON(http.StatusCreated, "Create users table")

適当なハンドラ関数を作って, エンドポイントに紐付けます. MariaDBに戻って以下を実行します.

SHOW COLUMNS FROM users;

これでusersの構造が表示されるはずです.

まとめ

EchoからGORMを通じてMariaDBにアクセスすることができました. GORMのチュートリアルでは利用するデータベースの操作には慣れている前提なのか少しつまづきました.

今後

React

データベースを管理するCRUD用のインターフェースを作ります. formikDraft.jsを使います. これはできたらPWA(Chrome Desktop)にしてネイティブ・アプリのように実行できるようにしたいです. 詳細に踏み込みたくないのでcreate-react-appを使います. またテストにはCypress.jsを使ってみたいです.

認証

現状他の人は使わない前提なので, 実験的にWebAuthenticationを使おうと思っています.

Gitの導入

プロジェクトはバージョン管理をするのですが, このままだとgom.Openに指定したデータベースのパスワードが丸見えです. 色々方法があるようです. 最初はコマンドライン引数で渡せばいいのかと思ったのですが, Stackoverflowで質問してみるとダメだそうです. コマンドラインにセンシティブなデータを渡すことがそもそもタブーのようです.

What is a standard way to specify password for MariaDB in an API server?

じゃあどうするのか?

まずそもそも構成ファイルをgitの管理下に置かないという手があります(.gitignore). あるいは構成ファイルに.exampleのような拡張子を付け適当なパスワードを入れて本番用とは切り分けるやり方です.

What is the best practice for dealing with passwords in git repositories?

ただこの方法はどちらかというとプロジェクト・テンプレートを公開するような用途に使った方がいい気もします. どちらの方法も認証情報は別の方法で管理する必要があります.

もう一つはVaultというソフトウェアを使う方法ですが, こちらはかなり大げさなようです.

4 secrets management tools for Git encryption

今回はgit-cryptgit-secretというのがあるようでこの辺を調べてみようと思います.

STORING ENCRYPTED CREDENTIALS IN GIT
本番用の.envを外部に一切知られずに安全にgithubで保存する方法
Gitリポジトリ暗号化のススメ - git-secret -
Git

Reference

【Go】goenvを使ってGo1.13.4の環境構築
Go Modulesも触れてみるGo入門
Go言語のGORMを使ってみた①
Golang + GORM + MySQL でデータをやりとりする

ツイッターでシェア
みんなに共有、忘れないようにメモ

ブレイン

Androidアプリ開発者を目指しています. 興味あることリスト: https://t.co/ew3bb6grdJ Github: https://t.co/9btqysHqWr Qiita: https://t.co/ZVRhjouauX

Crieitは個人で開発中です。 興味がある方は是非記事の投稿をお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください!
ボードとは?

関連記事

コメント