- 投稿日:2021-03-14T22:09:48+09:00
17rep - GolangでMySQLのトランザクションラッパー作成
使うシーン
- 一括で複数のInsert Updateで全部成功させないといけないシーン
- リレーションを貼っているテーブルに対してデータの更新や追加などを一括して追加変更しないといけないテーブルが存在するシーン
Go言語でトランザクション制御
- トランザクション制御のラッパー関数の作成
- トランザクションラッパー関数を開始し、エラーだったらrollbackを実行エラーじゃなければcommit
Begin()、Commit()、Rollback()が同じ関数内に定義(これは冗長)
- SqlHandlerポインタレシーバからDB接続を定義し同じ関数内で実行
func (h *SqlHandler) WithTransaction() (err error) { tx, err := h.db.Begin() if err != nil { return } defer func() { if err != nil { tx.Rollback() return } err = tx.Commit() }() if _, err = tx.Exec(...); err != nil { return } if _, err = tx.Exec(...); err != nil { return } // ... return } func (t *sql.Tx)Exec(...){ // Insert or Update SQL // ... }トランザクションラッパー関数
- 実行したい関数を引数 txFuncとして受け取り、トランザクションを開始してからtxFuncを実行
- txFuncの中でerrorもしくはpanicが起きたらdeferでrecoverを使って広い、rollbackを実行
- 問題がなければcommitという流れです。
- トランザクション制御を行う際に、トランザクション境界線がリクエストの開始〜終了となりますが、ラッパー関数方式の場合トランザクション境界を柔軟に変更可能
func (t *sqlRepository) ExecWithTx(txFunc func(*sql.Tx) error) (err error) { tx, err := t.db.Begin() if err != nil { return } defer func() { if p := recover(); p != nil { tx.Rollback() } else if err != nil { tx.Rollback() } else { err = tx.Commit() } }() err = txFunc(tx) return err }トランザクションラッパー関数使い方(サンプル)
- 実行したいSQL関数を引数txFunc内に記述
func (r *Repository) DoSomething() error { return r.DB.ExecWithTx(func(tx *sql.Tx) error { if _, err := tx.Exec(...); err != nil { // ここにSQL系関数定義 return err } if _, err := tx.Exec(...); err != nil { // ここにSQL系関数定義 return err } return nil }) }参考資料
- 投稿日:2021-03-14T21:22:24+09:00
【Go言語/golang】ブロックチェーンの実装
※備忘録として記載しますので、ご容赦ください。
package main import ( "bytes" "crypto/sha256" "fmt" "strconv" "time" ) type Block struct { Timestamp int64 Data []byte PrevBlockHash []byte Hash []byte } func (b *Block) SetHash() { timestamp := []byte(strconv.FormatInt(b.Timestamp, 10)) headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{}) hash := sha256.Sum256(headers) b.Hash = hash[:] } func NewBlock(data string, prevBlockHash []byte) *Block { block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}} block.SetHash() return block } type Blockchain struct { blocks []*Block } func (bc *Blockchain) AddBlock(data string) { prevBlock := bc.blocks[len(bc.blocks)-1] newBlock := NewBlock(data, prevBlock.Hash) bc.blocks = append(bc.blocks, newBlock) } func NewGenesisBlock() *Block { return NewBlock("Genesis Block", []byte{}) } func NewBlockchain() *Blockchain { return &Blockchain{[]*Block{NewGenesisBlock()}} } func main() { bc := NewBlockchain() bc.AddBlock("Send 1 BTC to Ivan") bc.AddBlock("Send 2 more BTC to Ivan") for _, block := range bc.blocks { fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash) fmt.Printf("Data: %s\n", block.Data) fmt.Printf("Hash: %x\n", block.Hash) fmt.Println() } }
- 投稿日:2021-03-14T21:06:48+09:00
【Go/golang】sync.Mutex / sync.WaitGroupで排他制御をする
※本記事は備忘録として記述しておりますので、ご容赦ください。
sync.Mutex
次のコードは、IDを作成するコードが同時に1つしか実行されないようにしています。
※goroutineにmutex構造体を渡すときは、必ずポインタでわたすこと。
値のまま渡してしまうと、ロックしている状態のまま別のsync.Mutexインスタンスを渡すことになってしまうので、注意が必要です。package main import ( "fmt" "sync" "time" ) var id int func generateID(mutex *sync.Mutex) int { // Lock() / Unlock()をペアで呼び出してロックする mutex.Lock() defer mutex.Unlock() id++ result := id return result } func main(){ // sync.Mutex構造体の変数宣言 // 次の宣言をしてもポインタ型になるだけで正常に動作する // mutex := new(sync.Mutex) var mutex sync.Mutex for i := 0; i < 100; i++ { go func() { fmt.Printf("id: %d\n", generateID(&mutex)) }() } // 100まで出力させたいなら終了待ちの操作が必要 // time.Sleep(4 * time.Second) }# 実行結果 id: 1 id: 5 id: 6 id: 2 id: 7 id: 4 id: 3 id: 8 id: 10 id: 11 id: 12 id: 13 id: 14 id: 15 id: 16 id: 17 id: 18 id: 9 id: 19 id: 20 ... 省略 # goroutineの終了待ちをしていないので、100個に到達する前に終了してしまうことがあるロックしたままの値を渡していないか確認するには、
go vet
を実行することで見ることができる。Mutexとチャネルの使い分け
- チャネルが有用な場合
- 非同期で結果を受け取る場合
- Mutexが有用な場合
- キャッシュ、状態管理
sync.WaitGroup
多数のgoroutineで実行しているジョブの終了待ちに使います。
※goroutineにwg構造体を渡すときは、Mutex同様必ずポインタでわたすこと。package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup // ジョブ数をあらかじめ登録 (必ずgoroutineを作成する前に呼ぶ) // Doneが呼ばれるたびに、数がデクリメントされていく // Waitはこの中の数が0になるのを待機している wg.Add(2) go func() { // 非同期で仕事をする fmt.Println("仕事1") // Doneで完了を通知 wg.Done() }() go func() { // 非同期で仕事をする fmt.Println("仕事2") // Doneで完了を通知 wg.Done() }() // 全ての処理が終わるのを待つ wg.Wait() fmt.Println("完了") }# 出力結果 (仕事1と仕事2は順不同で出力される) 仕事2 仕事1 完了 # Mutexと違い、終了待ちをしているため、確実にgoroutineの終了を確認できるWaitGroupとチャネルの使い分け
- WaitGroupの方が良い場合
- ジョブ数が大量にある場合
- 可変個の場合
- 投稿日:2021-03-14T20:48:29+09:00
GoでO/Rマッパーと生SQLの違いを比較してみた
はじめに
Ruby on Railsを学習している方はActiveRecordという名称のORマッパーを目にする事が度々あると思います。
私もプログラミングの学習をRubyから始めたのですが、ORマッパーと聞くとなんとなく「DBからのデータの取り出しを簡単にやってくれているんだな」くらいの認識でした。
Railsがあまりに様々な事をよしなにしてくれるので、Rails初学者の方は生のSQLを書く機会もほとんどないのではないかと思います。
最近Goを勉強し始めた事で、そのRailsがよしなにしてくれていた部分を基本から学び直す必要が出てきたので、ORMに関しても一つ学び直してここに備忘録として残したいと思います。対象者
- プログラミング初学者
- SQLは書いた事があるがORMは利用した事がない。あるいはORMは利用した事があるが生のSQLを書く事はあまりない。
概要
Goのデータベースとのやり取りを、生のSQLで行った場合とORマッパーを利用した場合とでどうコードの違いが出るのかを比較して見ていきます。
Goを扱っていますが、簡単なコードなのでGoを触った事が無い方でも理解出来ると思います。
データベースはPostgreSQL、ORMはgormを使用します。環境
Go 1.16
PostgreSQL 13.0コード全容
store.goが生SQLで記述した場合。
store2.goがORMを使用した場合のコードです。setup.sqlcreate table users ( id serial primary key, name text ); create table posts ( id serial primary key, content text, user_id integer references users(id) );store.gopackage main import ( "database/sql" "fmt" _ "github.com/lib/pq" ) // テーブルに対応した構造体を定義 type User struct { Id int Name string Posts []Post } type Post struct { Id int Content string User *User } // データベースへ接続 var Db *sql.DB var err error func init() { Db, err = sql.Open("postgres", "user=test dbname=test password=test sslmode=disable") if err != nil { panic(err) } } func main() { // ユーザデータの作成 user := User{Name: "堀口太郎"} sql := "INSERT INTO \"users\" (name) values ($1) RETURNING id" Db.QueryRow(sql, user.Name).Scan(&user.Id) // ポストデータの作成 post := Post{Content: "どうも皆さんこんにちは", User: &user} sql2 := "INSERT INTO posts (content, user_id) values ($1, $2)" Db.Exec(sql2, post.Content, post.User.Id) // ユーザの取得 var readUser User sql3 := "SELECT id, name FROM users WHERE name = $1" usename := "堀口太郎" Db.QueryRow(sql3, usename).Scan(&readUser.Id, &readUser.Name) // ユーザからコメント取得 var posts []Post sql4 := "SELECT Id, Content FROM posts WHERE user_id = $1" rows, _ := Db.Query(sql4, readUser.Id) for rows.Next() { post := Post{User: &readUser} rows.Scan(&post.Id, &post.Content) posts = append(posts, post) } }store2.gopackage main import ( "fmt" "github.com/jinzhu/gorm" _ "github.com/lib/pq" ) // テーブルに対応した構造体を定義 type User struct { Id int Name string Posts []Post } type Post struct { Id int Content string UserId int } // データベースへ接続 var Db *gorm.DB func init() { var err error Db, err = gorm.Open("postgres", "user=test dbname=test password=test sslmode=disable") if err != nil { panic(err) } // 構造体の内容を基にテーブルを自動作成 Db.AutoMigrate(&User{}, &Post{}) } func main() { // ユーザデータの作成 user := User{Name: "山田太郎"} Db.Create(&user) // ポストデータの作成 post := Post{Content: "どうも皆さんこんにちは"} Db.Model(&user).Association("Posts").Append(post) // ユーザの取得 var readUser User username := "山田太郎" Db.Where("name=$1", username).First(&readUser) // ユーザからコメント取得 var posts []Post Db.Model(&readUser).Related(&posts) }ORMとは
オブジェクト関係マッピング(英: Object-relational mapping, O/RM, ORM)とは、データベースとオブジェクト指向プログラミング言語の間の非互換なデータを変換するプログラミング技法である。オブジェクト関連マッピングとも呼ぶ。実際には、オブジェクト指向言語から使える「仮想」オブジェクトデータベースを構築する手法である。 - Wikipedia
簡単に言うと、オブジェクト指向(Object)とリレーショナルデータベース(Relational)の対応付け(Mapping)を行う事でデータベースから取得するレコードデータをオブジェクト化し、直感的に扱えるようにしたものになります。
なぜそのような対応付けが必要になるかというと、現実世界のモデルに即したデータモデルであるオブジェクト指向と、データのCRUD処理に最適化されたデータモデルであるRDBではその設計思想の違いにより、インピーダンスミスマッチが発生してしまう為と言われています。
解説
※store.goが生SQLで作成したコード、store2.goがgormを使用したコードになります。
今回はとてもシンブルなテーブル構造のデータベースに対してCreate,及びReadを行った場合の違いを比較していきます。
データベース側はtestという名称のユーザ、同じくtestという名称のDBをそれぞれ作成しています。
テーブル以下のUserとPostの二つです。
User id name
Post id content user_id 次に、この二つのテーブルを作成していきます。
ORMを利用しない場合は以下スクリプトファイルを作成し、データベース側でテーブルの作成を行います。(Go側で作成する事も可能ですが、今回はデータベース側行います。)setup.sqlcreate table users ( id serial primary key, name text ); create table posts ( id serial primary key, content text, user_id integer references users(id) );以下のコマンドを実行し、テーブルを作成します。
$ psql -U test -f .\setup.sql -d test対して、ORMを利用した場合の処理を見ていきます。
store2.gotype User struct { Id int Name string Posts []Post } type Post struct { Id int Content string UserId int }store2.goDb.AutoMigrate(&User{}, &Post{})テーブルの作成処理を行っている部分は上記のコードになります。
レコードを格納するための構造体を定義する事で、その構造体を利用してテーブルの作成を行う事が出来ます。次に実際のデータの操作を見ていきます。
Create
store.go// ユーザデータの作成 user := User{Name: "堀口太郎"} sql := "INSERT INTO \"users\" (name) values ($1) RETURNING id" Db.QueryRow(sql, user.Name).Scan(&user.Id) // ポストデータの作成 post := Post{Content: "どうも皆さんこんにちは", User: &user} sql2 := "INSERT INTO posts (content, user_id) values ($1, $2)" Db.Exec(sql2, post.Content, post.User.Id)store2.go// ユーザデータの作成 user := User{Name: "堀口太郎"} Db.Create(&user) // ポストデータの作成 post := Post{Content: "どうも皆さんこんにちは"} Db.Model(&user).Association("Posts").Append(post)Read
store.go// ユーザの取得 var readUser User sql3 := "SELECT id, name FROM users WHERE name = $1" usename := "堀口太郎" Db.QueryRow(sql3, usename).Scan(&readUser.Id, &readUser.Name) // ユーザからコメント取得 var posts []Post sql4 := "SELECT Id, Content FROM posts WHERE user_id = $1" rows, _ := Db.Query(sql4, readUser.Id) for rows.Next() { post := Post{User: &readUser} rows.Scan(&post.Id, &post.Content) posts = append(posts, post) }store2.go// ユーザの取得 var readUser User username := "堀口太郎" Db.Where("name=$1", username).First(&readUser) // ユーザからコメント取得 var posts []Post Db.Model(&readUser).Related(&posts)どのコードを比較しても、ORMを使用した場合は短く、そして直感的に理解しやすいコードになっているかと思います。
まとめ
実際にORMを利用した場合と生のSQLを利用した場合でどのような違いが出るのか簡単にではありますが比較してみました。
今回はとても簡単なコードで比較したので一見メリットしかないように見えますが、ORMにも様々なデメリットが存在しています。
今回はその点については触れませんが、調べてみると賛否両論色んな意見が出てくると思いますので興味がある方は是非調べて見て下さい。
また、私自身Goの学習を始めたばかりなので気になる点があれば是非ご指摘頂けると幸いです。
- 投稿日:2021-03-14T20:22:13+09:00
goroutineで値(変数)を共有する
go routineのスレッド間でデータを共有する
※本記事は備忘録として記載したものでありますため、ご容赦ください。
goroutineで動作している関数でデータを共有するには、
1. 引数として渡す方法
2. クロージャのローカル変数にキャプチャして渡す方法
があります。package main import ( "fmt" "time" ) func sub1(c int) { fmt.Println("share by arguments : ", c*c) } func main() { // 引数で渡す方法 go sub1(10) // クロージャのキャプチャで渡す方法 c := 20 go func() { fmt.Println("share by capture : ", c*c) }() time.Sleep(time.Second) }# 結果 share by arguments : 100 share by capture : 400クロージャのキャプチャ渡しの場合も、内部的には無名関数に引数が追加されて参照が渡されるため、厳密には同じ動きとなります。
forを使用してみる
package main import ( "fmt" "time" ) func main() { tasks := []string{ "cmake ..", "cmake . --build release", "cpack", } for _, task := range tasks { go func() { // goroutineが起動する時にはループが回り切って // 全部のtaskが最後のタスクになってしまう fmt.Println(task) }() } time.Sleep(time.Second) }# 結果 cpack cpack cpack高速ではありますが、単純なループに比べてgoroutineの起動が遅いため、taskに"cpack"が入った状態でループが回ってしまう。
このような場合は、引数経由にして明示的に値をコントロールするべきです。子goroutineから親goroutineへの値渡し
両者は共有アドレスを参照しているため、上書きに注意が必要です。
参考
- 投稿日:2021-03-14T19:39:42+09:00
DynamoDBでインクリメンタルなIDを管理する場合の実装
概要
状況としては以下の様な場合を想定
- 各グループ毎にインクリメンタルなIDを管理する
- グループの設定を管理するテーブルに最新のIDを保存
結論
更新式でADDを使ってインクリメントしてIDを生成する
※ただしIDの間が抜ける可能性があるIDの抜けが許容できない場合はトランザクションでUpdateとPutする
※Lockをを考慮する必要がある1. インクリメントするやり方
サンプルはGoとguregu/dynamoを使用したコードとなります
var domainConfig DomainConfig err := configTable.Update(hashkey, target). Add("CurrentID", 1). Value(&domainConfig) if err != nil { // error処理 } data.ID = domainConfig.CurrentID err = dataTable.Put(data).Run() if err != nil { // error処理 }上記の書き方で現在のIDに+1した値が確実に取得できるので、このIDをデータにセットすればグループ毎にインクリメンタルなIDを使うことができる
ただし、取得した後のPut処理が失敗した場合、取得したIDが使われないケースはある2. トランザクションを使って確実にIDをセットするやり方
DynamoDBのトランザクションはGet系と更新系でしかまとめられないので、Getしてから設定をUpdate、データをPutするところまで全てを同一のトランザクションに入れることはできない
なので条件付きのUpdate処理を使うことでデータが既に更新されている場合は更新しないようにする
以下の様にトランザクションを使ってUpdateとPutを実行し、かつ設定をUpdateする条件にCurrentIDが変更されていない時のみとすればIDが先に更新されてしまった場合はエラーとなるのでIDがずれることはなくなる
ただし、先に他の処理に更新されてしまった場合や、トランザクション処理中にDBアクセスした時はエラーとなるため、リトライ処理を検討する
大量のバッチ処理時にはロックをかけて複数の処理が同時に実行されないような工夫をしないと、大量にエラーとなってしまうので注意するerr := configTable.Get(hashkey, domainID).One(&domainConfig) if err != nil { // error処理 } data.ID = domainConfig.CurrentKey + 1 tx := db.WriteTx() // dbは *dynamo.DB tx.Update(configTable.Update(hashkey, domainID). Set(currentID, key). If("$ = ?", "CurrentID", domainConfig.CurrentID)) // CurrentIDが変更されていない時のみ更新する tx.Put(dataTable.Put(data)) err = tx.Run() if err != nil { // error処理 }
- 投稿日:2021-03-14T18:49:09+09:00
Goで実装したHyperledger FabricのChaincodeを単体テストしたい
はじめに
Hyperledger FabricのChaincodeをネットワークにデプロイする前に、機能チェックしておきたい。
ということで、Go初学者がFabricのChaincodeを題材にして、Goで単体テストする方法を書き残しました。
環境
- Hyperledger Fabric v2.2.0
- サンプルソース、バイナリツール、Dockerイメージのインストール方法はこちら
- Go 1.15.8
- Visual Studio Codeのおすすめ拡張機能
注意
本記事では、Chaincode開発用のGoパッケージとしてshimの利用を前提としていますが、v2.0以降ではshimより高水準なAPIとしてcontractapiが提供されています。
今後はこちらを使っていく方が望ましいかもしれません。(参考)
What is the difference between fabric-chaincode-go and fabric-contract-api-go?
https://stackoverflow.com/questions/60900762/what-is-the-difference-between-fabric-chaincode-go-and-fabric-contract-api-go準備知識
Goのパッケージについて
FabricのChaincode開発ではshimパッケージが提供するAPIを使います。
Goでは依存パッケージを
import
で指定します。import ( "github.com/hyperledger/fabric-chaincode-go/shim" )
- コマンド
$go mod tidy
で、import
で記載したパッケージをダウンロードできます。このときgo.mod
とgo.sum
という2つのファイルも更新されます。
go.mod
は依存パッケージを管理するファイル。go.sum
はパッケージに更新があったかを判断するためのファイルで、依存モジュールのチェックサムが記録される。- パッケージ自体は
$GOPATH/pkg/mod
配下に格納されます。- コマンド
$go get
でも任意のパッケージを引数に指定してダウンロードできます。ただし、$go mod tidy
の方がimport
で記載した依存パッケージに応じて過不足なくダウンロードできる点が良さそうです。Goのテストについて
- Goは標準で自動テストが組み込まれています。(コマンド
$go test
)- カバレッジやベンチマーク測定なども、オプションで組み込まれています。(
-cover
、-bench
)- テストする上で命名規則が決まってます。
XXX.go
がテスト対象の場合、テストプログラムのファイル名はXXX_test.go
とする。- テスト関数名の接頭に
Test
をつける。- テストプログラムはテスト対象と同じパッケージにします。
XXX.go
と同ディレクトリにXXX_test.go
を置いておくのが吉。(参考)
はじめてのGoTest(Qiita)
https://qiita.com/marnie_ms4/items/e51cc6d879cc9ad07af3GoによるChaincode開発/単体テスト
既に述べた通り、Chaincode開発ではshimパッケージを使います。
そして、Chaincodeの単体テストではshimtestパッケージを使います。Fabric v1.4.xでの注意点
Fabric v1.4.xではパッケージ体系が異なるのでご注意ください。
Fabric v2.x以降から変更があったようです。
v1.4.x
v2.x以降
また、v1.4.xではshimに単体テスト用のAPIも含まれていて、shimtestは提供されていません。
v1.4.xで開発/単体テストする場合は、バージョンを指定してパッケージをダウンロードしましょう。
$ go mod init ... $ go get github.com/hyperledger/fabric/core/chaincode/shim@v1.4(参考)
Work with Hyperledger Fabric Chaincode Development Tools #4415
https://github.com/SAPDocuments/Tutorials/issues/4415実践
ここではmarbles02(Tag v2.2.0)の単体テストを実行する方法を記します。(カバレッジ100%を目指すものではありません)
サンプルコードを使うのでテスト対象の
marbles_chaincode.go
は実装完了しているものとします。単体テスト
marbles_chaincode.go
と同じディレクトリに、命名規則に従ってテストプログラム(空ファイル)を作成します。$ cd fabric-samples/chaincode/marbles02/go/ $ touch marbles_chaincode_test.go一部メソッドのみを対象とした、簡易なテストコードを実装します。
Chaincodeの実装ではバイト配列を扱うことが多いので、標準パッケージの他にも幾つかimportしてます。marbles_chaincode.gopackage main import ( "strconv" // 文字列変換を扱う標準パッケージ "strings" // 文字列を扱う標準パッケージ "testing" // テスト用の関数・構造体を扱う標準パッケージ simplejson "github.com/bitly/go-simplejson" // JSON型式を扱うパッケージ "github.com/stretchr/testify/assert" // アサーション機能を扱うパッケージ funk "github.com/thoas/go-funk" // Sliceに対する便利機能(Map, Filterなど)を扱うパッケージ "github.com/hyperledger/fabric-chaincode-go/shim" "github.com/hyperledger/fabric-chaincode-go/shimtest" ) var simpleChaincode = new(SimpleChaincode) var mockStub = shimtest.NewMockStub("Test Creation", simpleChaincode) func TestInitMarble(t *testing.T) { t.Run("success InitMarble()", func(t *testing.T) { functionName := "initMarble" marbleName := "marble1" marbleColor := "blue" marbleSize := "35" marbleOwner := "tom" functionAndArgs := [][]byte{[]byte(functionName), []byte(marbleName), []byte(marbleColor), []byte(marbleSize), []byte(marbleOwner)} // Invoke Function res := mockStub.MockInvoke("InitMarble001_1", functionAndArgs) if res.Status != shim.OK { t.Error("Invoke", functionAndArgs, "failed", string(res.Message)) t.FailNow() } else { functionAndArgsMap := funk.Map(functionAndArgs, func(b []byte) string { return string(b) }) t.Log("Invoke", "(", strings.Join(functionAndArgsMap.([]string), ", "), ")", "successful", string(res.Message)) } // Check StateDB bytes, err := mockStub.GetState(marbleName) if err != nil { t.Error("Failed to get state") t.FailNow() } else if bytes == nil { t.Error("State", marbleName, "failed to get value") t.FailNow() } state, err := simplejson.NewJson(bytes) assert.Equal(t, marbleName, state.Get("name").MustString()) assert.Equal(t, marbleColor, state.Get("color").MustString()) assert.Equal(t, marbleSize, strconv.Itoa((state.Get("size").MustInt()))) assert.Equal(t, marbleOwner, state.Get("owner").MustString()) }) } func TestReadMarble(t *testing.T) { t.Run("success ReadMarble()", func(t *testing.T) { functionName := "readMarble" marbleName := "marble1" marbleColor := "blue" marbleSize := "35" marbleOwner := "tom" functionAndArgs := [][]byte{[]byte(functionName), []byte(marbleName)} // Invoke Function res := mockStub.MockInvoke("ReadMarble001_1", functionAndArgs) if res.Status != shim.OK { t.Error("Invoke", functionAndArgs, "failed", string(res.Message)) t.FailNow() } else { functionAndArgsMap := funk.Map(functionAndArgs, func(b []byte) string { return string(b) }) t.Log("Invoke", "(", strings.Join(functionAndArgsMap.([]string), ", "), ")", "successful", string(res.Message)) } // Check Response payload := res.Payload value, _ := simplejson.NewJson(payload) assert.Equal(t, marbleName, value.Get("name").MustString()) assert.Equal(t, marbleColor, value.Get("color").MustString()) assert.Equal(t, marbleSize, strconv.Itoa((value.Get("size").MustInt()))) assert.Equal(t, marbleOwner, value.Get("owner").MustString()) }) }依存パッケージダウンロード
テストコードを書いたら依存パッケージをダウンロードします。
go.mod
がなければ$ go mod init
で作成する必要があります。
ただし、marbles02(Tag v2.2.0)では既にgo.mod
も含まれているため省略します。
$ go mod tidy
コマンドで必要なパッケージをダウンロードしましょう。$ go mod tidyテスト実行
コマンド
$ go test
でテスト実行です。$ go test > invoke is running initMarble > - start init marble > - end init marble > invoke is running readMarble > PASS > ok github.com/hyperledger/fabric-samples/chaincode/marbles02/go 0.006sカバレッジ取得
カバレッジ結果はファイルに出力できます。
HTMLに変換することもでき、カバーされている箇所を見やすくできます。$ go test -coverprofile=cover.out . > ok github.com/hyperledger/fabric-samples/chaincode/marbles02/go 0.005s coverage: 15.0% of statements $ go tool cover -html=cover.out -o cover.html(参考)
Go でコードカバレッジを取得する
https://qiita.com/kkohtaka/items/965fe08821cda8c9da8aVisual Studio Codeなら、html-preview-vscodeという拡張機能でHTMLをプレビューできます。ブラウザ要らずで便利です。
Some content has been disabled in this document
というメッセージが表示される場合は、「change Preview Security Settings」を「Disable」に設定しましょう。(参考)
VSCodeのhtmlPreviewでscriptタブの箇所がうまく表示されない場合の解決方法
https://entlife.net/2020/10/26/vscode%E3%81%AEhtmlpreview%E3%81%A7script%E3%82%BF%E3%83%96%E3%81%AE%E7%AE%87%E6%89%80%E3%81%8C%E3%81%86%E3%81%BE%E3%81%8F%E8%A1%A8%E7%A4%BA%E3%81%95%E3%82%8C%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88/おわり
個人的にはGoの勉強になりました。
- 投稿日:2021-03-14T09:53:12+09:00
Go module の Dependency 問題を解決する
今日はGoの依存関係で躓いたので、次回躓かないようにいろいろ試してみる。
go mod initの後次の簡単なk8s client のプログラムを書いて実行する。サンプルのまんまなのに、失敗する。
package main import ( "flag" "fmt" "path/filepath" "time" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" ) func main() { var kubeconfig *string if home := homedir.HomeDir(); home != "" { kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file") } else { kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file") } flag.Parse() config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) if err != nil { panic(err.Error()) } clientset, err := kubernetes.NewForConfig(config) if err != nil { panic(err.Error()) } for { pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{}) if err != nil { panic(err.Error()) } fmt.Printf("Tehre are %d pods in the cluster\n", len(pods.Items)) namespace := "default" pod := "nginx" _, err = clientset.CoreV1().Pods(namespace).Get(pod, metav1.GetOptions{}) if errors.IsNotFound(err) { fmt.Printf("Pod %s is namespace %s not found\n", pod, namespace) } else if statusError, isStatus := err.(*errors.StatusError); isStatus { fmt.Printf("Error getting pod %s in namespace %s: %v\n", pod, namespace, statusError.ErrStatus.Message) } else if err != nil { panic(err.Error()) } else { fmt.Printf("Found pod %s in namespace %s\n", pod, namespace) } time.Sleep(10 * time.Second) } }result
$ go run main.go build command-line-arguments: cannot load github.com/googleapis/gnostic/OpenAPIv2: module github.com/googleapis/gnostic@latest found (v0.5.4), but does not contain package github.com/googleapis/gnostic/OpenAPIv2なんだこれ?多分該当のバージョンが壊れていることが推察される。
今は解決しているわけだが、その場その場の解決策がわかっても意味がないので、実際に自分がやったことを書いてみる。
最初に思ったことは、
github.com/googleapis/gnostic/OpenAPIv2
を使っている依存関係を調べてそのライブラリをバージョンアップ、もしくはダウンすればよいということ。go では明確に依存関係のグラフみたいなものが出るのもが見つからなかった。go mod why
が説明的にはそうなのだが、うまく動作していないように感じる。StackOverflowも同じようなことを言っていた。いろいろしたが、本来するべきだった手順は、きっと
go.mod
を見ることだろう。module simplearchitect.com go 1.13 require ( github.com/imdario/mergo v0.3.12 // indirect golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect k8s.io/api v0.20.4 // indirect k8s.io/apimachinery v0.20.4 k8s.io/client-go v11.0.0+incompatible k8s.io/klog v1.0.0 // indirect k8s.io/utils v0.0.0-20210305010621-2afb4311ab10 // indirect )// indirect というのがあるが、これは私が直接使っていないもの。
+incompatible
は go module に対応していないものなので怪しいところ。もし go module の依存関係がツリーで出てくるものがあれば最高なのだが、見つからなかったので、直接使っているライブラリの go.mod をGitHub のページに行って、それぞれ調査する。少なくとも、master/main のgithub.com/googleapis/gnostic
のバージョンはv0.4.1
であり、v0.5.4
ではない。k8s.io/client-go
k8s.io/apimachinery
の双方が依存しているらしいが、どちらも、v0.4.1
を使っている。不思議である。では、次のステップとして、
github.com/googleapis/gnostic@v0.4.1
を試してみるが、同じエラーが出る。おそらく何かがv0.5.4
を参照しているからであろう。次に考えたのがv0.5.4
を削除してみようということ。go mod tidy
を使うと参照がキレた依存関係を整理してくれる。$ go mod tidy simplearchitect.com imports k8s.io/client-go/kubernetes imports k8s.io/client-go/discovery imports github.com/googleapis/gnostic/OpenAPIv2: module github.com/googleapis/gnostic@latest found (v0.5.4), but does not contain package github.com/googleapis/gnostic/OpenAPIv2 simplearchitect.com imports k8s.io/client-go/kubernetes imports k8s.io/client-go/kubernetes/typed/auditregistration/v1alpha1 imports k8s.io/api/auditregistration/v1alpha1: module k8s.io/api@latest found (v0.20.4), but does not contain package k8s.io/api/auditregistration/v1alpha1 simplearchitect.com imports k8s.io/client-go/kubernetes imports k8s.io/client-go/kubernetes/typed/settings/v1alpha1 imports k8s.io/api/settings/v1alpha1: module k8s.io/api@latest found (v0.20.4), but does not contain package k8s.io/api/settings/v1alpha1うむ。これはなかなかわかりやすい。近づいている。例のライブラリは、
k8s.io/client-go
が使用している。では、v11.0.0
はどんなバージョンだろうとclient-go のサイトにいくと、意味が分からないが最新 v12.0.0 とかあるのだが、(日付に注意)クリックすると、最新のバージョン全然ちゃうがな。
しかも、最新のプレリリースは、v11.0.0+incompatible
を回避してくれて最新バージョンになるみたい。
結局のところ、解決策は
go get k8s.io/client-go@v0.20.4
だったのだがなぜそうなるんだろう?Major version suffix
go mod
の公式ドキュメントを読んでいると、Major Version Suffix というコンセプトが出てきた。これは、依存性の問題を避けるための解決策で、通常ライブラリが異なるバージョンを使っている場合、より上のバージョンが採用される。ところがそうだとバージョンのコンフリクトの問題が発生する。であるので、Goは メジャーバージョンが2以上の場合は、メジャーバージョンサフィックスをつけるように変更を行った。つまり、
example.com/mod
は、@v1.0.0
もしくは@v0.0.0
代のバージョンで使える表記であるが、メジャーバージョンが2以上の場合は、example.com/mod/v2
として使う必要がある。これによって、メジャーバージョンが違うと、パスが異なるので、同じライブラリの異なるバージョンが存在できるようになる。つまりv11.0.0.0+incompatible
の意味合いは、モジュールに対応していないからではなく、v11.0.0.0
のライブラリは本来バージョンサフィックス付きで使われるべきなので、k8s.io/client-go/v11 としてインポートするべき(だけとできないのだろう)インポート部分を以下のように変えてみる。
import ( "flag" "fmt" "path/filepath" "time" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/v11/kubernetes" "k8s.io/client-go/v11/tools/clientcmd" "k8s.io/client-go/v11/util/homedir" )result
$ go run main.go build command-line-arguments: cannot load k8s.io/client-go/v11/kubernetes: module k8s.io/client-go@latest found (v11.0.0+incompatible), but does not contain package k8s.io/client-go/v11/kubernetesすると、どこかで見たようなエラーメッセージが出てきた。本来、v11 のサフィックスでアクセスできるようにするべきが、そうなってないので、コンテンツが無いように見えるということである。だから、おそらく
k8s.io/client-go
のプロジェクトは、バージョンサフィックスに対応するためにバージョン番号をv11.0.0 から、v0.20.0 に変更して、バージョンサフィックス問題を避けているのだろう。(多分)ところが、v11.0.0.0 は既にリリースされているので、普通に使うとlatest
ではそれがインポートされてincompatible
問題が発生したということであろう。自分のメンテしているアプリが、メジャーバージョン上げたいときは、ディレクトリを掘る必要があるっぽい。
まとめ
- メジャーバージョンが大きい場合は、現在の
go mod
ではバージョンサフィックスが必要なはずなので、無い場合は、そこが怪しい。- 最初に確認するべきは
go.mod
- go mod tidy で、依存関係が壊れている個所を特定する
- 変なメジャーバージョンがある場合は、リポジトリの最新を見て、最新のものをインストールする。
go get k8s.io/client-go@v0.20.4
等
- 投稿日:2021-03-14T01:50:19+09:00
Golang ハンドラ関数のチェインで詰まった過去の自分へ
Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作るを読み進める中で、
第3章にある 3.10 ハンドラとハンドラ関数のチェイン の項で最初理解不明だったけど向き合ったら理解できたのでその過程を書いていきます。↓(詰まった)問題となるコード
gowebprog/ch03/10chain_handlerfunc/server.gopackage main import ( "fmt" "net/http" "reflect" "runtime" ) func hello(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello!") } func log(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { name := runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name() fmt.Println("ハンドラ関数が呼び出されました - " + name) h(w, r) } } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/hello", log(hello)) server.ListenAndServe() }詰まった(1回目)
log()でreturnしている匿名関数が、log()内に有りもしない値を引数としてセットしている点。
HandleFunc()の中身を見てみる。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }第2引数にある
handler func(ResponseWriter, *Request)
に着目すると、
log()内の匿名関数と形が一致していることがわかる -> 高階関数↓処理のフロー的なもの
HandleFunc(~, log(hello)) ↓ 中身を展開 log(hello) { return func(w, r) { name := runtime.FuncForPC(reflect.ValueOf(next).Pointer()).Name() fmt.Println("ハンドラ関数が呼び出されました - " + name) hello(w, r) } } ↓ log実行 func (w, r) { name := runtime.FuncForPC(reflect.ValueOf(next).Pointer()).Name() fmt.Println("ハンドラ関数が呼び出されました - " + name) hello(w, r) } ↓ funcがhandlerとして扱われる name := runtime.FuncForPC(reflect.ValueOf(next).Pointer()).Name() fmt.Println("ハンドラ関数が呼び出されました - " + name) hello(w, r) // ←最終的にhelloが実行詰まった(2回目)
log()でreturnしている匿名関数ではh()に引数を書くのに、main()で書くhelloは引数が不要な点
HandleFuncの引数のhelloは関数オブジェクトとして扱われていて、
log()内匿名関数のhは関数呼び出しをしていることの違い。まとめ
個人的にはこう書いたほうがわかりやすそう
package main import ( "fmt" "net/http" "reflect" "runtime" ) func hello(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello!") } func log(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { name := runtime.FuncForPC(reflect.ValueOf(next).Pointer()).Name() fmt.Println("ハンドラ関数が呼び出されました - " + name) next(w, r) } } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/hello", log(hello)) server.ListenAndServe() }追記
log()のreturnよりも上で書いた処理がgo run時点で走ってしまうのは関数呼び出しとして扱われているからか
— shotaro ozawa (@tk_zawa) March 13, 2021
それとHandleFunc()の第2引数はあくまで匿名関数(handler)の中身しか見てないからエンドポイントにアクセスしてものlog()のreturnより上の処理が走らんのね
- 投稿日:2021-03-14T00:38:53+09:00
[andlabs / ui] Windows GUI examples
GoでWindowsなGUIを作れるものを見つけたので、
exampleを動かして確認してみます。今回紹介するのは、andlabs/ui です。
この記事を書いている時点では、
まだ安定版ではなさそうなので使用される場合はご注意を。GoでWindowsなGUIを作成したいときは、
すこし安定している walk をオススメします。
examples
サンプルとなるコードは、以下のURLにあります。
https://github.com/andlabs/ui/tree/master/examples
- Control Gallery
- テキストボックス・ボタン・ダイアログ・スライダー など
- Draw Text
- 様々なテキストの表示方法
- Histogram
- 折れ線グラフ
- Table
- 表(行内に画像・ボタン・プログレスバーがある)
- UpdateUI
- UIを更新するサンプル?
では順番に見てみましょう。
Control Gallery
controlgallery.go
https://github.com/andlabs/ui/blob/master/examples/controlgallery.goボタン・チェックボックス・ラベル・テキストボックス など
スライダーをグリグリすると、プログレスバーが連動して動きます。
Dateピッカーやファイルパスの指定(読み込み先・保存先)・ダイアログ など
Draw Text
drawtext.go
https://github.com/andlabs/ui/blob/master/examples/drawtext.goフォントの修飾・サイズ・位置・フォント変更 など
結構きめ細かに指定できます!Histogram
histogram.go
https://github.com/andlabs/ui/blob/master/examples/histogram.go数値を変えると折れ線グラフが連動して動く!
Table
table.go
https://github.com/andlabs/ui/blob/master/examples/table.go行内でいろいろカスタムができるみたいですね!
UpdateUI
動かなかった、、、
まとめ
動かしてみた感じ、ちょっと気になるとこがありましたがそれなりには使えそう感。
あとビルド自体は少し遅めかなという印象(とはいえ数秒ですが)
コード自体はスッキリしていて書きやすい気がします。気になる部品があったらソースコードを覗いて触ってみてくださいね~!