- 投稿日:2019-07-01T21:38:52+09:00
Go言語で「FizzBuzz」を書いてみた
背景
Go言語を学び始めてから3日程経ち、基本的な文法を学び終えたためアウトプットをしようと思った。
FizzBuzzとは
内容は1〜100までの数字を表示し、3の倍数の時は「Fizz」、5の倍数の時は「Buzz」、15の倍数の時は「FizzBuzz」と表示するというものです。
主に、プログラミング言語学習の最初の段階で学ぶ、「変数」「for文」「if文」で書けます。「変数」「for文」「if文」を学び終えた段階で丁度良いアウトプットなのではないでしょうか。
本題
Go言語でのFizzBuzzはこのように書きます。
fizzbuzz.gopackage main import "fmt" func main() { for i := 1; i < 101; i++ { if i%15 == 0 { fmt.Println("FizzBuzz") } else if i%3 == 0 { fmt.Println("Fizz") } else if i%5 == 0 { fmt.Println("Buzz") } else { fmt.Println(i) } } }Go言語を学び始めてまだ日が浅い私にとってはとても良い練習になりました!
良いGoLifeを!
- 投稿日:2019-07-01T15:10:21+09:00
GoのユニットテストでAWS SDKのモックをする
Goのユニットテストでどうしてもaws-sdkを叩くレイヤーのtestがしたくなったとき、localstackなど実際のawsを模した機能を用意できればそれが一番だが、たいてい大掛かりになってしまう。
goのaws-sdkには大抵interfaceが用意されているので、これをmockでうまく使うとなかなか良かった。そもそもDIの基本になるが、なるべくs3.S3などのstructそのものでなく、このようなinterfaceへの依存を持っておくことで簡単にmockが可能になるので、設計の際に留意しておく。
例えば、s3であれば S3APIというinterfaceがある。
今回はこういうものを用意した。
package aws_sdk import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/service/s3" ) type S3SdkMockImpl struct { //テストごとに差し替え可能にする ListObjects_Mock func(*s3.ListObjectsInput) (*s3.ListObjectsOutput, error) HeadObject_Mock func(*s3.HeadObjectInput) (*s3.HeadObjectOutput, error) } // auto generated by jetbrains func (S3SdkMockImpl) AbortMultipartUpload(*s3.AbortMultipartUploadInput) (*s3.AbortMultipartUploadOutput, error) { panic("implement me") } func (S3SdkMockImpl) AbortMultipartUploadWithContext(aws.Context, *s3.AbortMultipartUploadInput, ...request.Option) (*s3.AbortMultipartUploadOutput, error) { panic("implement me") } func (S3SdkMockImpl) AbortMultipartUploadRequest(*s3.AbortMultipartUploadInput) (*request.Request, *s3.AbortMultipartUploadOutput) { panic("implement me") } func (S3SdkMockImpl) CompleteMultipartUpload(*s3.CompleteMultipartUploadInput) (*s3.CompleteMultipartUploadOutput, error) { panic("implement me") } func (S3SdkMockImpl) CompleteMultipartUploadWithContext(aws.Context, *s3.CompleteMultipartUploadInput, ...request.Option) (*s3.CompleteMultipartUploadOutput, error) { panic("implement me") } ... // ダミーが刺されていれば使う func (self *S3SdkMockImpl) HeadObject(in *s3.HeadObjectInput) (*s3.HeadObjectOutput, error) { if self.HeadObject_Mock != nil { return self.HeadObject_Mock(in) } panic("implement me") } .... func (self *S3SdkMockImpl) ListObjects(in *s3.ListObjectsInput) (*s3.ListObjectsOutput, error) { if self.ListObjects_Mock != nil { return self.ListObjects_Mock(in) } panic("implement me") }interfaceを埋めるメソッド自体はGolang等のIDEですぐにgenerateできるが、テストケースごとに生成するのも見通しが悪いため、必要なメソッドごとに差し替え可能にし、使いまわせるようにした。
使用側はこうなる。s3SdkMockTestCase01 := aws_sdk.S3SdkMockImpl{ ListObjects_Mock: func(input *s3.ListObjectsInput) (output *s3.ListObjectsOutput, e error) { return &s3.ListObjectsOutput{ ... //自由に返す } , nil }, } s3SdkMockTestCase02 := aws_sdk.S3SdkMockImpl{ ListObjects_Mock: func(input *s3.ListObjectsInput) (output *s3.ListObjectsOutput, e error) { return &s3.ListObjectsOutput{ ... //自由に返す } , nil }, } s3DataRepoisitoryForTest01 := NewS3DataRepository(s3SdkMockTestCase01) s3DataRepoisitoryForTest02 := NewS3DataRepository(s3SdkMockTestCase02)場合に応じて差し替えが簡単になり、使いまわせるようにもなった。
- 投稿日:2019-07-01T15:01:38+09:00
The Go Playground上で簡単に複数のファイルをシェアする #golang
The Go Playgroundが複数ファイル対応になった
Goコミュニティでみんなが便利に使っているThe Go Playgroundですが、ついに複数ファイルに対応しました。
参考:https://github.com/golang/go/issues/32040
txtarという複数のファイルを1つのファイルにまとめる方法を使うことによって複数のファイルを記述しています。
例えば、次のようなディレクトリ構成のGoのプロジェクトがあったとします。
. ├── fuga │ └── fuga.go ├── go.mod └── main.goこれをtxtar形式で1つのファイルにまとめると次のようになります。
-- go.mod -- module hogera go 1.12 -- fuga/fuga.go -- package fuga func Fuga() string { return "Fuga" } -- main.go -- package main import ( "fmt" "hogera/fuga" ) func main() { fmt.Println(fuga.Fuga()) }txtar形式はファイルを
-- ファイルパス --
で区切って表現します。
シンプルな形式なので簡単に書くことができますが、ファイル中に--
で始まる行があるとうまくいかないので注意が必要です。https://play.golang.org/p/4CGQN8slkSl
package main import ( "fmt" ) /* -- hogera -- */ func main() { fmt.Println("Hello, playground") }手で入力していくのはなかなか大変なため、Chromeの拡張機能を使うとファイルごとにタブで記述できるようになるため便利です。
コマンドラインから複数のファイルをまとめてシェアする
コマンドラインから手元のソースコードをサクッとシェアできると便利です。
筆者が開発しているThe Go Playgroundをコマンドラインから操作するツールであるgp
を使うと簡単に複数のファイルをtxar形式でシェアすること可能になります。
gp
のインストールはgo get
で行うことができます。$ go get https://github.com/tenntenn/goplayground/cmd/gp
複数のファイルをまとめてシェアするには、次のように
share
サブコマンドを用います。$ gp share a.go b.go
複数のファイルをまとめるには、次のように
find
コマンドなどで用いると良いでしょう。
なお、ここでは不可視ファイルを無視するように-path '*/\.*'
を指定しています。find . -not -path '*/\.*' -type f | xargs gp shareThe Go Playgroundからソースコードをダウンロードする
gp
コマンドを用いるとThe Go Playground上でシェアされたコードをダウンロードすることもできます。
次のように何も指定しない場合には標準出力に出力されます。$ gp download https://play.golang.org/p/sTkdodLtokQ package main import ( "fmt" ) func main() { fmt.Println("Hello, playground") }txtar形式の場合でも何も指定しなければそのまま標準出力に出力されますが、
-dldir
オプションでディレクトリを指定するとディレクトリ以下にtxttar形式を展開した形でファイルやディレクトリを保存してくれます。$ gp download -dldir=output https://play.golang.org/p/Ccn7TxELuAT $ tree output output ├── fuga │ └── fuga.go ├── go.mod └── main.goまとめ
gp
便利なのでぜひ使ってみてください。
- 投稿日:2019-07-01T07:31:36+09:00
Beego(Golang Framework)使い方メモ
■ はじめに
Go 言語のフレームワークである Beego の使い方について、メモを残す。
■ 環境
Mac
■ Install
beego をローカルにインストールする。
go get github.com/beego/bee go get -u github.com/astaxie/beego■ パスを通す
bee コマンドを実行できるように
~/.bashrc
にパスを通す。export GO15VENDOREXPERIMENT=1 export GOPATH=$HOME/go export PATH=$PATH:$GOPATH/bin export PATH=$PATH:$GOROOT/bin適応。
source ~/.bashrc■ Create Project
bee コマンドを使って、プロジェクトを作成する。
bee new beego-project上記を実行すると beego-project フォルダが作成される。
■ 動作確認
ターミナル上で、bee run コマンドを実行するとプロジェクトが起動する。
cd beego-project bee runlocalhost:8080 にブラウザからアクセスすると以下の画面が表示される。
■ Controller の作成
Beego は、MVC タイプのフレームワークらしい。
そのため、C(Controller) を作成する。
touch beego-project/controllers/firstcontroller.gofirstcontroller.gopackage controllers import "github.com/astaxie/beego" type FirstController struct { beego.Controller } type Employee struct { ID int `json:"id"` FirstName string `json:"firstName"` LastName string `json:lastName` } type Employees []Employee var employees []Employee func init() { employees = Employees{ Employee{ID: 1, FirstName: "Foo", LastName: "Bar"}, Employee{ID: 2, FirstName: "Baz", LastName: "Qux"}, } } func (this *FirstController) GetEmployees() { this.Ctx.ResponseWriter.WriteHeader(200) this.Data["json"] = employees this.ServeJSON() }init 関数で初期データを用意する。
ブラウザから localhost:8080/employees にアクセスした時、GetEmployees 関数が呼ばれ、初期化されたデータを JSON として保存し、呼び出し元に返す。
といった感じの関数。
■ Router の作成
次に Router を作成する。
router.go は。Beego プロジェクトにデフォルトで用意されている。
beego-project/routers/router.gopackage routers import ( "github.com/astaxie/beego" "github.com/hoge/golang_cookcodes/beego_framework/beego-project/controllers" ) func init() { beego.Router("/", &controllers.MainController{}) # 追加 beego.Router("/employees", &controllers.FirstController{}, "get:GetEmployees") }ここまで設定したらブラウザ上から
localhost:8080/employees
にアクセスする。bee runcurl でも良い。
$ curl -X GET http://localhost:8080/employees [ { "id": 1, "firstName": "Foo", "LastName": "Bar" }, { "id": 2, "firstName": "Baz", "LastName": "Qux" } ]■ View の作成
次は、MVC の V(View) だ。
まずは、テンプレートファイルを作成する。
touch beego-project/views/dashboard.tpldashboard.tpl<!DOCTYPE html> <html> <head> <title>Using beego Framework</title> </head> <body> <table border="1" style="width:100%;"> {{ range.employees }} <tr> <td>{{.ID}}</td> <td>{{.FirstName}}</td> <td>{{.LastName}}</td> </tr> {{end}} </table> </body> </html>このファイルは、リクエストを処理した後に、ブラウザに表示するテンプレートファイルだ。
Controller に Dashboard 関数を追加する。
firstcontroller.gofunc (this *FirstController) Dashboard() { this.Data["employees"] = employees this.TplName = "dashboard.tpl" }さっきは、JSON にデータを渡したが、今回は先に作成したテンプレートファイルにデータを渡す。
最後に router に対して、controller に記述した Dashboard 関数をマッピングする。
router.gofunc init() { beego.Router("/", &controllers.MainController{}) beego.Router("/employees", &controllers.FirstController{}, "get:GetEmployees") # 追加 beego.Router("/dashboard", &controllers.FirstController{}, "get:Dashboard") }localhost:8080/dashboard にアクセスする。
■ redis の導入
redis を入れて、セッション管理を行う。
go get -u github.com/astaxie/beego/session/redis touch beego-project/controllers/sessioncontorller.gosessioncontorller.gopackage controllers import ( "github.com/astaxie/beego" ) type SessionController struct { beego.Controller } func (this *SessionController) Home() { isAuthenticated := this.GetSession("authenticated") if isAuthenticated == nil || isAuthenticated == false { this.Ctx.WriteString("You are unauthorized to view the page.") return } this.Ctx.ResponseWriter.WriteHeader(200) this.Ctx.WriteString("Home Page") } func (this *SessionController) Login() { this.SetSession("authenticated", true) this.Ctx.ResponseWriter.WriteHeader(200) this.Ctx.WriteString("You have successfully logged in.") } func (this *SessionController) Logout() { this.SetSession("authenticated", false) this.Ctx.ResponseWriter.WriteHeader(200) this.Ctx.WriteString("You have successfully logged out.") }次は、router を /home, /login, /logout をマッピングする。
router.gofunc init() { beego.Router("/", &controllers.MainController{}) beego.Router("/employees", &controllers.FirstController{}, "get:GetEmployees") beego.Router("/dashboard", &controllers.FirstController{}, "get:Dashboard") beego.Router("/home", &controllers.SessionController{}, "get:Home") beego.Router("/login", &controllers.SessionController{}, "get:Login") beego.Router("/logout", &controllers.SessionController{}, "get:Logout") }beego-project/main.go に redis の記述をする。
main.gopackage main import ( "github.com/astaxie/beego" _ "github.com/astaxie/beego/session/redis" _ "github.com/hoge/golang_cookcodes/beego_framework/beego-project/routers" ) func main() { beego.BConfig.WebConfig.DirectoryIndex = true beego.BConfig.WebConfig.StaticDir["/swagger"] = "swqgger" beego.Run() }Session を使うように設定変更を行う。
beego-project/conf/app.confappname = beego-project httpport = 8080 runmode = dev SessionOn = true SessionProvider = "redis" SessionProviderConfig = "127.0.0.1:6379"プロジェクトを実行する。
bee run実行すると connection refused になった。
panic: dial tcp 127.0.0.1:6379: connect: connection refusedなんでだろうと思ってしばらく考えていたが、redis をインストールしていなかった!
brew を使って、redis をローカルに入れる。
$ brew install redis # 起動 $ redis-server /usr/local/etc/redis.conf 18758:C 29 Jun 2019 21:42:33.127 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 18758:C 29 Jun 2019 21:42:33.127 # Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=18758, just started 18758:C 29 Jun 2019 21:42:33.127 # Configuration loaded _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 5.0.3 (00000000/0) 64 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in standalone mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 | `-._ `._ / _.-' | PID: 18758 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-' 18758:M 29 Jun 2019 21:42:33.130 # Server initialized 18758:M 29 Jun 2019 21:42:33.130 * DB loaded from disk: 0.001 sプロジェクトを実行し、今度は、connection refuzed にならないことを確認。
bee runターミナル上で動作確認。
$ curl -X GET http://localhost:8080/home You are unauthorized to view the page. $ curl -X GET -i http://localhost:8080/login HTTP/1.1 200 OK Server: beegoServer:1.11.2 Set-Cookie: beegosessionID=cc54cdea942085d9186be51bfd3971e6; Path=/; HttpOnly Date: Sat, 29 Jun 2019 12:46:46 GMT Content-Length: 32 Content-Type: text/plain; charset=utf-8 You have successfully logged in.$ $ curl --cookie "beegosessionID=cc54cdea942085d9186be51bfd3971e6" http://localhost:8080/home Home Page $ curl -X GET http://localhost:8080/logout You have successfully logged out.■ ログ出力
コンソールにログを出力する機能を実装する。
go get github.com/astaxie/beego/context mkdir beego-project/filters && touch beego-project/filters/filter.gofilter.gopackage filters import ( "fmt" "time" "github.com/astaxie/beego/context" ) var LogManager = func(ctx *context.Context) { fmt.Println("IP::" + ctx.Request.RemoteAddr + ", Time::" + time.Now().Format(time.RFC850)) }アクセスがあった場合、コンソールにログを出力する。
/*(なんでも) を router にマッピングする。
router.gopackage routers import ( "github.com/astaxie/beego" "github.com/hoge/golang_cookcodes/beego_framework/beego-project/controllers" "github.com/hoge/golang_cookcodes/beego_framework/beego-project/filters" ) func init() { beego.Router("/", &controllers.MainController{}) beego.Router("/employees", &controllers.FirstController{}, "get:GetEmployees") beego.Router("/dashboard", &controllers.FirstController{}, "get:Dashboard") beego.Router("/home", &controllers.SessionController{}, "get:Home") beego.Router("/login", &controllers.SessionController{}, "get:Login") beego.Router("/logout", &controllers.SessionController{}, "get:Logout") beego.InsertFilter("/*", beego.BeforeRouter, filters.LogManager) }動作確認を行う。
curl -X GET http://localhost:8080/employees [ { "id": 1, "firstName": "Foo", "LastName": "Bar" }, { "id": 2, "firstName": "Baz", "LastName": "Qux" } ]Go のアプリケーションサーバーを動かしているコンソールに、以下が出力される。
IP::[::1]:51364, Time::Saturday, 29-Jun-19 22:01:29 JST 2019/06/29 22:01:29.936 [D] [server.go:2774] | ::1| 200 | 1.256205ms| match| GET /employees r:/employees■ エラーハンドリング
エラーハンドリングは、重要。
予期しない動作を防いだり、デバッグに使えたりする。
Controller に、500 と 404 のエラーハンドラーを記述する。
touch controllers/errorcontroller.goerrorcontroller.gopackage controllers import ( "github.com/astaxie/beego" ) type ErrorController struct { beego.Controller } func (c *ErrorController) Error404() { c.Data["content"] = "Page Not Found" c.TplName = "404.tpl" } func (c *ErrorController) Error500() { c.Data["content"] = "Internal Server Error" c.TplName = "500.tpl" } func (c *ErrorController) ErrorGeneric() { c.Data["content"] = "Some Error Occurred" c.TplName = "genericerror.tpl" }firstcontroller.go に GetEmployee ハンドラーを追加する。
GetEmployee は、特定のユーザー情報を引っこ抜く関数。
firstcontroller.gofunc (this *FirstController) GetEmployee() { var ID int this.Ctx.Input.Bind(&ID, "id") var isEmployeeExist bool var emps []Employee for _, employee := range employees { if employee.ID == ID { emps = append(emps, Employee{ID: employee.ID, FirstName: employee.FirstName, LastName: employee.LastName}) isEmployeeExist = true break } } if !isEmployeeExist { this.Abort("Error Page") } else { this.Data["employees"] = emps this.TplName = "dashboard.tpl" } }router に GetEmployee をマッピングする。
router.gofunc init() { beego.Router("/", &controllers.MainController{}) beego.Router("/employees", &controllers.FirstController{}, "get:GetEmployees") // 追加 beego.Router("/employee", &controllers.FirstController{}, "get:GetEmployee") beego.Router("/dashboard", &controllers.FirstController{}, "get:Dashboard") beego.Router("/home", &controllers.SessionController{}, "get:Home") beego.Router("/login", &controllers.SessionController{}, "get:Login") beego.Router("/logout", &controllers.SessionController{}, "get:Logout") beego.InsertFilter("/*", beego.BeforeRouter, filters.LogManager) }最後にエラーを表示せるためのテンプレートを作成する。
touch views/genericerror.tplgenericerror.tpl<!DOCTYPE html> <html> <head> <title>Error</title> </head> <body> {{.content}} </body> </html>プロジェクトを起動する。
bee runhttp://localhost:8080/employee?id=1 にアクセスする。
http://localhost:8080/employee?id=1000 にアクセスする。(404)
500 の動作確認ができないッ!
■ キャッシング
キャッシングもできる。
Beego では、memory キャッシュが使えるらしい。
go get github.com/astaxie/beego/cache touch controllers/cachecontroller.gocachecontroller.gopackage controllers import ( "fmt" "time" "github.com/astaxie/beego" "github.com/astaxie/beego/cache" ) type CacheController struct { beego.Controller } var beegoCache cache.Cache var err error func init() { beegoCache, err = cache.NewCache("memory", `{"interval: 60}`) beegoCache.Put("foo", "bar", 100000*time.Second) } func (this *CacheController) GetFromCache() { foo := beegoCache.Get("foo") this.Ctx.WriteString("Hello " + fmt.Sprintf("%v", foo)) }プロジェクト起動時(bee run)に、foo(key) と bar(value) をキャッシュに追加する。
router に GetFromCache をマッピングする。
router.gofunc init() { beego.Router("/", &controllers.MainController{}) beego.Router("/employees", &controllers.FirstController{}, "get:GetEmployees") beego.Router("/employee", &controllers.FirstController{}, "get:GetEmployee") beego.Router("/dashboard", &controllers.FirstController{}, "get:Dashboard") beego.Router("/home", &controllers.SessionController{}, "get:Home") beego.Router("/login", &controllers.SessionController{}, "get:Login") beego.Router("/logout", &controllers.SessionController{}, "get:Logout") beego.InsertFilter("/*", beego.BeforeRouter, filters.LogManager) // 追加 beego.Router("/getFromCache", &controllers.CacheController{}, "get:GetFromCache") }http://localhost:8080/getFromCache にアクセスする。
bar が出てきているので、キャッシュされているみたい。
■ モニタリング機能
Beego には、モニタリング機能がついているらしい。
まずは、設定ファイルに以下を追加する。
conf/app.confEnableAdmin = true AdminAddr = "localhost" AdminPort = 8088プロジェクトを起動する。
bee runブラウザから localhost:8088 にアクセスする。
Dashboard が出てきた。
何かに使える...?
■ 動作モード
Beego はデフォルトでは、開発モードで起動するらしい。
本番用には、以下の設定を行う。
conf/app.confbeego.runmode = "prod"■ バックエンドで実行
bee run では、フォアグラウンドで実行されていたが、バックエンドで起動したい場合は、nohup コマンドを使用する。
nohup ./beego-project■ Docker
Go + Docker を学んだので、せっかくだから Docker 上で起動してみる。
beego-project フォルダと同じ階層に以下のファイルを作成する。
touch Dockerfile touch docker-compose.ymlまずは、Dockerfile を作成する。
Dockerfile# golang のイメージを取得 FROM golang:1.9.2 # ソースのコピー先(自分のリポジトリに合わせる) ENV SRC_DIR=/go/src/github.com/{username}/golang_cookcodes/beego_framework/ # ソースをコピーする ADD . $SRC_DIR # ワーキングディレクトリを変更 WORKDIR $SRC_DIR/beego-project # コンパイル RUN cd /go/src; RUN go get github.com/beego/bee RUN go get -u github.com/astaxie/beego RUN go get -u github.com/astaxie/beego/session/redis RUN go get github.com/astaxie/beego/context RUN go get github.com/astaxie/beego/cacheソースのコピー先(自分のリポジトリに合わせる)
ENV SRC_DIR=/go/src/github.com/{username}/golang_cookcodes/beego_framework/
router.go などのファイルに
import "github.com/**username**/golang_cookcodes/beego_framework/beego-project/filters"
と記述をしているので、環境依存度が高く、SRC_DIR に指定するパスが長くなってしまった。なんとかできないかと思いつつ、とりあえず、docker-compose.yml を作成。
docker-compose.ymlversion: '3' services: golang: build: . ports: - "8080:8080" container_name: mohohewo command: > bee run depends_on: - redis redis: image: redis:3.0 container_name: redisredis を docker 化したので、app.conf を書き換える。
conf/app.confappname = beego-project httpport = 8080 runmode = dev SessionOn = true SessionProvider = "redis" # docker-compose.yml に設定した container_name を指定 SessionProviderConfig = "redis:6379" EnableAdmin = true AdminAddr = "localhost" AdminPort = 8088 beego.runmode = "prod"terminal 上で、下記のコマンドを実行。(docker-compose.ymlファイルがある場所)
docker-compose uplocalhost:8000 にアクセスすると Beego のトップページが表示された。
■ Nginx の導入
最後に Web アプリケーションらしく、Nginx と繋げてみる。
beego-project フォルダと同じ階層に nginx フォルダを作成する。
mkdir nginx touch nginx/uwsgi_params mkdir nginx/conf touch nginx/conf/mohohewo_nginx.confuwsgi_params は、公式を参考に作成した。
mohohewo_nginx.conf に nginx の設定を行う。
mohohewo_nginx.conf# コネクト先 upstream beego { ip_hash; server golang:8080; } # nginx 基本設定 server { server_name 127.0.0.1; # ひとまず自分自身の IP アドレスを設定 charset utf-8; # favicon 無効 location = /favicon.ico {access_log off; log_not_found off;} # localhost にアクセスした時の設定 location / { uwsgi_pass beego; # upstream に飛ばす include /etc/nginx/uwsgi_params; # uwsgi_params ファイル読み込み proxy_pass http://golang:8080/; # upstream の server(golang:8080) と合わせないと 502 bad gateway になる } }uwsgi の設定はいるのか?とは思うが、設定はひとまず完了。
次は、docker-compose.yml に nginx を追加する。
docker-compose.ymlversion: '3' services: golang: build: . ports: - "8080:8080" container_name: mohohewo command: > bee run depends_on: - redis redis: image: redis:3.0 container_name: redis nginx: image: nginx:1.15.9-alpine ports: - "80:80" volumes: - ./nginx/conf:/etc/nginx/conf.d - ./nginx/uwsgi_params:/etc/nginx/uwsgi_params - ./nginx/log:/var/log/nginx container_name: nginx depends_on: - golangここまで完了したら docker-compose up でプログラムを実行。
docker-compose uplocalhost にアクセスする。
■ まとめ
ここで学んだことは、Beego の基本機能の一部でしかないと思う。
結構、書きやすくていい感じのフレームワークなので、色々アプリケーションを作っていこうと思う。
- 投稿日:2019-07-01T06:19:09+09:00
MongoDB.com公式Goドライバを使った基本操作
Mgoを使った例は結構あるのだけど、MongoDB.comの公式ドライバを使った例があまりないなと思い、未来の自分のために書き残しておく。タグはStackOverflowに合わせて
mongo-go
としてみた。今回のソースコード一式はhttps://github.com/mkiuchi/go-mongo-driver-exampleに置いておきました。
Mgoと公式ドライバの違いとか情報源とか
調べてないのでよくわからない。公式でも若干触れられてるけどあまりこのへんをどうこう言う気はないので気になる人は各自なんとかしてください。
公式ドライバについてはMongoDB.comのドライバページを見る。記事執筆時点の最新バージョンは 1.0.3。たぶん最もよく見るのはGoDoc, チュートリアルだとは思うんだけど、いまいち詳細さに欠けていて、これを見てもわかるようなわからないようなイマイチわかりきらないところがある。この記事を書くことにした理由の一つ。
上でも書いたけど、公式以外で私が見た限りではStackOverflowが情報源としては一番ある。あるけどほとんど会話がなく、正直あてにならない。各自頑張ってください。
インストール
公式のとおりにすれば入る。私の場合は
dep
が動かなかったのでgo get
で入れた。$ dep ensure -add "go.mongodb.org/mongo-driver/mongo@~1.0.0"$ go get -u "go.mongodb.org/mongo-driver/mongo" $ go get "go.mongodb.org/mongo-driver/bsontype"接続
以下のようにする。以下の例ではデータベースへの接続をチェックして、結果を出力する。
connect.gopackage main import ( "context" "fmt" "time" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/readpref" ) func main() { // コンテキストの作成 // - バックグラウンドで接続する。タイムアウトは10秒 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) // 関数を抜けたらクローズするようにする defer cancel() // 指定したURIに接続する c, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017")) defer c.Disconnect(ctx) // DBにPingする err = c.Ping(ctx, readpref.Primary()) if err != nil { fmt.Println("connection error:", err) } else { fmt.Println("connection success:") } }接続オプションはだいたいどの記事でも context.Background() が使用されているけど、例によっては context.TODO() も使用されていることがある。とりあえず context.Background() を使用しておけばいいと思う。
読み出し
単純に検索して1件読み出す
readone.gopackage main import ( "context" "fmt" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) func main() { // MongoDBの接続設定 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() c, _ := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017")) defer c.Disconnect(ctx) // 結果を格納する変数の構造定義 type resultType struct { Rid string Keyword string } // 結果を格納する変数を宣言 var result resultType // MongoDBのCollectionを取得 col := c.Database("rec").Collection("autorec") // 検索条件となるprimitive.ObjectID型の変数を指定 objectID, _ := primitive.ObjectIDFromHex("5cffa613c74a91322fc7cbb2") // 検索を実行し、結果を変数 result に格納 err := col.FindOne(context.Background(), bson.M{"_id": objectID}).Decode(&result) _ = err fmt.Println(result) }結果は以下のような感じになる
$ go run readone.go {abcd1234 オリンピック}複数の検索結果を読み出す
readmany.gopackage main import ( "context" "fmt" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) func main() { // MongoDBの接続設定 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() c, _ := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017")) defer c.Disconnect(ctx) // 結果を格納する変数の構造定義 type resultType struct { Start int Title string } // MongoDBのCollectionを取得 col := c.Database("rec").Collection("rec") // 検索条件となるint型の変数を指定 location, _ := time.LoadLocation("Asia/Tokyo") start := time.Date(2019, 6, 30, 0, 0, 0, 0, location).Unix() * 1000 end := time.Date(2019, 6, 30, 23, 59, 59, 99, location).Unix() * 1000 fmt.Println(start, end) // 検索を実行 cur, err := col.Find(context.Background(), bson.M{ "start": bson.M{ "$gte": start, "$lte": end, }}) _ = err // 結果のカーソルをforで回して順番に結果を取得 for cur.Next(context.Background()) { var ret resultType cur.Decode(&ret) fmt.Println(ret) } }結果は以下のようになる
$ go run readmany.go 1561820400000 1561906799000 {1561879320000 プレマップ} {1561880460000 フラッシュ天気}ドキュメント(=レコード)の挿入
insert.gopackage main import ( "context" "time" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) func main() { // MongoDBの接続設定 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() c, _ := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017")) defer c.Disconnect(ctx) // 挿入するデータの構造を定義 // 21, 22行目の末尾の文字はType aliasと呼ぶらしい。MongoDB内でフィールド名として解釈される type dataType struct { Rid string `bson:"rid"` Keyword string `bson:"keyword"` } // 挿入するデータを作成 data := dataType{ Rid: "俺のID", Keyword: "俺のキーワード", } // MongoDBのCollectionを取得 col := c.Database("rec").Collection("autorec") // データを挿入 col.InsertOne(context.Background(), data) }上記の例では構造体(struct)を挿入しているが、他の型(例えばmapとか)を直接突っ込んでも割とよろしく取り扱ってくれる
複数データのバルク挿入はまだやったことがないのでスキップ。
ドキュメント(=レコード)の削除
delete.gopackage main import ( "context" "fmt" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) func main() { // MongoDBの接続設定 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() c, _ := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017")) defer c.Disconnect(ctx) // MongoDBのCollectionを取得 col := c.Database("rec").Collection("autorec") // 検索条件となるprimitive.ObjectID型の変数を指定 objectID, _ := primitive.ObjectIDFromHex("5d1924916a81c3556cf3479b") // 検索を実行し、結果のドキュメントを削除 _, err := col.DeleteOne(context.Background(), bson.M{"_id": objectID}) if err != nil { fmt.Println("delete failed:", err) } else { fmt.Println("delete success") } }特にコメントなし。