Photo by Toa Heftiba on Unsplash
GoとCouchbase
で
microservicesを作るには?
2017/07/03
Yusuke Komatsu
⾃⼰紹介
⼩松祐介
所属:パーソルキャリア株式会社
github:usk81
Qiita:tienlen
お仕事:
・GoでAPIとかを作る
・おいしいコーヒーを淹れる
構成(前提)
フレームワーク
・labstack/echo
データベース
・Couchbase4.5+
・MySQL
その他
・企業秘密
labstack/echo
labstack/echo
package main
import (
"net/http"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)
func main() {
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Routes
e.POST("/users", createUser)
e.GET("/users/:id", getUser)
e.PUT("/users/:id", updateUser)
e.DELETE("/users/:id", deleteUser)
// Start server
e.Logger.Fatal(e.Start(":1323"))
}
labstack/echo
・ mainの処理書くの⾯倒
・ middleware毎回宣⾔するのも⾯倒
・ Routingをきれいにしたい
・ サーバの実⾏とHandler, routingを切り分
けたい
・ RequestIDがない(開発当時)
go-shosa/shosa
go-shosa/shosapackage main
import (
"github.com/go-shosa/shosa/router"
"github.com/go-shosa/shosa/server"
)
func main() {
// Server runs
server.Run(":1323", routes)
}
var (
routes = []router.Route{
router.Route{
Method: "POST",
Routing: "/users",
Func: createUser,
},
router.Route{
Method: "GET",
Routing: "/users/:id",
Func: getUser,
},
router.Route{
Method: "PUT",
Routing: "/users/:id",
Func: updateUser,
},
router.Route{
Method: "DELETE",
Routing: "/users/:id",
Func: deleteUser,
},
}
)
go-shosa/shosa
・ Logger
・ Middleware
・ Response
・ Server
・ Generator (go-shosa/kata作成中)
RequestIDがない!
RequestIDがない!
Couchbase?
Couchbaseとは?
・ スキーマレス
・ SQL準拠のクエリがある
・ JSONでデータが保存できる
・ 第⼆階層にダイレクトにアクセスできる
・ オフライン設計ができている
・ マルチマスター構造
・ Gitのようにデータが履歴管理されている
・ なんかよくわかんないけど速い
MySQLからの移⾏
type Standard struct {
ID int
Name string
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}
=> うごきません!!
MySQLからの移⾏
cluster, _ := gocb.Connect("couchbase://localhost")
bucket, _ := cluster.OpenBucket("default", "")
query := gocb.NewN1qlQuery("SELECT * FROM default")
rows, _ := bucket.ExecuteN1qlQuery(query)
var row interface{}
for rows.Next(&row) {
fmt.Printf("Row: %+vn", row)
}
rows.Close()
See. https://blog.couchbase.com/go-sdk-1-0-ga/
usk81/generic
usk81/generic
・ NULL判定を勝⼿にしてくれる
・ 緩やかな型解釈
・ とりあえず⼀通りのTypeは存在
・ Marshal/Unmarshal対応
MySQLからの移⾏
type Standard struct {
ID int
Name string
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt generic.Time
}
fixtureを使ったテスト(MySQL)
1.fixtureからデータをロード
2.テスト実⾏する
3.truncate table ⽂を実⾏する
fixtureを使ったテスト(MySQL)
func TestMain(m *testing.M) {
// setup database
db, err := database.Connect()
if err != nil {
log.Fatalf("failed to connect database. %v", err)
}
Tx = db.Begin()
err = testfixtures.LoadFixtures(FixturesPath, db.DB(), &testfixtures.MySQLHelper{})
if err != nil {
log.Fatalf("failed to load fixtures. %v", err)
}
defer func() {
Tx.Rollback()
database.Close(db)
}()
retCode := m.Run()
tearDown(Tx)
os.Exit(retCode)
}
func tearDown(d *gorm.DB) {
d.Exec(fmt.Sprintf("TRUNCATE TABLE %s;", table))
os.Unsetenv("testing")
}
fixtureを使ったテスト(Couchbase)
1.fixtureからデータをロード
2.テスト実⾏する
3.データをflushする(データを削除する)
fixtureを使ったテスト(Couchbase)
1.fixtureからデータをロード
・そんな処理もパッケージもない
・レスポンスが返るのはInsert完了前
2.テスト実⾏する
3.データをflushする(データを削除する)
・このためのパッケージはない
・レスポンスが返るのは処理完了前
・データの⼀括作成・削除を繰り返すと遅延する
・そもそもFlushは開発向けオプション
・こんなことのためにあるのではない
fixtureを使ったテスト(Couchbase)
// Import imports data from json files in specified directory.
func Import(bucket *gocb.Bucket, path string) error {
files, err := ioutil.ReadDir(path)
if err != nil {
return err
}
for _, fi := range files {
filename := fi.Name()
if !fi.IsDir() && validateJSONFile(filename) {
bs, err := ioutil.ReadFile(filepath.Join(path, filename))
if err != nil {
return err
}
var req map[string]interface{}
err = json.Unmarshal(bs, &req)
if err != nil {
return err
}
for k, v := range req {
if _, err = bucket.Upsert(k, v, 0); err != nil {
return err
}
}
}
}
return nil
}
func validateJSONFile(filename string) bool {
match, _ := regexp.MatchString(".json$", filename)
return match
}
fixtureを使ったテスト(Couchbase)
// Flush will delete all the of the data from a bucket.
func Flush(bucket *gocb.Bucket, username, password string) error {
bm := bucket.Manager(username, password)
return bm.Flush()
}
// IsActive checks bucket is active or not
func IsActive(bucket string) (result bool, err error) {
req, err := BucketStat(bucket)
if err != nil {
return
}
b, err := json.Marshal(req)
if err != nil {
return
}
var rq CBNodeStatus
err = json.Unmarshal(b, &rq)
if err != nil {
return
}
for _, n := range rq.Nodes {
if n.Status != "healthy" {
return false, nil
}
}
return true, nil
}
fixtureを使ったテスト(Couchbase)
func TestMain(m *testing.M) {
(省略)
// import data
err = couchbase.Import(b, FixturesPath)
if err != nil {
log.Fatalf("failed to load fixtures. %v", err)
}
// waiting couchbase server warmup
r := false
i := 0
for !r {
if i > 0 {
if i >= 10 {
log.Panic("timeout loading data")
}
log.Printf("loading data...%s", strings.Repeat(".", i))
time.Sleep(6 * time.Second)
}
r, err = couchbase.IsActive(bucketNameTest)
if err != nil {
log.Fatalf("failed to connect database (by management API). %v", err)
}
i++
}
// Insertが完了する前にhealthyに戻るようなので、念のため
time.Sleep(5 * time.Second)
(省略)
retCode := m.Run()
tearDown(b)
os.Exit(retCode)
}
Thank you!!

Go と Couchbase で microservices を作るには?