- 投稿日:2020-03-15T22:54:46+09:00
【Golang】Go 1.14での独自パッケージimportでエラーになるとき
数年Goを離れてたら、バージョン1.14になってたのでキャッチアップしなければ(Go 1.10の人)。
go mod
による管理が楽しい。TL;DR
- 各ディレクトリにgo.modを設置
go mod init
をする- local importするため各go.modファイルに
replace
を追加
- 参照階層がネストするときに注意
- 例)main.go → ./api.go → ./domain/models/Sample.go
- mainのgo.modにはreplace文が2行必要になります
- main.goから見たapiの相対パス
- main.goから見たmodelsの相対パス
出てきて困ったエラー
C:\Users\username\go\src\github.com\username\repo>go build ./... go: finding module for package github.com/username/repo/domain/models api\api.go:13:2: no matching versions for query "latest"とか
go: github.com/username/repo/api@v0.0.0-00010101000000-000000000000 requires github.com/username/repo/domain/models@v0.0.0-00010101000000-000000000000: invalid version: unknown revision 000000000000前提
- Windows 10
- VSCodeでGo言語の開発環境を構築する - Qiitaを参考に環境構築
- module-aware mode
- デフォルトでonになるみたいですが、念のため
go env -w GO111MODULE=on
でonに
go env
の結果です。env.logset GO111MODULE=on set GOPATH=C:\Users\username\goディレクトリ構成
%GOPATH%配下である必要はないですが、一応標準的な配置。本記事に関係ないファイルを除外して記載してあります。
tree.log%GOPATH%\src\github.com\username\repo │ main.go │ ├─api │ api.go │ └─domain └─models Sample.go解決手順
./main.go→./api/api.go
main.goのなかで、自作apiパッケージを利用する場合、下記の手順が必要。
- main.goと同階層にあるgo.modにreplace追加
replace github.com/username/repo/api => ./api
- apiの階層にgo.mod作成
cd ./api
go mod init
- main.goにimport
import api "github.com/username/repo/api"
./api/api.go→./domain/models/Sample.go
api.goのなかで、自作modelsパッケージを利用する場合、下記の手順が必要。
- api.goと同階層にあるgo.modにreplace追加
replace github.com/username/repo/domain/models => ../domain/models
- そのgo.modから見ての相対パスなので注意!
- domain/modelsの階層にgo.mod作成
cd ./domain/models
go mod init
- api.goにimport
import models "github.com/username/repo/domain/models"
- main.goと同階層にあるgo.modにもreplace追加
replace github.com/username/repo/domain/models => ./domain/models
mainのgo.mod
go.modmodule github.com/username/repo go 1.14 replace ( github.com/username/repo/api => ./api github.com/username/repo/domain/models => ./domain/models ) require ( ... )apiのgo.mod
go.modmodule github.com/username/repo/api go 1.14 replace github.com/username/repo/domain/models => ../domain/models require ( ... )解決後のgo.mod分布イメージ
tree.log%GOPATH%\src\github.com\username\repo │ go.mod │ go.sum │ main.go │ ├─api │ api.go │ go.mod │ go.sum │ └─domain └─models go.mod Sample.go今回は問題なかったけど
- 将来domain直下に.goファイルが出てくるようになったら、今回と同様の手順で解決できるはず
- domain/repositoriesとか作ってrepositoriesパッケージ使う場合も同じお話
同ジャンルのQiitaの先人たち
- 投稿日:2020-03-15T22:29:37+09:00
interface をなるべく変更しないように gorm を使う
背景
clean architectureでアプリケーションを作っていると db とのデータのやりとりは抽象化しておくことが多いと思います。そのときに定義する interface が findUserByXXX, findByUserZZZ だと他のデータの取り出し方をしたいと思った時に interface を新規に追加しないといけません。メソッドを分けたとしても、似たようなコードがその中に書かれ、コードベースは肥大化していきます。
そこで、クエリビルダっぽいものを作ってなるべくデータの取り出し方を抽象化したメソッド内部でやるのではなく、クエリビルダに任せたいと思いました。
試作的に書いたのでまだ考慮は必要なのですが、簡単なCRUDアプリケーションのときには十分使えそうなので自ら使って改良を加えて行きたいです。内容
db とのやり取りを Repository を使って実装します。
Repository は Query struct を受け取り、struct に設定された条件をデータ取得時に組み立て、クエリを実行します。そのため、Query struct はビジネスロジック側で条件設定を行いRepository側にただ渡すだけにしました。本当は domain や model の層にinterface や User struct を置きたいのですが簡略化して書いています。あくまでサンプルコードです。repo.gotype User struct { ID uint `json:"-" gorm:"primary_key"` CreatedAt time.Time `json:"createdAt" gorm:"index"` UpdatedAt time.Time `json:"updatedAt" gorm:"index"` Email string `json:"email" gorm:"unique_index"` Name string `json:"name" gorm:"name"` } type Repo interface { GetUser(q *Query) (User, error) } type repo struct { db *gorm.DB } func (a *repo) GetUser(q *Query) (User, error) { var u User db := q.build(a.db) err := db.Take(&u).Error return u, err }クエリはこんな風にひとまず書きました。基本的に、Repository側のメソッドで build() を呼んでもらえればあとは実行するだけです。
query.gopackage gormq import ( "time" "github.com/jinzhu/gorm" ) type Query struct { SelectFields []string Conditions []func(db *gorm.DB) *gorm.DB Preloads []func(db *gorm.DB) *gorm.DB Order func(db *gorm.DB) *gorm.DB ForUpdate func(db *gorm.DB) *gorm.DB } func NewQuery(f []string) *Query { if len(f) == 0 { f = []string{"*"} } return &Query{ SelectFields: f, } } func (a *Query) AddWhere(cond string, v interface{}) { a.Conditions = append(a.Conditions, func(db *gorm.DB) *gorm.DB { return db.Where(cond, v) }) } func (a *Query) AddPreload(target string) { a.Preloads = append(a.Preloads, func(db *gorm.DB) *gorm.DB { return db.Preload(target) }) } func (a *Query) AddOr(cond string, v interface{}) { a.Conditions = append(a.Conditions, func(db *gorm.DB) *gorm.DB { return db.Or(cond, v) }) } func (a *Query) SetOrder(cond string) { a.Order = func(db *gorm.DB) *gorm.DB { return db.Order(cond) } } func (a *Query) EnableForUpdate() { a.ForUpdate = func(db *gorm.DB) *gorm.DB { return db.Set("gorm:query_option", "FOR UPDATE") } } func (a *Query) build(db *gorm.DB) *gorm.DB { db = db.Select(a.SelectFields) for _, item := range a.Conditions { db = item(db) } for _, item := range a.Preloads { db = item(db) } if a.Order != nil { db = a.Order(db) } if a.ForUpdate != nil { db = a.ForUpdate(db) } return db }しかし、色々な部分で build() が呼ばれるため小さな変更にも気を使う必要があるかもしれません。そんな時のために、データの取り出し方について最低限テストを書いておくべきかと思います。もちろん、Query 側の SQL 発行のテストもしておきたいですよね。go-sql-mock を使えば簡単にできます。
query_test.gopackage gormq import ( "database/sql/driver" "reflect" "testing" "github.com/DATA-DOG/go-sqlmock" "github.com/jinzhu/gorm" ) func emptyConn() *gorm.DB { db, _, _ := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) gdb, _ := gorm.Open("mysql", db) // gdb.LogMode(true) return gdb } func TestRepo_GetUser(t *testing.T) { type fields struct { db func() *gorm.DB } type args struct { q func() *Query } tests := []struct { name string fields fields args args want User wantErr bool }{ { fields: fields{ db: func() *gorm.DB { db, mock, _ := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) mock.ExpectQuery("SELECT * FROM `users` LIMIT 1").WillReturnRows(sqlmock.NewRows([]string{})) gdb, _ := gorm.Open("mysql", db) return gdb }, }, args: args{ q: func() *Query { q := NewQuery([]string{}) return q }, }, }, { fields: fields{ db: func() *gorm.DB { db, mock, _ := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) mock.ExpectQuery("SELECT * FROM `users` WHERE (name = ?) AND (email = ?) LIMIT 1").WithArgs(driver.Value("test"), driver.Value("test@gmail.com")).WillReturnRows(sqlmock.NewRows([]string{})) gdb, _ := gorm.Open("mysql", db) return gdb }, }, args: args{ q: func() *Query { q := NewQuery([]string{}) q.AddWhere("name = ?", "test") q.AddWhere("email = ?", "test@gmail.com") return q }, }, }, { fields: fields{ db: func() *gorm.DB { db, mock, _ := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) mock.ExpectQuery("SELECT * FROM `users` WHERE (name IN (?,?)) LIMIT 1").WithArgs(driver.Value("1"), driver.Value("2")).WillReturnRows(sqlmock.NewRows([]string{})) gdb, _ := gorm.Open("mysql", db) return gdb }, }, args: args{ q: func() *Query { q := NewQuery([]string{}) q.AddWhere("name IN (?)", []string{"1", "2"}) return q }, }, }, { fields: fields{ db: func() *gorm.DB { db, mock, _ := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) mock.ExpectQuery("SELECT id, name FROM `users` WHERE (name IN (?)) OR (email = ?) LIMIT 1").WithArgs(driver.Value("1"), driver.Value("test@com")).WillReturnRows(sqlmock.NewRows([]string{})) gdb, _ := gorm.Open("mysql", db) return gdb }, }, args: args{ q: func() *Query { q := NewQuery([]string{"id, name"}) q.AddWhere("name IN (?)", []string{"1"}) q.AddOr("email = ?", "test@com") return q }, }, }, { fields: fields{ db: func() *gorm.DB { db, mock, _ := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) mock.ExpectQuery("SELECT id, name FROM `users` LIMIT 1").WillReturnRows(sqlmock.NewRows([]string{})) gdb, _ := gorm.Open("mysql", db) return gdb }, }, args: args{ q: func() *Query { q := NewQuery([]string{"id, name"}) q.AddPreload("Profile") return q }, }, }, { fields: fields{ db: func() *gorm.DB { db, mock, _ := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) mock.ExpectQuery("SELECT id, name FROM `users` ORDER BY id desc LIMIT 1").WillReturnRows(sqlmock.NewRows([]string{})) gdb, _ := gorm.Open("mysql", db) return gdb }, }, args: args{ q: func() *Query { q := NewQuery([]string{"id, name"}) q.SetOrder("id desc") return q }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { a := &Repo{ db: tt.fields.db(), } got, err := a.GetUser(tt.args.q()) if (err != nil) != tt.wantErr { if err != gorm.ErrRecordNotFound { t.Errorf("Repo.GetUser() error = %v, wantErr %v", err, tt.wantErr) return } } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Repo.GetUser() = %v, want %v", got, tt.want) } }) } }参考
- 投稿日:2020-03-15T19:38:04+09:00
Golangでwebアップロードダーを作る
とりあえず作成したので、アップロード
https://github.com/karosuwindam/goupload重要部分を抜き出し
アップロードの関数部分upload.gopackage main import ( "fmt" "net/http" "os" ) func upload(w http.ResponseWriter, r *http.Request) { var data []byte = make([]byte, 1024) var tmplength int64 = 0 var output string urldata := "" searchdata := "" if r.Method == "POST" { file, fileHeader, e := r.FormFile("file") if e != nil { fmt.Fprintf(w, "%v", backHtmlUpload()) return } writefilename := fileHeader.Filename fp, err := os.Create(UPLOAD + "/" + writefilename) if err != nil { } defer fp.Close() defer file.Close() for { n, e := file.Read(data) if n == 0 { break } if e != nil { return } fp.WriteAt(data, tmplength) tmplength += int64(n) } fmt.Printf("POST\n") } else { fmt.Printf("GET\n") } fmt.Fprintf(w, "ファイルアップロード") }サーバのスタート部分
main.gopackage main import ( "net/http" "fmt" ) const Port = "8080" const Ipdata = "" func webstart() { http.HandleFunc("/upload/", upload) http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("./html")))) http.ListenAndServe(Ipdata+":"+Port, nil) } func main() { fmt.Printf("%v:%v Webserver start\n", Ipdata, Port) webstart() }index部分ファイルアップロードはブラウザのPOST命令に任せる
index.html<html> <head> <title>upload</title> </head> <body> <form action="/upload/" method="post" enctype="multipart/form-data"> <input type="file" name="file" id="up_loadfile" multiple="multiple"> <input type="submit" value="send"> </form> </body> </html>メモ書き程度なので、githubのアップロードファイルをビルドした方が多機能かも
- 投稿日:2020-03-15T19:38:04+09:00
Golangでwebアップローダーを作る
とりあえず作成したので、アップロード
https://github.com/karosuwindam/goupload重要部分を抜き出し
アップロードの関数部分upload.gopackage main import ( "fmt" "net/http" "os" ) const UPLOAD = "upload" func upload(w http.ResponseWriter, r *http.Request) { var data []byte = make([]byte, 1024) var tmplength int64 = 0 var output string urldata := "" searchdata := "" if r.Method == "POST" { file, fileHeader, e := r.FormFile("file") if e != nil { fmt.Fprintf(w, "アップロードに失敗しました。") return } writefilename := fileHeader.Filename fp, err := os.Create(UPLOAD + "/" + writefilename) if err != nil { } defer fp.Close() defer file.Close() for { n, e := file.Read(data) if n == 0 { break } if e != nil { return } fp.WriteAt(data, tmplength) tmplength += int64(n) } fmt.Printf("POST\n") } else { fmt.Printf("GET\n") fmt.Fprintf(w, "GET読み込み") return } fmt.Fprintf(w, "ファイルアップロード") }サーバのスタート部分
main.gopackage main import ( "net/http" "fmt" ) const Port = "8080" const Ipdata = "" func webstart() { http.HandleFunc("/upload/", upload) http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("./html")))) http.ListenAndServe(Ipdata+":"+Port, nil) } func main() { fmt.Printf("%v:%v Webserver start\n", Ipdata, Port) webstart() }index部分ファイルアップロードはブラウザのPOST命令に任せる
index.html<html> <head> <title>upload</title> </head> <body> <form action="/upload/" method="post" enctype="multipart/form-data"> <input type="file" name="file" id="up_loadfile" multiple="multiple"> <input type="submit" value="send"> </form> </body> </html>メモ書き程度なので、githubのアップロードファイルをビルドした方が多機能かも
- 投稿日:2020-03-15T18:37:42+09:00
「Azure SDK for Go」とは?
Azure SDK for Goは、マイクロソフト社のパブリッククラウド「Azure」をGoでコーディングして扱うためのSDK(ソフトウェア開発キット)です。
Apache License 2.0ライセンスのオープンソースソフトウェアであり、GitHub上で管理および開発されています。
最初のコミットは2014年8月11日で、2016年、2017年とベータ期間を経て、2018年2月12日のv14.0.0で正式リリースとなりました。
おおよそ毎月末にメジャーバージョンのアップデートが行われます。
現在では、日本語ドキュメントも公開され、サポートされるAzureサービスも増えています。
当時、他のプログラミング言語のSDKやドキュメントが出揃っていた中、Goだけがなかなか正式リリースされずヤキモキしたり、ベータ期間中にストレージ系のパッケージが本家から出たり戻ったりしていたのを眺めていたも良い思い出です。
まずは「Azure/azure-sdk-for-go」の README.md や「Go 開発者向けの Azure」のドキュメントを読み進め、どんなものなのか?何ができるのか?を見て行きます。
- 投稿日:2020-03-15T17:57:53+09:00
コマンドラインから文字列の取得
初めに
Atcoderの文字列取得の定石を貼ります。
コマンドラインから複数行一度に取得
package main import( "fmt" "bufio" "stdin" ) func main(){ lines := getStdin() for k,v := range lines{ fmt.Printf("line[%s]=%s\n",k,v) } } func getStdin() []string { stdin := bufio.NewScanner(os.Stdin) lines := []string{} for stdin.Scan() { if err := stdin.Err(); err != nil { fmt.Fprintln(os.Stderr, err) } lines = append(lines, stdin.Text()) } return lines }実行結果
$go run sample.go a b c d e//コマンドライン入力 f g h i j//コマンドライン入力 line[0]="a b c d e"//出力 line[1]="f g h i j"//出力コマンドラインから得た文字列を空白区切りで返す(string型)
func splitspace(line string) []string { words := strings.Split(line, " ") return words }コマンドラインから得た文字列を空白区切りで返す(int型)
func splitspace(line string) []int { buf := strings.Split(line, " ") num := stringToInt(buf) return t } func stringToInt(i []string) []int { f := make([]int, len(i)) for n := range i { f[n], _ = strconv.Atoi(i[n]) } return f }
- 投稿日:2020-03-15T17:32:58+09:00
(主に)Goの開発環境構築でWindowsユーザがMacに歩み寄る工夫
はじめに
チームの開発環境の構築手順って、みなさまどうしていますか?多くのチームはリポジトリ直下のREADMEや、チームWikiに用意することが多いのではないでしょうか?
その際、そのリポジトリ固有のBuild & Deploy & Release手順は記載することは普通だと思いますが、ここで問題になるのは、開発者の環境がWindows, Macなどで割れている場合です。全員Windows or Macで揃えろよってことですが、今の自分のチームはMac:Windows=7:3くらいです。
Windowsは参画したてのメンバーや、アルバイトer社員の方が多く、事実上スキルが低いメンバーが多いのですが、環境構築手順がMacに比重高めなので結構翻訳が大変な場合があります。ここでなるべくWindowsユーザがMacユーザ用に書かれた環境構築手順書でも対応できるようなTipsをまとめていきたいと思います。
1. 環境構築の設定
WindowsとMacで揺れているところです。
- Windows:
set AWS_REGION=ap-northeast-1
- Mac:
export AWS_REGION=ap-northeast-1
毎回、exportをsetと入力してもらうのは大変ですが、Windowsでも export のコマンドで環境変数を設定できます。
doskey export=set $*上記を実行すると、export を setのエイリアスのように認識してくれます。
これを永続化するためには以下のサイトにあるように、macroファイルを準備し、ショートカットの引数に追加すると良いようです。2. Makeコマンド
特にGoだとMakefileでビルドスクリプトを用意する文化があるので、Windowsユーザはちょっと対応が大変です。Makeコマンド自体は、Windowsでも追加でインストールできるものの、その内部でgrepコマンドなどを利用されたり、shellスクリプトを実行されるとお手上げです。とはいえ、このMakefileをWindows用も用意するのはダブルメンテですし、MacユーザはWindows側の実行テストするのがハードルが高いので、即座に腐っていきそうです。バッドスメルです?。
というわけで、WSL一択になります。この時点で1の環境変数設定もWSL側でやったほうが良いという結論になりがちですが、環境構築手順のレベル感を見てどこからWSLを使うかは個別判断になると思います。
Windows標準のコマンドプロンプトから、
wsl
またはbash
と入力するとコンソールがそのまま切り替わり、exit
を打つとWindows側に戻れるので便利です。ワンラインで処理したい場合は
bash
コマンドだと以下のように実行できます。bash -c "ls -la /c/mnt/Users/laqiiz/go/src | grep github"参考: https://docs.microsoft.com/ja-jp/windows/wsl/interop
というわけで、Makeコマンドを実行したい場合は以下のように実行できます。
bash -c "make"3. GOPATHの設定
2で動けば良いですが、おそらく課題として、内部で
go build
する時にWindows側とWSL側でGOPATH
が異なるのでうまく動かないことも多いと思います。少しややこしいことになりました。対応としてはオススメは Windows側に WSL側のGOPATH
を寄せることです。bash # WSL側へ移動 export GOPATH=/mnt/c/Users/<ユーザ名>/go exit # Windows側へ戻るこれで go build も問題なく動くと思います。
しかし、go generate系も動かない可能性があるので、PATHにも追加します
export PATH=${PATH}:${GOPATH}/binWindows側で
go get -u
などでインストールした実行ファイルは.exe
なのでそのまま使えませんので、WSL側でも実行ファイルを作成する必要がありますが、MakefileにInstall手順も書かれている場合は問題にならないと思います。4. GoアプリのBuild
Goはクロスコンパイルできますので、Windowsでも問題なくLinux用の実行ファイルを作成できます。
しかし、環境手順には以下のようなワンラインで書かれていることも多いのではないでしょうか?ワンラインで書かれて辛いケースGOOS=linux GOARCH=amd64 go build ./cmd/your-app/your-app.goこの場合、Windowsに翻訳すると以下のように分割する必要があります。
Windowsだとこれset GOOS=linux set GOARCH=amd64 go build ./cmd/your-app/your-app.goコレでも良いのですが、このまま
go test
すると内部でbuildされる実行ファイルがWindowsで実行できなくなるため、set GOOS=windows
と設定し直す必要があり非常にノイジーです。というわけで、これもWSL側で実行するのがオススメです。
bash -c "GOOS=linux GOARCH=amd64 go build ./cmd/your-app/your-app.go"4. AWSCLIのための設定
Goのよくある使い所の一つとして、サーバサイドのWebAPI開発があると思います。このときAWSにDeployする方も多いのではないでしょうか? AWSCLIはWindowsでもMacでも公式で準備されているため、何も問題が無いことが多いですがいくつかのAWSコマンド実行する上で、ややこしいことがあります。
AWS CLIの設定
Profileを利用している場合、Windows側とWSL側でダブルメンテになるのは、設定漏れで作業効率を落とす原因になる事が多いので、なるべく避けた方が良いと思います。
例によって、WSL側の設定をWindows側に寄せます。
ln -s /mnt/c/Users/<User-Name>/.aws ~/.awsこれでAWSCLIなどで利用する configやcredentialsをWindows側と共用できるようになりました。もちろん、credentialsは最上位の機密情報ですので、取り扱いはWindows側と同様に注意して取り扱いましょう。
Lambdaのデプロイ
LambdaでGoアプリをデプロイするときは、以下のようにZIP化する必要があります。
LambdaのデプロイGOOS=linux GOARCH=amd64 go build ./cmd/lambda/lambda.go # ZIPファイルを作成 zip -j lambda.zip lambda # Lambdaのコードデプロイ(Lambda関数自体はすでに作成されている前提) aws --profile <YOUR_ENV> lambda update-function-code --function-name <YOUR_LAMBDA_NAME> --zip-file fileb://lambda.zipこの、
zip
コマンドはWindowsに無いのですが、公式にはbuild-lambda-zip
ツールのインストールが推奨されています。https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-go-how-to-create-deployment-package.html
これでももちろん良いですが、微妙にWindows側とコマンドが異なりややこしいです。ここでもWSLの出番となります。
複数ラインの場合はbash -c "コマンド"
と渡すのではなく、bash + exit で切り替えがオススメです。bash # Bashを起動 GOOS=linux GOARCH=amd64 go build ./cmd/lambda/lambda.go zip -j lambda.zip lambda aws --profile <YOUR_ENV> lambda update-function-code --function-name <YOUR_LAMBDA_NAME> --zip-file fileb://lambda.zip exit # (必要に応じて)Windows側へ戻るDocker
DockerもVolume周りの設定をされていると、微妙にWindowsでうまく動かない可能性があります。Docker Desktop for WSL 2でお試し中です。うまくいき次第追記予定です(2020/03/15)
まとめ
- 環境変数設定くらいであればdoskeyで逃げられる
- それ以上になれば、WSLをうまく利用し逃げるのが楽。その際の設定周りはなるべくWindowsと共用し、ダブルメンテにならないようにするのがオススメ
- 投稿日:2020-03-15T17:32:58+09:00
AWSを用いたGoの開発環境構築でWindowsユーザがMacユーザに歩み寄るナレッジをまとめた
はじめに
チームの開発環境の構築手順って、みなさまどうしていますか?多くのチームはリポジトリ内のREADMEや、チームWikiに準備することが多いのではないでしょうか?
その際、そのリポジトリ固有のBuild & Deploy & Release手順は記載することは普通だと思いますが、ここで問題になるのは、開発者の環境がWindows, Macなどで割れている場合です。全員Windows or Macで揃えろよってことですが、今の自分のチームはMac:Windows=7:3くらいです。ハードやライセンスの問題から自分たちと同じ用に完全に開発環境を揃えるのも難しいチームも多いでしょう。Amazon Workspacesは一つの解には間違いないですが、なるべく既存の資産を有効活用したいPJも多いと思います。
私のチームの話ですがWindowsは参画したてのメンバーや、アルバイトer社員の方が多く、事実上スキルが低いメンバーが多いのですが、環境構築手順がMacに比重高めなので脳内翻訳が大変な場合があります。
ここではなるべくWindowsユーザがMacユーザ用に書かれた環境構築手順書でも対応できるようなTipsをまとめていきたいと思います。
1. 環境構築の設定
WindowsとMacで揺れやすいところです。
- Windows:
set AWS_REGION=ap-northeast-1
- Mac:
export AWS_REGION=ap-northeast-1
毎回、exportをsetと入力してもらうのは大変ですが、ここで朗報です。Windowsでも export のコマンドで環境変数を設定できます(!)
doskey export=set $*上記を実行すると、export を setのエイリアスのように認識してくれます。
これを永続化するためには以下のサイトにあるように、macroファイルを準備し、ショートカットの引数に追加すると良いようです。2. パスの指定
自分たちのチームでGo系だとよくあるのが、
cd ${GOPATH}/src/github.com/future-architect/<Repository>
みたいな指定を書かれることです。Windowsだと
%GOPATH%
と書き換える以外なく、早くも脱落感があります。次の3,4,5のようにWSLをうまく活用しましょう。3. Makeコマンド
特にGoだとMakefileでビルドスクリプトを用意する文化があるので、Windowsユーザはちょっと対応が大変です。Makeコマンド自体は、Windowsでも追加でインストールできるものの、その内部でgrepコマンドなどを利用されたり、shellスクリプトを実行されるとお手上げです。とはいえ、このMakefileをWindows用も用意するのはダブルメンテですし、MacユーザはWindows側の実行テストするのがハードルが高いので、即座に腐っていきそうです。バッドスメルです?。
というわけで、WSL一択になります。この時点で1の環境変数設定もWSL側でやったほうが良いという結論になりがちですが、環境構築手順のレベル感を見てどこからWSLを使うかは個別判断になると思います。
Windows標準のコマンドプロンプトから、
wsl
またはbash
と入力するとコンソールがそのまま切り替わり、exit
を打つとWindows側に戻れるので便利です。ワンラインで処理したい場合は
bash
コマンドだと以下のように実行できます。bash -c "ls -la /c/mnt/Users/laqiiz/go/src | grep github"というわけで、Makeコマンドを実行したい場合は以下のように実行できます。
bash -c "make"WSLを利用したワンラインでの実行方法は以下で詳しく説明されていておすすめです。
https://docs.microsoft.com/ja-jp/windows/wsl/interop
4. GOPATHの設定
2で動けば良いですが、おそらく課題として、内部で
go build
する時にWindows側とWSL側でGOPATH
が異なるのでうまく動かないことも多いと思います。少しややこしいことになりました。対応としてはオススメは Windows側に WSL側のGOPATH
を寄せることです。bash # WSL側へ移動 export GOPATH=/mnt/c/Users/<ユーザ名>/go exit # Windows側へ戻るこれで go build も問題なく動くと思います。
しかし、go generate系も動かない可能性があるので、PATHにも追加します
export PATH=${PATH}:${GOPATH}/binWindows側で
go get -u
などでインストールした実行ファイルは.exe
なのでそのまま使えませんので、WSL側でも実行ファイルを作成する必要がありますが、MakefileにInstall手順も書かれている場合は問題にならないと思います。5. GoアプリのBuild
Goはクロスコンパイルできますので、Windowsでも問題なくLinux用の実行ファイルを作成できます。
しかし、環境手順には以下のようなワンラインで書かれていることも多いのではないでしょうか?ワンラインで書かれて辛いケースGOOS=linux GOARCH=amd64 go build ./cmd/your-app/your-app.goこの場合、Windowsに翻訳すると以下のように分割する必要があります。
Windowsだとこれset GOOS=linux set GOARCH=amd64 go build ./cmd/your-app/your-app.goコレでも良いのですが、このまま
go test
すると内部でbuildされる実行ファイルがWindowsで実行できなくなるため、set GOOS=windows
と設定し直す必要があり非常にノイジーです。というわけで、これもWSL側で実行するのがオススメです。
bash -c "GOOS=linux GOARCH=amd64 go build ./cmd/your-app/your-app.go"6. AWSCLIのための設定
Goのよくある使い所の一つとして、サーバサイドのWebAPI開発があると思います。このときAWSにDeployする方も多いのではないでしょうか? AWSCLIはWindowsでもMacでも公式で準備されているため、何も問題が無いことが多いですがいくつかのAWSコマンド実行する上で、ややこしいことがあります。
AWS CLIの設定
Profileを利用している場合、Windows側とWSL側でダブルメンテになるのは、設定漏れで作業効率を落とす原因になる事が多いので、なるべく避けた方が良いと思います。
例によって、WSL側の設定をWindows側に寄せます。
ln -s /mnt/c/Users/<User-Name>/.aws ~/.awsこれでAWSCLIなどで利用する configやcredentialsをWindows側と共用できるようになりました。もちろん、credentialsは最上位の機密情報ですので、取り扱いはWindows側と同様に注意して取り扱いましょう。
Lambdaのデプロイ
LambdaでGoアプリをデプロイするときは、以下のようにZIP化する必要があります。
LambdaのデプロイGOOS=linux GOARCH=amd64 go build ./cmd/lambda/lambda.go # ZIPファイルを作成 zip -j lambda.zip lambda # Lambdaのコードデプロイ(Lambda関数自体はすでに作成されている前提) aws --profile <YOUR_ENV> lambda update-function-code --function-name <YOUR_LAMBDA_NAME> --zip-file fileb://lambda.zipこの、
zip
コマンドはWindowsに無いのですが、公式にはbuild-lambda-zip
ツールのインストールが推奨されています。https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-go-how-to-create-deployment-package.html
これでももちろん良いですが、微妙にWindows側とコマンドが異なりややこしいです。ここでもWSLの出番となります。
複数ラインの場合はbash -c "コマンド"
と渡すのではなく、bash + exit で切り替えがオススメです。bash # Bashを起動 GOOS=linux GOARCH=amd64 go build ./cmd/lambda/lambda.go zip -j lambda.zip lambda aws --profile <YOUR_ENV> lambda update-function-code --function-name <YOUR_LAMBDA_NAME> --zip-file fileb://lambda.zip exit # (必要に応じて)Windows側へ戻る7. Docker
DockerもVolume周りの設定をされていると、微妙にWindowsでうまく動かない可能性があります。Docker Desktop for WSL 2でお試し中です。うまくいき次第追記予定です(2020/03/15)
設定について
説明の簡略化のため、今回はコンソール内でexportコマンドで済ましていますが、GoやAWSCLIを毎回利用する場合はWSL側の
~/.profile
などに記載して永続化することをオススメします。まとめ
- 環境変数設定くらいであればdoskeyで逃げられる
- それ以上になれば、WSLをうまく利用し逃げるのが楽。その際の設定周りはなるべくWindowsと共用し、ダブルメンテにならないようにするのがオススメ
- 投稿日:2020-03-15T11:27:59+09:00
[golang]gooseでrake db:migrateみたいな事をやる
Go言語でマイグレーションどうやんの…というところで色々試したうちの1つ。
Railsのrake db:migration
みたいな事ができるので、Railsに慣れている人には比較的使いやすいのではないかと思う。環境
go 1.13.4
gooseが記事執筆時点で対応しているデータベース
- postgres
- mysql
- sqlite3
- mssql
- redshift
gooseのインストール
go get -u github.com/pressly/goose/cmd/goose
各種コマンド
databaseを作る
db:create相当のコマンドは無い
マイグレーションファイルを作る
$ goose mysql "user:password@/testdb?parseTime=true" create AddColumnToTestTable sql 2020/03/15 11:10:29 Created new file: 20200315111029_AddColumnToTestTable.sqlすると以下のようなファイルが出力されるので、中身のSQL文は自分で書く。
Railsほどいい感じにテンプレート出力されるわけではない。-- +goose Up -- SQL in this section is executed when the migration is applied. -- +goose Down -- SQL in this section is executed when the migration is rolled back.マイグレーションを実行する
-- +goose Up
セクションに記載された内容が実行される。# 1つだけ実行 goose mysql "user:password@/testdb?parseTime=true" up-by-one # まだ実行してないものを全部実行 goose mysql "user:password@/testdb?parseTime=true" up # 特定のバージョンまで実行 # そのバージョンも含む goose mysql "user:password@/testdb?parseTime=true" up-to 20200315111029巻き戻し
-- +goose Down
セクションに記載された内容が実行される。# 最初まで巻き戻し goose mysql "user:password@/testdb?parseTime=true" down # 特定のバージョンまで巻き戻し # 指定したバージョンは含まない goose mysql "user:password@/testdb?parseTime=true" down-to 20200315111029再実行
特定のマイグレーションを
down
してからup
する。goose mysql "user:password@/testdb?parseTime=true" redo 20200315111029
欠点
コマンドラインにパスワードを含むのでDBのパスワードがコマンド履歴に残る。
BitBucketのほうのGooseは設定ファイルに書けるらしい。
- 投稿日:2020-03-15T11:27:59+09:00
[golang]rake db:migrateみたいな事をgoでできるgooseを使ってみた
Go言語でマイグレーションどうやんの…というところで色々試したうちの1つ。
Railsのrake db:migration
みたいな事ができるので、Railsに慣れている人には比較的使いやすいのではないかと思う。環境
go 1.13.4
gooseが記事執筆時点で対応しているデータベース
- postgres
- mysql
- sqlite3
- mssql
- redshift
gooseのインストール
go get -u github.com/pressly/goose/cmd/goose
各種コマンド
databaseを作る
db:create相当のコマンドは無い
マイグレーションファイルを作る
$ goose mysql "user:password@/testdb?parseTime=true" create AddColumnToTestTable sql 2020/03/15 11:10:29 Created new file: 20200315111029_AddColumnToTestTable.sqlすると以下のようなファイルが出力されるので、中身のSQL文は自分で書く。
Railsほどいい感じにテンプレート出力されるわけではない。-- +goose Up -- SQL in this section is executed when the migration is applied. -- +goose Down -- SQL in this section is executed when the migration is rolled back.マイグレーションを実行する
-- +goose Up
セクションに記載された内容が実行される。# 1つだけ実行 goose mysql "user:password@/testdb?parseTime=true" up-by-one # まだ実行してないものを全部実行 goose mysql "user:password@/testdb?parseTime=true" up # 特定のバージョンまで実行 # そのバージョンも含む goose mysql "user:password@/testdb?parseTime=true" up-to 20200315111029巻き戻し
-- +goose Down
セクションに記載された内容が実行される。# 最初まで巻き戻し goose mysql "user:password@/testdb?parseTime=true" down # 特定のバージョンまで巻き戻し # 指定したバージョンは含まない goose mysql "user:password@/testdb?parseTime=true" down-to 20200315111029再実行
特定のマイグレーションを
down
してからup
する。goose mysql "user:password@/testdb?parseTime=true" redo 20200315111029
欠点
コマンドラインにパスワードを含むのでDBのパスワードがコマンド履歴に残る。
BitBucketのほうのGooseは設定ファイルに書けるらしい。
- 投稿日:2020-03-15T07:54:35+09:00
【Go】Mac + VSCode + Go 環境設定
Goのインストール
基本こちらに記載されていた手順でインストール
参考リンク【エラー対応諸々】
① Go:Install/Update Tools が FAILED になる (スクショないので参考画面)
→以前GoLand(IDE)で旧バージョン(v1.11)を利用していたため。
→旧バージョンを削除し新バージョン(v1.13)をインストール (アップグレードの手順参考)
(旧バージョンのアンインストールは /usr/local/go 消したら良いよう)→アップグレード後、FAILEDなくすべてSUCCESSした
→VSCodeにて問題なくHello world もデバッグ実行できることを確認② godoc command not found
go get golang.org/x/tools/cmd/godoc で godocのダウンロードしたが
godoc fmt で確認すると godoc command not found となった(旧バージョン&GoLandでうまく動いていたから、これまで手動でPATH設定していなかった)
→vim ~/.bash_profile で
GOPATH を明記
PATH に GOPATH と GOROOT を明記source ~/.bash_profile で反映
再度 godoc fmt すると、
Unexpected arguments. Use "go doc" for command-line help output instead. For example, "go doc fmt.Printf".
と表示され、
"go doc fmt.Printf" すると確認できた。
参考リンク諸々
http://horie1024.hatenablog.com/entry/2014/08/24/024733
http://kodama-tech.hatenablog.com/entry/2016/12/14/002115
https://stackoverflow.com/questions/34708207/command-not-found-go-on-mac-after-installing-go③ コードにコメント書かないとエラー表示される
→VSCodeの基本設定にてgolintを検索し、Lint on Save を OFF にして無効にした(VSCode再起動)
- 投稿日:2020-03-15T03:54:25+09:00
【Go】【xorm/reverse】MySQLのINT UNSIGNEDに対応する!
はいどうも~
新人のプルリクに、選択肢を2つ与えつつ「自分のチカラで考えてみそ」とレビューしたら、なぜか新米部長から横レスで「じゃあお前が決めろよ、ちなみに選択肢Bは俺的に無しだから」と来て、何が琴線?に触れたのか不明ですがとりまLGTMで済ませた昨今、いかがお過ごしでしょうか
今回は、↓でご紹介した
xorm/reverse
を改造したお話です。
- ありの~ままの~DBスキーマを、go言語ソースコードに自動変換【xorm/reverse】
https://qiita.com/yagrush/items/cc60166d85befbcbf3d6何を変えたの?
MySQL DBリバース
↓
Go言語のstructコード
↓
INT UNSIGNEDカラムが、ただのint型structフィールドになってしまう
↓
uint型になってほしい
↓
改造変更点
まずは、最新版
xorm/reverse
とxorm/xorm
の帳尻合わせ
xorm/reverse
はxorm/xorm
(xorm本体)をインポートしていて大部分依存しているのですが、xorm/xorm
のバージョンが0.81
→0.82
と上がっていたので「どうせなら最新版に合わすか」と軽い気持ちでgo.modrequire ( ・ ・ ・ xorm.io/xorm latest )と
latest
で引っ張りなおしたら、結構影響が…
このver0.01の更新でリファクタリングがあったようです。ので、まずは
xorm/reverse
を最新xorm/xorm
に合わせる修正をします。※ご注意: giteaリポジトリ上のルートは
xorm
ですが、go package名は公式サイトドメインに沿ってxorm.io
になっているようです。
cmd/reverse.go
,language/golang.go
,language/language.go
Table, Column構造体がパッケージ
core
からschemas
に移動していたので、
import "xorm.io/xorm/schemas"
を追加core.Table
,core.Column
→schemas.Table
,schemas.Column
に修正これで、最新版
xorm/xorm
をインポートしたxorm/reverse
がビルド可能になりました。リバースロジックに手を入れ…
そう単純にはいかないようです…
処理を追跡すると、
language/golang.go
のtypestring()
で呼ばれているschemas.SQLType2Type()
の中で変換が行われているようです。
これってxorm/xorm
本体側のfuncですよね……分かりました、やりましょう!
xorm/xorm
も改造しちゃいます!
xorm/xorm
最新版をgit clone
早速
xorm/xorm
をgit clone
してきます。
ディレクトリはxorm.io/reverse
と同じ階層になるようにします。$ cd ../ $ git clone https://gitea.com/xorm/xorm.git $ ls xorm/ reverse/手元の
xorm/reverse
が、同じく手元のxorm/xorm
を参照するように変更※Go Modules方式でビルドしている前提です。
reverse/go.mod
に↓を追記します。go.modreplace xorm.io/xorm => ../xormこれで
xorm.io/xorm
パッケージのライブラリだけ、ネット上に公開されているものではなく手元のディスク内のものを参照するようになります。今度こそ、リバースロジックに手を入れる!
えっと、目論見を付けたのは
schemas.Type2SQLType()
でしたね。
このfuncは…xorm/schemas/type.go
にありますね。
早速手を入れていきましょう。type.go// default sql type change to go types func SQLType2Type(st SQLType) reflect.Type { name := strings.ToUpper(st.Name) switch name { case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, Serial: return reflect.TypeOf(1) case BigInt, BigSerial: return reflect.TypeOf(int64(1)) …ここに
case UInt:
を足せばいいのかなと思いますが、きっとそれだけでは済まないですね…
でもこれはこれで必要なので足します。type.gofunc SQLType2Type(st SQLType) reflect.Type { name := strings.ToUpper(st.Name) switch name { … case UInt: // ← return reflect.TypeOf(uint(1)) // ← …定数宣言部が冒頭にあるのでそれも足します。
type.govar ( … UInt = "INT UNSIGNED" // ← … SqlTypes = map[string]int{ … UInt: NUMERIC_TYPE, // ← … ) …さて、GoLandのデバッグ実行を使ってみましたが
INT UNSIGNED
カラムを持つテーブルを食わせてもSQLType2Type
のパラメータst.Name
として渡ってくる時点でUNSIGNED
が削られてしまっています……ここはひとつ、ちゃんとイチから辿ってみますか。
reverse/cmd/reverse.go
Reverse()
>runreverse()
>xorm/engine.go
DBMetas()
>loadTableInfo()
>
xorm/dialects/dialect.go
GetColumns()
>xorm/dialects/mysql.go
GetColumns()
お、ここにMySQLのカラム宣言部を解析する処理がありました。
mysql.gofunc (db *mysql) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { … cts := strings.Split(colType, "(") colName := cts[0] colType = strings.ToUpper(colTypeBase) …ここで、例えば
INT(10) UNIQUE UNSIGNED NOT NULL
といったカラム宣言からINT
を切り出しています。
これだと確かにUNSIGNED
が切り捨てられちゃいますね。
なのでINT(X) ... UNSIGNED ...
からINT UNSIGNED
として拾い上げるよう修正します。mysql.gofunc (db *mysql) GetColumns(ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { … cts := strings.Split(colType, "(") //colName := cts[0] var colTypeBase string group := regexp.MustCompile(`(?i)([a-zA-Z]+).*(\sUNSIGNED?).*`).FindSubmatch([]byte(colType)) if group == nil { colTypeBase = cts[0] } else { for i, g := range group { if i == 0 { continue } colTypeBase += string(g) } } colType = strings.ToUpper(colTypeBase) …ここ、実はMySQL側のバージョンによって罠がありました。
MySQL5.xだと
INT UNSIGNED ...
とカラム宣言しても、テーブル作成時、自動的にINT(10) UNSIGNED ...
と、(10)
が付けられてしまうので↑この修正が必要です。(MySQL8.xだとこの現象は発生しません。)いずれにしろ、↑のようにしておけば大丈夫です。
実行
では
xorm/reverse
を実行してみましょう。$ cd reverse $ go run main.go -f example/my-mysql.yml※設定ファイル
my-mysql.yml
の内容は、前回記事 https://qiita.com/yagrush/items/cc60166d85befbcbf3d6 をご参照下さい。すると、例えば↓のテーブルが
CREATE TABLE `users` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `name` varchar(32) COLLATE utf8_unicode_ci NOT NULL, `created_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`) );↓
type User struct { Id uint Name string CreatedAt *time.Time }
Id uint
になってますねおわり
ということで、以下を修正することで
INT UNSIGNED
カラムに対応することができました。
xorm/reverse
cmd/reverse.go
language/golang.go
language/language.go
xorm/xorm
schemas/type.go
dialects/mysql.go
実際には、この修正で
BIGINT UNSIGNED
などもUNSIGNED
付きで拾ってしまうようになるので 併せて修正が必要なのですが、ほぼコピペで済むので。
記事が冗長になってしまうので、ここでは割愛します。それではまた!
- 投稿日:2020-03-15T01:32:47+09:00
golang,docker,mysqlの環境をherokuにデプロイする
はじめに
言語: golang
コンテナ: docker-compose
RDB: mysql
ORM: gorm
マイグレーション: migrateな環境をherokuにデプロイするまで結構ハマったので残す。
コード
https://github.com/pokotyan/study-slack
実行したherokuのコマンド一覧
$ cd /hoge/huga # アプリケーションのコードがあるところに移動 $ heroku container:login # ログイン $ heroku create -a app_name # herokuアプリの作成 $ heroku git:remote -a app_name # herokuリポジトリをgit登録 $ heroku addons:add cleardb:ignite # mysqlのアドオンを追加 $ heroku config # CLEARDB_DATABASE_URLが登録されていることを確認 $ heroku config:set DATABASE_URL="<ユーザー名>:<password>@tcp(<ホスト名>:3306)/<DB名>?parseTime=true" # CLEARDB_DATABASE_URLの値を元にsql.Open()に渡す用の文字列に整形 $ heroku config # DATABASE_URLが登録されていることを確認 $ heroku stack:set container # heroku.ymlを使う時はこれがいるぽい $ git push heroku master # リリースheroku.yml
heroku.ymlを使うとCI/CDみたいなことができる。アプリのルートディレクトリに置いて使う。
buildにはdockerのビルドの指定ができる。
releaseにはリリースする際に挟みたい処理があれば書くことができる。ここではマイグレーションの実行をしている。
runはプロセスタイプ1ごとに実行するコマンドを指定する。./heroku.ymlbuild: docker: web: Dockerfile worker: dockerfile: Dockerfile target: builder release: image: worker command: - make up_migrate_prod run: web: /mainDockerfile
上述のheroku.ymlが参照するDockerfile。アプリのルートディレクトリに置く。
いくつかポイントがある。./DockerfileFROM golang:alpine as builder RUN apk update \ && apk add --no-cache git curl make gcc g++ \ && go get github.com/oxequa/realize WORKDIR /app COPY go.mod . COPY go.sum . RUN go mod download COPY . . RUN GOOS=linux GOARCH=amd64 go build -o /main FROM alpine:3.9 COPY --from=builder /main . ENV PORT=${PORT} ENTRYPOINT ["/main"]ライブラリのインストール
./DockerfileRUN apk update \ && apk add --no-cache git curl make gcc g++ \ && go get github.com/oxequa/realizerealizeは開発時のホットリロードのため。
make、gcc、g++はheroku.ymlのreleaseフェーズにてmakeコマンドでマイグレーションを流せるようにするため
curlはherokuのUI上でログを残すため(※)。※ curlを入れていないとこんな感じで何も表示されない。リリースが途中で死んでもなんで落ちたかが追えなくなるので入れておいた方がいいと思う。ログを出すためにcurlが必要なことは公式にも記載されている。
ビルド
builderのイメージはheroku.ymlでイメージのビルドをする際に使ったり、マイグレーションを実行する時のイメージとして利用している。
./DockerfileFROM golang:alpine as builder./heroku.ymlbuild: docker: web: Dockerfile worker: dockerfile: Dockerfile target: builder # builderのイメージをbuildする際に使う release: image: worker # 上記のworkerのイメージをreleaseフェーズでも使う command: - make up_migrate_prodアプリの実行
RUN GOOS=linux GOARCH=amd64 go build -o /main
でビルドしたファイルを実行する./DockerfileFROM alpine:3.9 COPY --from=builder /main . ENV PORT=${PORT} ENTRYPOINT ["/main"]./heroku.ymlrun: web: /maindocker-compose.yml
ローカルで開発する時のみに利用するdocker-compose.yml。
mysqlのコンテナが立ち上がる際にdocker-entrypoint-initdb.d
を利用してCREATE DATABASE
をするようにしている。herokuの本番環境ではdatabaseは
heroku addons:add cleardb:ignite
で用意されたものを利用する。
そのため、本番環境ではこのdocker-compose.ymlは利用しない。dockers/docker-compose.ymlversion: "3.5" services: mysql: container_name: push_study_db image: mysql:5.7.22 volumes: - ./mysql/:/docker-entrypoint-initdb.d/ - ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf environment: - MYSQL_ALLOW_EMPTY_PASSWORD=yes ports: - 4306:3306 app: build: context: .. target: builder volumes: - ../:/app command: realize start --server environment: - API_VERSION=development ports: - 7777:7777 depends_on: - mysql起動するポート
herokuはアプリが起動するたびにポートが変わるらしい。$PORTを指定して起動するようにする。
router.Run(":" + os.Getenv("PORT"))CLEARDB_DATABASE_URL
mysqlのアドオンを追加するとCLEARDB_DATABASE_URLという環境変数が自動で設定される。
heroku.ymlのreleaseフェーズで流れるようにしたマイグレーションだが、そのコードでは以下のようにしてdbと接続していた。dbURL := os.Getenv("CLEARDB_DATABASE_URL") db, _ := sql.Open("mysql", dbURL)リリースを実行すると
invalid memory address or nil pointer dereference
のエラーが出る。結果として、herokuが自動で作成してくれるCLEARDB_DATABASE_URLの書式をsql.Openが求めている書式に変換する必要があった。こちらの記事を参考にさせていただきました。
冒頭のherokuのコマンド一覧のところでも記載しているが、"<ユーザー名>:<password>@tcp(<ホスト名>:3306)/<DB名>?parseTime=true"
の形にしてあげる必要があった。最後に
herokuのリリース方法、色々ありすぎてまとまった情報を見つけるのが難しい。
ここで使ってるのはwebのプロセスタイプ。herokuが動くコンテナであるdynoのプロセスタイプは他にworker、one-offの計3つのプロセスタイプがあるぽい ↩