- 投稿日:2021-01-19T23:08:06+09:00
Golangでのゼロ埋め
Golangでのゼロ埋め
0~9の場合は数字の前に0を付け、
10以上の場合は0を付けない場合は以下の様に実現できるs := 3 str := fmt.Sprintf("%02d", s) fmt.Println(str) // 03 s2 := 13 str2 := fmt.Sprintf("%02d", s2) fmt.Println(str2) // 13
知らずに以下の様な回りくどい方法で実現したので知ったときは衝撃だった...if num < 10 { numStr = "0" + strconv.Itoa(num) } else { numStr = strconv.Itoa(num) }
- 投稿日:2021-01-19T23:04:51+09:00
Goでスライス内の最大値・最小値を抽出する関数
はじめに
Goでコードを書いているとスライス内の最大値、もしくは最小値を算出する処理を
何度か利用したので忘れないためにもメモスライス内の最大値を取得
func maxInt(a []int) int { sort.Sort(sort.IntSlice(a)) return a[len(a)-1] }スライス内の最小値を取得
func minInt(a []int) int { sort.Sort(sort.IntSlice(a)) return a[0] }サンプルコード
以下コードをThe Go Playgroundでコピペして実行
package main import "fmt" import "sort" func main() { test := []int{7, 8 ,1, 4, 3, 21} fmt.Println("max:", maxInt(test)) fmt.Println("min:", minInt(test)) } func maxInt(slice []int) int { sort.Sort(sort.IntSlice(slice)) return slice[len(slice)-1] } func minInt(slice []int) int { sort.Sort(sort.IntSlice(slice)) return slice[0] }参考
- 投稿日:2021-01-19T21:38:55+09:00
Go(Echo) Go Modules × Dockerで開発環境構築
はじめに
フレームワークに
Echo
ライブラリ管理にgo mod
ホットリロードにfresh
を使用したGoの開発環境をDockerを使って構築したいと思います。
GoのバージョンはGo 1.15
です。この記事ではDockerのインストール方法や細かい解説等はしません。
最終的なディレクトリ構成
. ├── app │ ├── Dockerfile │ ├── go.mod │ ├── go.sum │ └── main.go └── docker-compose.yml急いでいる人向け
ここからコードをダウンロードして
docker-compose up
すればできます。Dockerfile作成
まず最初に適当なディレクトリを作成(私の場合go-dockerというディレクトリを作成)
上記のディレクトリ構成を参考にappというディレクトリを作成し、
そしてappディレクトリにDockerfile
という名前でファイルを作成します。DockerfileFROM golang:1.15-alpine WORKDIR /go/src/app ADD ./app /go/src/app RUN apk update && \ apk add --no-cache git && \ go get github.com/labstack/echo/... && \ go get github.com/pilu/fresh EXPOSE 8080 CMD ["fresh"]main.go作成
次はappディレクトリに
main.go
というファイルを作成します。main.gopackage 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(":8080")) }docker-compose.yml作成
次に
docker-compose.yml
という名前でファイルを作成します。
フォルダ構成を参考にファイルを作成する場所に気をつけてください。docker-compose.ymlversion: "3" services: app: build: context: . dockerfile: app/Dockerfile #Dockerfileの場所 volumes: - ./app:/go/src/app ports: - "8080:8080" tty: true #コンテナ永続化build
次にymlファイルがある場所と同じ階層で
docker-compose build
コマンドを実行します。$ docker-compose build Building app Step 1/6 : FROM golang:1.15-alpine ---> b3bc898ad092 Step 2/6 : WORKDIR /go/src/app ---> Running in 55f4bfc0b0e5 Removing intermediate container 55f4bfc0b0e5 ---> bb957624bc5e Step 3/6 : ADD ./app /go/src/app ---> 94a4c0aeb52e Step 4/6 : RUN apk update && apk add --no-cache git && go get github.com/labstack/echo/... && go get github.com/pilu/fresh ---> Running in 2e16203c8eac fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz v3.12.3-65-g746e0b7bc7 [http://dl-cdn.alpinelinux.org/alpine/v3.12/main] v3.12.3-62-gebf75fec7d [http://dl-cdn.alpinelinux.org/alpine/v3.12/community] OK: 12756 distinct packages available fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz (1/5) Installing nghttp2-libs (1.41.0-r0) (2/5) Installing libcurl (7.69.1-r3) (3/5) Installing expat (2.2.9-r1) (4/5) Installing pcre2 (10.35-r0) (5/5) Installing git (2.26.2-r0) Executing busybox-1.31.1-r16.trigger OK: 22 MiB in 20 packages Removing intermediate container 2e16203c8eac ---> dfceb18e2ebd Step 5/6 : EXPOSE 8080 ---> Running in 8353095ba65e Removing intermediate container 8353095ba65e ---> fa4b48a798c4 Step 6/6 : CMD ["fresh"] ---> Running in a8c85cdb33ba Removing intermediate container a8c85cdb33ba ---> e121e6032342 Successfully built e121e6032342 Successfully tagged go-docker_app:latestgo mod init
次に
docker-compose run --rm app go mod init
というコマンドを実行します。
するとappディレクトリにgo.mod
が作成されます。$ docker-compose run --rm app go mod init Creating network "go-docker_default" with the default driver Creating go-docker_app_run ... done go: creating new go.mod: module appdocker-compose up
最後に
ddocker-compose up
というコマンドを実行します。
そしてgo.sum
も作られコンテナが立ち上がります。$ docker-compose up Creating go-docker_app_1 ... done Attaching to go-docker_app_1 app_1 | 9:27:18 runner | InitFolders app_1 | 9:27:18 runner | mkdir ./tmp app_1 | 9:27:18 watcher | Watching . app_1 | 9:27:18 main | Waiting (loop 1)... app_1 | 9:27:18 main | receiving first event / app_1 | 9:27:18 main | sleeping for 600 milliseconds app_1 | 9:27:18 main | flushing events app_1 | 9:27:18 main | Started! (5 Goroutines) app_1 | 9:27:18 main | remove tmp/runner-build-errors.log: no such file or directory app_1 | 9:27:18 build | Building... app_1 | 9:27:34 runner | Running... app_1 | 9:27:34 main | -------------------- app_1 | 9:27:34 main | Waiting (loop 2)... app_1 | 9:27:34 app | app_1 | ____ __ app_1 | / __/___/ / ___ app_1 | / _// __/ _ \/ _ \ app_1 | /___/\__/_//_/\___/ v3.3.10-dev app_1 | High performance, minimalist Go web framework app_1 | https://echo.labstack.com app_1 | ____________________________________O/_______ app_1 | O\ app_1 | 9:27:34 app | ⇨ http server started on [::]:8080この状態になったら下記にアクセスして
Hello world
と表示されていたら成功です。
http://localhost:8080/さいごに
次はMySQLの構築とその接続を時間があればやってみたいと思います。
- 投稿日:2021-01-19T20:39:28+09:00
go.modの補足 パッケージ
前回の続きです。
[go.mod]module example.com go 1.15 require github.com/labstack/echo/v4 v4.1.17上記のgo.modの一番上の
module example.com
の部分ですが、ここがimportして自分のパッケージを使う時の名前になります!
以下のような構成のプロジェクトの場合↓test
|--samplea--aaaaa.go
|--sampleb--bbbbb.go
|--main.go
|--go.mod
|--go.sum[main.go]package main import( "example.com/samplea" "example.com/sampleb" )このようにして使います。
ディレクトリの名前とモジュールの名前を同じにしたければ[go.mod]module testにします。
- 投稿日:2021-01-19T20:07:31+09:00
Go + MySQL + nginxの開発環境をDocker(docker-compose)で作る
やりたいこと
- Go、MySQL、nginxの開発環境をDocker(docker-compose)で作る
- Goの外部パッケージはGo Modulesで管理
- Goのプロジェクトを実践的なものにする
- DBのテーブル管理はマイグレーションを使う
- testはテスト用のDBを使う
こんな人におすすめ
- GoとMySQLでAPIサーバーの開発がしたい
- 環境構築はDockerで手っ取り早く済ませたい
- 拡張しやすいGoのプロジェクトが欲しい
使用するフレームワーク、バージョン
バージョン Go 1.15 MySQL 5.7 nginx 1.19 ディレクトリ構成
├── docker-compose.yml ├── Dockerfile ├── app │ ├── cmd │ │ ├── migrate │ │ │ └── main.go │ │ └── server │ │ └── main.go │ ├── db │ │ └── migrations │ │ ├── 1_create_users.down.sql │ │ └── 1_create_users.up.sql │ ├── go.mod │ ├── go.sum │ └── pkg │ ├── connecter │ │ └── connecter.go │ ├── controller │ │ ├── router.go │ │ └── users.go │ └── model │ ├── main_test.go │ ├── user.go │ └── user_test.go ├── mysql │ ├── Dockerfile │ ├── docker-entrypoint-initdb.d │ │ └── init.sql │ └── my.cnf └── nginx ├── Dockerfile └── default.confルートディレクトリにあるDockerfileがGoのコンテナ用です。
使い方
GitHubレポジトリはこちらにあります。
https://github.com/fuhiz/docker-go-sampleまずはdocker-compose.ymlがあるディレクトリでコンテナを立ち上げます。
$ docker-compose up -d --buildGoのコンテナに入ります。
$ docker-compose exec web bashGoのコンテナの/appでマイグレーションを実行します。
実行されるSQLはapp/db/migrationsのファイルです。
usersとマイグレーション管理のためのschema_migrationsが作られます。$ go run cmd/migrate/main.go -exec upusersには名前(name)、年齢(age)、日時カラムを用意しました。
/db/migrations/1_create_users.up.sqlCREATE TABLE `users` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `age` int NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, `deleted_at` datetime, PRIMARY KEY (`id`) ) DEFAULT CHARSET=utf8mb4;サーバーを起動します。
$ go run cmd/server/main.goこれでlocalhost:8082でGoのAPIにつながるようになります。
APIを使ってみる
DBにはusersテーブルがあって、ユーザーのCRUD機能が使えるようになっているので、curlで確認します。
- ユーザー作成
$ curl localhost:8082/api/v1/users -X POST -H "Content-Type: application/json" -d '{"name": "test", "age":30}'
- ユーザー一覧
$ curl localhost:8082/api/v1/users {"users":[{"ID":1,"CreatedAt":"2021-01-09T11:09:31+09:00","UpdatedAt":"2021-01-09T11:09:31+09:00","DeletedAt":null,"name":"test","age":30}]}%先ほど作ったユーザーが取得できます。
- ユーザー更新
$ curl localhost:8082/api/v1/users/1 -X PATCH -H "Content-Type: application/json" -d '{"name": "update", "age":31}'
- ユーザー削除
$ curl localhost:8082/api/v1/users/1 -X DELETEdocker-compose.yml
ここから環境構築の細かいところを見ていきます。
docker-compose.ymlの基本的な書き方には触れないので、参考にされる方はこちらを。
https://qiita.com/zembutsu/items/9e9d80e05e36e882caaaそれぞれのserviceについてはこのようになっております。
db
- MySQLコンテナ
- ユーザー名やパスワードなどを環境変数で定義。
- docker-entrypoint-initdb.dをマウントして、コンテナ起動時にdocker-entrypoint-initdb.d/init.sqlが実行されるようにします。init.sqlでgo_sampleとgo_sample_testというデータベースを作ります。 /docker-entrypoint-initdb.dはMySQLのDockerイメージに備わっているディレクトリで初期データを作ることができます。
- ホスト側のポートが3310なのはローカルで動かすMySQLと被らないようにするためです。
- Sequel Proで接続するときはこうなります。
※パスワードはlocalpass。データベースは空でも構わないです。web
- Goコンテナ
- 起動後すぐにコンテナが閉じてしまわないようにtty: trueでコンテナを永続化します。 (サーバー起動をDockerfileに書かず、コンテナの中で手動で実行するためです)
- Goプロジェクト内で使う環境変数を定義。Goのコードで
os.Getenv("DB_PASSWORD")
とすればこの値が読み込めます。DB_HOSTのdbはMySQLコンテナのサービス名です。GORMでDB接続するときにこのサービス名で接続できます。- Goプロジェクトがある./app(ホスト)を/app(コンテナ)にマウントします。コンテナ内の/appはDockerfileのWORKDIRで指定したときに作成されます。
proxy
- nginxはリバースプロキシによってURLを転送します。ここでは
http://localhost
がGoのAPIになるように設定する目的で使います。- ホスト側のportは8082を指定しました。
docker-compose.ymlversion: "3" services: db: build: ./mysql environment: MYSQL_ROOT_PASSWORD: root MYSQL_USER: localuser MYSQL_PASSWORD: localpass TZ: Asia/Tokyo volumes: - ./mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d ports: - "3310:3306" web: build: . tty: true environment: APP_MODE: local DB_PASSWORD: localpass volumes: - "./go:/app" depends_on: - db proxy: build: ./nginx ports: - 8082:80 depends_on: - webMySQLコンテナ
MySQLのDockerfileはこれらの一般的な設定です。
タイムゾーンをAsia/Tokyoにする。
設定ファイルのmy.cnfをコピーする。
起動時に実行するinit.sqlをコピーする。mysql/DockerfileFROM mysql:5.7 ENV TZ Asia/Tokyo RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && chown -R mysql:root /var/lib/mysql/ COPY my.cnf /etc/mysql/conf.d/my.cnf COPY docker-entrypoint-initdb.d/init.sql /docker-entrypoint-initdb.d/ CMD ["mysqld"] EXPOSE 3306init.sqlではgo_sampleとテスト用のgo_sample_testを作ってユーザーの権限設定をします。
mysql/docker-entrypoint-initdb.d/init.sqlCREATE DATABASE IF NOT EXISTS `go_sample` COLLATE 'utf8mb4_general_ci' ; CREATE DATABASE IF NOT EXISTS `go_sample_test` COLLATE 'utf8mb4_general_ci' ; GRANT ALL ON `go_sample`.* TO 'localuser'@'%' ; GRANT ALL ON `go_sample_test`.* TO 'localuser'@'%' ; FLUSH PRIVILEGES ;Goコンテナ
内容はコメントの通りで、外部パッケージをダウンロードするためにgo.modとgo.sumを事前にコピーしています。
DockerfileFROM golang:1.15 ## 作業ディレクトリ WORKDIR /app # モジュール管理のファイルをコピー COPY go/go.mod . COPY go/go.sum . # 外部パッケージのダウンロード RUN go mod download EXPOSE 9000nginxコンテナ
nginx.confで読み込むdefault.confをコピーします。
nginx/DockerfileFROM nginx:1.19-alpine COPY ./default.conf /etc/nginx/conf.d/default.conf EXPOSE 80nginx/default.confserver { listen 80; server_name localhost; location / { proxy_pass http://web:9000; } }nginxの設定ファイルである/etc/nginx/nginx.confで/etc/nginx/conf.d/配下の*.confを読み込むようになっているので、読み込まれる部分だけを作っています。
nginxの設定はこちらが参考になります。
https://qiita.com/morrr/items/7c97f0d2e46f7a8ec967大事なのは
proxy_pass http://web:9000;
の部分で、ここでhttp://localhost
をhttp://web:9000
に置き換えています。
webはdocker-compose.ymlで定義したGoコンテナのサービス名です。
docker-composeはサービス間でネットワーク通信できるので、このような指定ができます。
Goプロジェクトはポートを9000でサーバーを立ち上げているのでポートはそれに合わせます。また、docker-compose.ymlのnginxコンテナでポートを8082:80としているので、ホストからは
http://localhost:8082
でアクセスします。ややこしいですが、とどのつまりは
http://localhost:8082
でGoのAPIが叩けることになります。Goのプロジェクト概要
Goのコードはなるべく実践的に使えるものを意識して作りました。
ディレクトリ構造はこちらを参考にしています。
https://qiita.com/sueken/items/87093e5941bfbc09bea8cmd
アプリケーションのエントリーポイント。
サーバー起動とマイグレーション機能を配置。db
マイグレーションで実行したいsqlファイルを配置。pkg
アプリケーションの挙動に関わる部分。
モデル(model)、コントローラー(controller)、接続(connecter)を作成。マイグレーション
マイグレーション周りはこちらを参考にさせていただきました。
https://qiita.com/tanden/items/7b4fb1686a61dd5f580dgolang-migrateを使用して、db/migrationsにあるsqlファイルでDBを管理します。
ファイル名のルールは{version}を昇順にすれば、番号でもタイムスタンプでも問題ありません。
https://github.com/golang-migrate/migrate/blob/master/MIGRATIONS.md{version}_{title}.up.{extension} {version}_{title}.down.{extension}ここではusersテーブルを作成する
1_create_users.up.sql
とテーブル削除用の1_create_users.down.sql
を作成しました。マイグレーション管理のファイルは
cmd/migrate/main.go
にあります。内容は参考サイトのほぼコピペになります。このファイルを実行すれば追加した分の*.up.sqlだけが走ります。
$ go run cmd/migrate/main.go -exec up戻したいときはオプションをdownにすれば、全ての*.down.sqlが実行されます。
$ go run cmd/migrate/main.go -exec downtest用のデータベースに接続したいときはAPP_MODE=testで環境変数つきで実行します。
$ APP_MODE=test go run cmd/migrate/main.go -exec upcmd/migrate/main.goのinit()でAPP_MODEがtestなら、データベースはDB_NAME_TESTを使うようにしてます。
cmd/migrate/main.gofunc init() { // database name decide by APP_MODE dbName := os.Getenv("DB_NAME") if os.Getenv("APP_MODE") == "test"{ dbName = os.Getenv("DB_NAME_TEST") } Database = fmt.Sprintf("mysql://%s:%s@tcp(%s:%s)/%s", os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), dbName) }GoのAPIの処理の流れ
エントリーポイントとなるファイルはcmd/server/main.go。
ginを使って、ポート9000でサ-バーを立ち上げています。cmd/server/main.gopackage main import ( "net/http" "github.com/gin-gonic/gin" "github.com/fuhiz/docker-go-sample/app/pkg/connecter" "github.com/fuhiz/docker-go-sample/app/pkg/controller" ) func main() { // gormのDB接続 connecter.Setup() router := gin.Default() // apiの疎通確認用 router.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "Response OK") }) // routing r := router.Group("/api/v1") controller.Setup(r) router.Run(":9000") }細かい処理は/pkgのcontrollerなどを使っていきます。
gormのDB接続はpkg/connecter/connecter.goで行います。
変数dbに*gorm.DBを格納して、DB()で呼び出せる形になっています。接続の仕方は公式を見れば大体把握できます。
https://gorm.io/docs/connecting_to_the_database.htmlデータベースの各パラメータはdocker-compose.ymlで定めた環境変数から取得しています。
ここでもAPP_MODEがtestならDB_NAME_TESTを使います。pkg/connecter/connecter.gopackage connecter import ( "fmt" "os" "gorm.io/driver/mysql" "gorm.io/gorm" ) var db *gorm.DB func Setup() { // APP_MODEからデータベース名を決める dbName := os.Getenv("DB_NAME") if os.Getenv("APP_MODE") == "test"{ dbName = os.Getenv("DB_NAME_TEST") } // DB接続 (https://gorm.io/docs/connecting_to_the_database.html) dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=true&loc=%s", os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), dbName, os.Getenv("DB_LOC")) gormDB, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic(err) } db = gormDB } func DB() *gorm.DB { return db }ルーティングはpkg/controllers/router.goに書きます。
それぞれpkg/controllers/users.goのfuncを呼びます。pkg/controllers/router.gopackage controller import ( "github.com/gin-gonic/gin" ) func Setup(r *gin.RouterGroup) { users := r.Group("/users") { u := UserController{} users.GET("", u.Index) users.GET("/:id", u.GetUser) users.POST("", u.CreateUser) users.PATCH("/:id", u.UpdateUser) users.DELETE("/:id", u.DeleteUser) } }pkg/controllers/users.goでは処理に応じて/pkg/model/user.goのfuncを呼びます。
pkg/controllers/users.gopackage controller import ( "net/http" "strconv" "github.com/gin-gonic/gin" "github.com/fuhiz/docker-go-sample/app/pkg/connecter" "github.com/fuhiz/docker-go-sample/app/pkg/model" ) type UserController struct{} type UserParam struct { Name string `json:"name" binding:"required,min=1,max=50"` Age int `json:"age" binding:"required,number"` } // ユーザー取得 func (self *UserController) GetUser(c *gin.Context) { ID := c.Params.ByName("id") userID, _ := strconv.Atoi(ID) user, err := model.GetUserById(connecter.DB(), userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "user not found"}) return } c.JSON(http.StatusOK, gin.H{"user": user}) } // ユーザー一覧 func (self *UserController) Index(c *gin.Context) { users, err := model.GetUsers(connecter.DB()) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "user search failed"}) return } c.JSON(http.StatusOK, gin.H{"users": users}) } // ユーザー作成 func (self *UserController) CreateUser(c *gin.Context) { var param UserParam if err := c.BindJSON(¶m); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } newUser := model.NewUser(param.Name, param.Age) user, err := model.CreateUser(connecter.DB(), newUser) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "user create failed"}) return } c.JSON(http.StatusOK, gin.H{"user": user}) } // ユーザー更新 func (self *UserController) UpdateUser(c *gin.Context) { ID := c.Params.ByName("id") userID, _ := strconv.Atoi(ID) user, err := model.GetUserById(connecter.DB(), userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "user not found"}) return } var param UserParam if err := c.BindJSON(¶m); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } updateParam := map[string]interface{}{ "name": param.Name, "age": param.Age, } _, err = user.Update(connecter.DB(), updateParam) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "user update failed"}) return } c.JSON(http.StatusOK, gin.H{"user": user}) } // ユーザー削除 func (self *UserController) DeleteUser(c *gin.Context) { ID := c.Params.ByName("id") userID, _ := strconv.Atoi(ID) user, err := model.GetUserById(connecter.DB(), userID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "user not found"}) return } _, err = user.Delete(connecter.DB()) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "user delete failed"}) return } c.JSON(http.StatusOK, gin.H{"deleted": true}) }/pkg/model/user.gopackage model import ( "gorm.io/gorm" ) type User struct { gorm.Model Name string `json:"name"` Age int `json:"age"` } func NewUser(name string, age int) *User { return &User{ Name: name, Age: age} } func CreateUser(db *gorm.DB, user *User) (*User, error) { result := db.Create(&user) return user, result.Error } func GetUsers(db *gorm.DB) ([]*User, error) { users := []*User{} result := db.Find(&users) return users, result.Error } func GetUserById(db *gorm.DB, ID int) (*User, error) { user := User{} result := db.First(&user, ID) return &user, result.Error } func (user *User) Update(db *gorm.DB, param map[string]interface{}) (*User, error) { result := db.Model(&user).Updates(param) return user, result.Error } func (user *User) Delete(db *gorm.DB) (*User, error) { result := db.Delete(&user) return user, result.Error }テスト
テストはGoのコンテナ内でAPP_MODE=testをつけて実行します。
以下手順。マイグレーション(up)でgo_sample_testにテーブルを作成。
$ APP_MODE=test go run cmd/migrate/main.go -exec up/pkgをテスト。
$ APP_MODE=test go test -v ./pkg/...次のテストのためにgo_sample_testを戻す。
$ APP_MODE=test go run cmd/migrate/main.go -exec downテストファイルは/pkg/modelにmain_test.goとuser_test.goがあります。
TestMainが最初に実行されるので、そこでDB接続しときます。/pkg/model/main_test.gopackage model_test import ( "testing" "github.com/fuhiz/docker-go-sample/app/pkg/connecter" ) func TestMain(m *testing.M) { connecter.Setup() m.Run() }ユーザー作成のテスト。
/pkg/model/user_test.gopackage model_test import ( "testing" "github.com/fuhiz/docker-go-sample/app/pkg/connecter" "github.com/fuhiz/docker-go-sample/app/pkg/model" ) func TestCreateUser(t *testing.T) { newUser := model.NewUser("test_user", 30) user, _ := model.CreateUser(connecter.DB(), newUser) if user.Name != "test_user" { t.Fatal("model.CreateUser Failed") } }まとめ
ローカル環境としてはそれなりに使える環境が整えられたと思います。
自動テストやデプロイにも対応できるかは今後検証していきたいです。Goはまだまだベストプレクティスが確立されていないようでテスト環境の切り分けは苦労しました。
改めてLaravelやRailsのような全部入りのフレームワークの偉大さも感じました。長めの記事でしたが参考にしてもらえたらありがたいです!
- 投稿日:2021-01-19T15:50:14+09:00
Slack ソケットモードの最も簡単な始め方 (Go 編)
slack-go/slack がソケットモードに対応
広く使われている Go SDK の github.com/slack-go/slack がソケットモード対応しました @mumoshu さんと @kanata2 さんの素晴らしい仕事です
https://github.com/slack-go/slack
ソケットモードアプリ起動までの手順
この記事では、Slack ソケットモードの最も簡単な始め方で使ったサンプルアプリと同じものを、この Go SDK を使って動かす方法を解説します。
プロジェクトをつくる
ここで紹介するサンプルは v0.8.0 以上で動作します。
go mod init socket-mode-app go get github.com/slack-go/slack@v0.8.0main.go を用意
とりあえず以下のソースコードをそのままコピペしてみてください。
package main import ( "fmt" "github.com/slack-go/slack/socketmode" "log" "os" "strings" "github.com/slack-go/slack" "github.com/slack-go/slack/slackevents" ) func main() { webApi := slack.New( os.Getenv("SLACK_BOT_TOKEN"), slack.OptionAppLevelToken(os.Getenv("SLACK_APP_TOKEN")), slack.OptionDebug(true), slack.OptionLog(log.New(os.Stdout, "api: ", log.Lshortfile|log.LstdFlags)), ) socketMode := socketmode.New( webApi, socketmode.OptionDebug(true), socketmode.OptionLog(log.New(os.Stdout, "sm: ", log.Lshortfile|log.LstdFlags)), ) authTest, authTestErr := webApi.AuthTest() if authTestErr != nil { fmt.Fprintf(os.Stderr, "SLACK_BOT_TOKEN is invalid: %v\n", authTestErr) os.Exit(1) } selfUserId := authTest.UserID go func() { for envelope := range socketMode.Events { switch envelope.Type { case socketmode.EventTypeEventsAPI: // イベント API のハンドリング // 3 秒以内にとりあえず ack socketMode.Ack(*envelope.Request) eventPayload, _ := envelope.Data.(slackevents.EventsAPIEvent) switch eventPayload.Type { case slackevents.CallbackEvent: switch event := eventPayload.InnerEvent.Data.(type) { case *slackevents.MessageEvent: if event.User != selfUserId && strings.Contains(event.Text, "こんにちは") { _, _, err := webApi.PostMessage( event.Channel, slack.MsgOptionText( fmt.Sprintf(":wave: こんにちは <@%v> さん!", event.User), false, ), ) if err != nil { log.Printf("Failed to reply: %v", err) } } default: socketMode.Debugf("Skipped: %v", event) } default: socketMode.Debugf("unsupported Events API eventPayload received") } case socketmode.EventTypeInteractive: // ショートカットのハンドリングとモーダル起動 payload, _ := envelope.Data.(slack.InteractionCallback) switch payload.Type { case slack.InteractionTypeShortcut: if payload.CallbackID == "socket-mode-shortcut" { socketMode.Ack(*envelope.Request) modalView := slack.ModalViewRequest{ Type: "modal", CallbackID: "modal-id", Title: slack.NewTextBlockObject( "plain_text", "タスク登録", false, false, ), Submit: slack.NewTextBlockObject( "plain_text", "送信", false, false, ), Close: slack.NewTextBlockObject( "plain_text", "キャンセル", false, false, ), Blocks: slack.Blocks{ BlockSet: []slack.Block{ slack.NewInputBlock( "input-task", slack.NewTextBlockObject( "plain_text", "タスク", false, false, ), // multiline is not yet supported slack.NewPlainTextInputBlockElement( slack.NewTextBlockObject( "plain_text", "タスクの詳細・期限などを書いてください", false, false, ), "input", ), ), }, }, } resp, err := webApi.OpenView(payload.TriggerID, modalView) if err != nil { log.Printf("Failed to opemn a modal: %v", err) } socketMode.Debugf("views.open response: %v", resp) } case slack.InteractionTypeViewSubmission: // モーダルからの送信をハンドリング if payload.CallbackID == "modal-id" { socketMode.Debugf("Submitted Data: %v", payload.View.State.Values) socketMode.Ack(*envelope.Request) } default: socketMode.Debugf("Skipped: %v", payload) } default: socketMode.Debugf("Skipped: %v", envelope.Type) } } }() socketMode.Run() }起動する
前の記事と同様、環境変数を設定した上で起動してみましょう。
export SLACK_APP_TOKEN=xapp-<自分のトークンの値> export SLACK_BOT_TOKEN=xoxb-<自分のトークンの値> go run main.go以下のようなメッセージが表示されていれば、接続できています!
$ go run main.go api: 2021/01/19 15:41:48 slack.go:125: Challenging auth... sm: 2021/01/19 15:41:48 socket_mode_managed_conn.go:241: Starting SocketMode sm: 2021/01/19 15:41:48 main.go:133: Skipped: connecting api: 2021/01/19 15:41:48 socket_mode.go:30: Using URL: wss://wss-primary.slack.com/link/?ticket=xxx&app_id=yyy sm: 2021/01/19 15:41:48 socket_mode_managed_conn.go:249: Dialing to websocket on url wss://wss-primary.slack.com/link/?ticket=xxx&app_id=yyy sm: 2021/01/19 15:41:49 socket_mode_managed_conn.go:78: WebSocket connection succeeded on try 0 sm: 2021/01/19 15:41:49 socket_mode_managed_conn.go:422: Starting to receive message sm: 2021/01/19 15:41:49 main.go:133: Skipped: connected sm: 2021/01/19 15:41:49 socket_mode_managed_conn.go:464: Incoming WebSocket message: { "type": "hello", "num_connections": 1, "debug_info": { "host": "applink-xxx-yyy", "build_number": 10, "approximate_connection_time": 18060 }, "connection_info": { "app_id": "A111" } } sm: 2021/01/19 15:41:49 socket_mode_managed_conn.go:476: Finished to receive message sm: 2021/01/19 15:41:49 socket_mode_managed_conn.go:422: Starting to receive message sm: 2021/01/19 15:41:49 socket_mode_managed_conn.go:319: Received WebSocket message: {"type":"hello","num_connections":1,"debug_info":{"host":"applink-xxx-yyy","build_number":10,"approximate_connection_time":18060},"connection_info":{"app_id":"A111"}} sm: 2021/01/19 15:41:49 main.go:133: Skipped: hello sm: 2021/01/19 15:41:51 socket_mode_managed_conn.go:544: WebSocket ping message received: Ping from applink-xxx-yyy次のステップ
オフィシャルのサンプルコードは以下の場所にありますので、チェックしてみてください。
https://github.com/slack-go/slack/tree/master/examples/socketmode