20190701のGoに関する記事は5件です。

Go言語で「FizzBuzz」を書いてみた

背景

Go言語を学び始めてから3日程経ち、基本的な文法を学び終えたためアウトプットをしようと思った。

FizzBuzzとは

内容は1〜100までの数字を表示し、3の倍数の時は「Fizz」、5の倍数の時は「Buzz」、15の倍数の時は「FizzBuzz」と表示するというものです。
主に、プログラミング言語学習の最初の段階で学ぶ、「変数」「for文」「if文」で書けます。

「変数」「for文」「if文」を学び終えた段階で丁度良いアウトプットなのではないでしょうか。

本題

Go言語でのFizzBuzzはこのように書きます。

fizzbuzz.go
package 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を!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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)

場合に応じて差し替えが簡単になり、使いまわせるようにもなった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 share

The 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便利なのでぜひ使ってみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 run

localhost:8080 にブラウザからアクセスすると以下の画面が表示される。

スクリーンショット 2019-06-29 18.40.21.png

■ Controller の作成

Beego は、MVC タイプのフレームワークらしい。

そのため、C(Controller) を作成する。

touch beego-project/controllers/firstcontroller.go
firstcontroller.go
package 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.go
package 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 run

スクリーンショット 2019-06-29 18.46.55.png

curl でも良い。

$ 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.tpl
dashboard.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.go
func (this *FirstController) Dashboard() {
    this.Data["employees"] = employees
    this.TplName = "dashboard.tpl"
}

さっきは、JSON にデータを渡したが、今回は先に作成したテンプレートファイルにデータを渡す。

最後に router に対して、controller に記述した Dashboard 関数をマッピングする。

router.go
func init() {
    beego.Router("/", &controllers.MainController{})
    beego.Router("/employees", &controllers.FirstController{}, "get:GetEmployees")
    # 追加
    beego.Router("/dashboard", &controllers.FirstController{}, "get:Dashboard")
}

localhost:8080/dashboard にアクセスする。

スクリーンショット 2019-06-29 21.20.44.png

■ redis の導入

redis を入れて、セッション管理を行う。

go get -u github.com/astaxie/beego/session/redis

touch beego-project/controllers/sessioncontorller.go
sessioncontorller.go
package 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.go
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-project/main.go に redis の記述をする。

main.go
package 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.conf
appname = 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.go
filter.go
package 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.go
package 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.go
errorcontroller.go
package 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.go
func (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.go
func 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.tpl
genericerror.tpl
<!DOCTYPE html>
<html>
<head>
    <title>Error</title>
</head>
<body>
    {{.content}}
</body>
</html>

プロジェクトを起動する。

bee run

http://localhost:8080/employee?id=1 にアクセスする。

スクリーンショット 2019-06-30 8.00.55.png

http://localhost:8080/employee?id=1000 にアクセスする。(404)

スクリーンショット 2019-06-30 8.03.12.png

500 の動作確認ができないッ!

■ キャッシング

キャッシングもできる。

Beego では、memory キャッシュが使えるらしい。

go get github.com/astaxie/beego/cache

touch controllers/cachecontroller.go
cachecontroller.go
package 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.go
func 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 にアクセスする。

スクリーンショット 2019-06-30 8.17.11.png

bar が出てきているので、キャッシュされているみたい。

■ モニタリング機能

Beego には、モニタリング機能がついているらしい。

まずは、設定ファイルに以下を追加する。

conf/app.conf
EnableAdmin = true
AdminAddr = "localhost"
AdminPort = 8088

プロジェクトを起動する。

bee run

ブラウザから localhost:8088 にアクセスする。

スクリーンショット 2019-06-30 21.25.07.png

Dashboard が出てきた。

何かに使える...?

■ 動作モード

Beego はデフォルトでは、開発モードで起動するらしい。

本番用には、以下の設定を行う。

conf/app.conf
beego.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.yml
version: '3'

services:
    golang:
        build: .
        ports:
            - "8080:8080"
        container_name: mohohewo
        command: >
          bee run
        depends_on:
            - redis
    redis:
        image: redis:3.0
        container_name: redis

redis を docker 化したので、app.conf を書き換える。

conf/app.conf
appname = 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 up

localhost:8000 にアクセスすると Beego のトップページが表示された。

スクリーンショット 2019-06-30 22.49.05.png

■ Nginx の導入

最後に Web アプリケーションらしく、Nginx と繋げてみる。

beego-project フォルダと同じ階層に nginx フォルダを作成する。

mkdir nginx
touch nginx/uwsgi_params
mkdir nginx/conf
touch nginx/conf/mohohewo_nginx.conf

uwsgi_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.yml
version: '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 up

localhost にアクセスする。

スクリーンショット 2019-07-01 6.48.34.png

■ まとめ

ここで学んだことは、Beego の基本機能の一部でしかないと思う。
結構、書きやすくていい感じのフレームワークなので、色々アプリケーションを作っていこうと思う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.go
package 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.go
package 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.go
package 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.go
package 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.go
package 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")
    }
}

特にコメントなし。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む