- 投稿日:2019-07-28T21:47:46+09:00
Golang 環境設定
いろんな場所に情報が...
Golang初心者が環境設定をする時に色んな記事をたらい回しにされたので、ここにまとめておく
開発環境
- MacOS
- Visual Studio Code
- Github
gvmのインストール
gvmはGolangのバージョン管理ツール
下にある3つのコマンドを順番にシェルで実行すればgvmが使えるようになる$ brew install mercurial $ bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer) $ source $HOME/.gvm/scripts/gvm3つ目の
source $HOME/.gvm/scripts/gvm
は.bashrcに設定しておくGolangのインストール
gvmを使ってインストールする
まずダウンロードできる Golang のバージョンのリストを下のコマンドで得る
gvm listall
今回はv1.12.7を下のコマンドで入れる
gvm install go1.12.7 -B
v1.12.7をデフォルトで使う設定を下のコマンドでする
gvm use go1.12.7 --default
なぜgvmを使う?
- Golangを使った開発に必要なものを一箇所で管理できる
- 複数のバージョンを
gvm use
コマンドで切り替えられる からワークスペースの設定
ワークスペースはGoを使った開発を行うディレクトリの事で、環境変数のGOPATHで指定する
今回は以下の場所をワークスペースとするexport GOPATH=$HOME/goGolangのワークスペースには以下の3つのサブディレクトリが必要
- bin: Goアプリのコンパイル後の実行ファイルを保管
- pkg: 実行ファイルを作るための外部パッケージを保管
- src: アプリ開発のソースコードを保管
ワークスペースの階層構造
3つのサブディレクトリを作った後の構成を下に示す
$HOME ─ go ┬ bin ├ pkg └ src ─ github.com ─ {github_id} - {repogitory_name} ─ .git └ source code上に示したように、src内にはリポジトリ情報を含んだ階層構造でプロジェクトを作成するのが一般的なようだ
{github_id}はあなたのGithubのアカウント名、{repogitory_name}はレポジトリの名前である
そして、{repogitory_name}内に.gitを作るコーディングのサポートツール
公式でオススメされているサポートツールはgoplsというLanguage Serverのようだ
これを使うために、GolangのVSCodeの拡張機能であるGoを入れる次にgoplsの設定を行う
まずgoplsを下のコマンドでインストールする
go get golang.org/x/tools/gopls
その後、settings.jsonに下の内容を追加後再起動すればLanguage Serverが起動する
settings.json"go.useLanguageServer": true, "go.alternateTools": { "go-langserver": "gopls" }, "go.languageServerExperimentalFeatures": { "format": true, "autoComplete": true }, "[go]": { "editor.snippetSuggestions": "none", "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.organizeImports": true }, }, "gopls": { "usePlaceholders": true, "enhancedHover": true },パッケージ管理ツール
Golangのv1.11以降で採用されているgo modules(vgo)を用いる
これに関しては用意するものはなく、使い方を学ぶだけで良いvgoを使うには以下の設定をsettings.jsonに追加する
settings.json"go.toolsEnvVars": { "GO111MODULE": "on" },これでコード内でimportしている外部モジュールが、ビルド時にインストールされるようになる
これらのモジュールはビルド時に作られるファイルgo.mod
とgo.sum
で管理されるビルド後動きに問題はないがimport文でエラーが出るので、これを表示させないために下の設定をsettings.jsonに追加する
settings.json"go.installDependenciesWhenBuilding": false,ちゃんとできたか?
以上で設定はできているはず
後日気づいた点があれば追記していく予定参考文献
- 投稿日:2019-07-28T21:47:46+09:00
Golang ことはじめ
いろんな場所に情報が...
Golang初心者が開発環境を整える時に色んな記事をたらい回しにされたので、ここにまとめておく
開発環境
- MacOS
- Visual Studio Code
- Github
達成目標
- Golangのインストールとバージョン切り替えをスムーズに
- コーディングサポート用のLanguage Serverを起動する
- プロジェクト毎にパッケージを管理できる
gvmのインストール
gvmはGolangのバージョン管理ツール
下にある3つのコマンドを順番にシェルで実行すればgvmが使えるようになる$ brew install mercurial $ bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer) $ source $HOME/.gvm/scripts/gvm3つ目の
source $HOME/.gvm/scripts/gvm
は.bashrcに設定しておくGolangのインストール
gvmを使ってインストールする
まずダウンロードできる Golang のバージョンのリストを下のコマンドで得る
gvm listall
今回はv1.12.7を下のコマンドで入れる
gvm install go1.12.7 -B
v1.12.7をデフォルトで使う設定を下のコマンドでする
gvm use go1.12.7 --default
なぜgvmを使う?
- Golangを使った開発に必要なものを一箇所で管理できる
- 複数のバージョンを
gvm use
コマンドで切り替えられる からワークスペースの設定
ワークスペースはGoを使った開発を行うディレクトリの事で、環境変数のGOPATHで指定する
今回は以下の場所をワークスペースとするexport GOPATH=$HOME/goGolangのワークスペースには以下の3つのサブディレクトリが必要
- bin: Goアプリのコンパイル後の実行ファイルを保管
- pkg: 実行ファイルを作るための外部パッケージを保管
- src: アプリ開発のソースコードを保管
ワークスペースの階層構造
3つのサブディレクトリを作った後の構成を下に示す
$HOME ─ go ┬ bin ├ pkg └ src ─ github.com ─ {github_id} - {repogitory_name} ┬ .git └ main.go上に示したように、src内にはリポジトリ情報を含んだ階層構造でプロジェクトを作成するのが一般的なようだ
{github_id}はあなたのGithubのアカウント名、{repogitory_name}はレポジトリの名前である
そして、{repogitory_name}内に.gitを作るコーディングのサポートツール
公式でオススメされているサポートツールはgoplsというLanguage Serverのようだ
これを使うために、GolangのVSCodeの拡張機能であるGoを入れる次にgoplsの設定を行う
まずgoplsを下のコマンドでインストールする
go get golang.org/x/tools/gopls@latest
その後、settings.jsonに下の内容を追加後再起動すればLanguage Serverが起動する
settings.json"go.useLanguageServer": true, "go.alternateTools": { "go-langserver": "gopls" }, "go.languageServerExperimentalFeatures": { "format": true, "autoComplete": true }, "[go]": { "editor.snippetSuggestions": "none", "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.organizeImports": true }, }, "gopls": { "usePlaceholders": true, "enhancedHover": true },パッケージ管理ツール
Golangのv1.11以降で採用されているgo modules(vgo)を用いる
これに関しては用意するものはなく、使い方を学ぶだけで良いvgoを使うには以下の設定を.bashrcに追加する
.bashrcexport GO111MODULE=onこれでコード内でimportしている外部モジュールが、ビルド時にインストールされるようになる
これらのモジュールはビルド時に作られるファイルgo.mod
とgo.sum
で管理されるgo.modとgo.sumはプロジェクトごとに作るべきなので、プロジェクトディレクトリに入ってからアプリをビルドするのが良い
そうすると、go.modとgo.sumはプロジェクトディレクトリ内に作られるビルド後動きに問題はないがimport文でエラーが出るので、これを表示させないために下の設定をsettings.jsonに追加する
settings.json"go.installDependenciesWhenBuilding": false,ちゃんとできたか?
以上で設定はできているはず
後日気づいた点があれば追記していく予定参考文献
- 投稿日:2019-07-28T21:02:05+09:00
Go言語のサンプルコード
■ はじめに
勉強したことはすぐ忘れてしまうので、サンプルコードをメモしておく。
新しく学んだことは追記していく予定。
■ 乱数の生成
乱数を生成方法を記述する。
rand を使えば乱数を生成できると思っていたが、うまくいかなかった。
generate_number.gopackage main import ( "fmt" "math/rand" ) func main() { target := rand.Intn(100) fmt.Println(target) }$ go run generate_number.go 81 $ go run generate_number.go 81 $ go run generate_number.go 81ずっと 81 が表示される。
乱数を得たい場合は、rand.Seed に先に値を渡す必要がある。
この seed は、ランダムな番号を生成する generator だ。
ただし、この seed にも、プログラム実行毎に異なる値を渡して上げる必要がある。
generate_number.gopackage main import ( "fmt" "math/rand" "time" ) func main() { // 現在の時刻を int 型で取得 seconds := time.Now().Unix() rand.Seed(seconds) // 1 ~ 100 までの乱数を生成 target := rand.Intn(100) + 1 fmt.Println(target) }$ go run generate_number.go 45 $ go run generate_number.go 36 $ go run generate_number.go 93■ ファイルの読み込み
ファイルの読み込み、出力。(結構忘れる)
mkdir filereader touch filereader/readfile.go touch filereader/data.txtdata.txtHello, This is, Monkey.readfile.gopackage main import ( "bufio" "fmt" "log" "os" ) func main() { // Read a data file file, err := os.Open("data.txt") if err != nil { log.Fatal(err) } // Create New Scanner scanner := bufio.NewScanner(file) for scanner.Scan() { fmt.Println(scanner.Text()) } err = file.Close() if err != nil { log.Fatal(err) } if scanner.Err() != nil { log.Fatal(scanner.Err()) } }$ go run readfile.go Hello, This is, Monkey.■ 引数渡し
terminal からプログラムに引数を渡す。
main.gopackage main import ( "fmt" "os" ) func main() { fmt.Println(os.Args[1:]) }$ go run main.go Arg1 Arg2 Arg3 [Arg1 Arg2 Arg3]■ 可変長引数
main.gopackage main import "fmt" func severalInts(numbers ...int) { fmt.Println(numbers) } func main() { severalInts(1) severalInts(1, 2) severalInts(1, 2, 3) }$ go run main.go [1] [1 2] [1 2 3]■ 最大値の算出
main.gopackage main import ( "fmt" "math" ) func maximum(numbers ...float64) float64 { max := math.Inf(-1) for _, number := range numbers { if number > max { max = number } } return max } func main() { fmt.Println(maximum(1.2, 3.2, 3.5)) fmt.Println(maximum(-23.1, -30.2, -3.2)) }$ go run main.go 3.5 -3.2■ 境界値内の値の保持
min, max の境界値内の値を保持する。
main.gopackage main import ( "fmt" ) func inRange(min float64, max float64, numbers ...float64) []float64 { var result []float64 for _, number := range numbers { if number >= min && number <= max { result = append(result, number) } } return result } func main() { fmt.Println(inRange(1, 100, -12.5, 10.3, 49, 101)) fmt.Println(inRange(-10, 10, 5.2, 10.3, 4.9, 11)) }$ go run main.go [10.3 49] [5.2 4.9]■ スライスを可変長引数に渡す
main.gopackage main import "fmt" func severalInt(numbers ...int) { fmt.Println(numbers) } func mix(num int, flag bool, strings ...string) { fmt.Println(num, flag, strings) } func main() { intSlice := []int{1, 2, 3} severalInt(intSlice...) stringSlice := []string{"a", "b", "c", "d", "e"} mix(1, true, stringSlice...) }$ go run main.go [1 2 3] 1 true [a b c d e]■ Map の初期値
Map の Key に存在しない Key の値を渡すと、bool 型が取得できる。
main.gopackage main import "fmt" func main() { counters := map[string]int{"a": 3, "b": 0} var value int var ok bool value, ok = counters["a"] fmt.Println(value, ok) value, ok = counters["b"] fmt.Println(value, ok) value, ok = counters["c"] fmt.Println(value, ok) }$ go run main.go 3 true 0 true 0 false応用するとスライスの中身を数えることもできる。
main.gopackage main import ( "fmt" ) func main() { names := []string{ "田中", "中山", "山田", "田中", "中山", "山田", "田中", "中山", "山田", "田中", } counts := make(map[string]int) for _, name := range names { counts[name]++ } for name, count := range counts { fmt.Printf("Votes for %s: %d\n", name, count) } }$ go run main.go Votes for 中山: 3 Votes for 山田: 3 Votes for 田中: 4■ まとめ
使えそうなコードをストックしておけば、実務でも役に立つときがある。
- 投稿日:2019-07-28T19:37:48+09:00
Goで世界一シンプルなCUIライブラリを作りました
こんにちは。今日もいいターミナル日和ですね。
今回はGo製の新たなCUIライブラリ goban を作ったので、その紹介と簡単な使い方を説明します。(追記)罫線と文字幅について
日本語環境では罫線が全角幅で表示されてしまい、綺麗に出力されない問題があります。その際は、以下の一行をMainの前に挿入してください。
runewidth.DefaultCondition = &runewidth.Condition{EastAsianWidth: false}ただし、これは ambiguous width に分類される文字すべてを半角幅で表示してしまうため、記号などを用いたときに別の問題が発生する場合があります。注意して使用してください。
今後、CJK環境では罫線を諦めてASCIIでボックスを表示する実装を入れる予定です。
CUIライブラリについて
ターミナルで動くグラフィカルなアプリを作るためのライブラリです。著名なものに gocui, tviewなどがあります。
gobanはこれらと比較してよりシンプルに、よりGoらしく書けることを意識しています。gobanの特徴
- シンプルながら強力なAPI
- ボックス描画
- グリッドレイアウト
- チャネル経由でイベントを処理する。(not イベントハンドラ)
- 分離されたビューとコントローラー
- 容易なビューの組み合わせ
Hello, World!
READMEにも記載していますが、こちらがHello Worldです。
package main import ( "context" "github.com/eihigh/goban" ) func main() { goban.Main(app, view) } func app(_ context.Context, es goban.Events) error { goban.Show() es.ReadKey() return nil } func view() { goban.Screen().Enclose("hello").Prints("Hello World!\nPress any key to exit.") }
goban.Main
で画面を作成し、メイン処理を呼び出します。app
がユーザーが記述するメインの処理です。app
が終了すると、Main
も終了します。goban.Show()
で描画を行います。(後述)es.ReadKey()
はキーイベントを待って、読み取る関数です。キー以外のイベントは無視されます。return nil
でappを終了します。その後Mainも終了し、appの返したエラーを返します。上述したとおり、イベントはチャネル経由で流れてきます。そのため、「ふつうの」プログラムのように流れ通りに処理を書いて、適時イベントを待ったり描画を行ったりする形になります。この辺はまさにGoのパワーが活きています。
描画システム
Hello Worldでは
view
という一つの描画関数のみを用いています。
goban.Main(app, view)
でview
を描画関数として登録しています。goban.Show()
で描画関数が呼び出されます。描画関数は複数登録でき、登録した順に呼び出されます。なので、ポップアップなどは以下のように適時ビューを追加することで、上にビューを重ねることができます。
func main() { goban.Main(app) } func app(_ context.Context, es goban.Events) error { // 下敷きとなるview v := func() { b := goban.Screen() b.Puts("Press any key to open popup") b.Puts("Ctrl+C to exit") } goban.PushViewFunc(v) for { goban.Show() es.ReadKey() popup(es) } } func popup(es goban.Events) { // ポップアップのview v := func() { b := goban.NewBox(0, 0, 40, 5).Enclose("popup window") b.Prints("Press any key to close popup") } // viewを上に追加する。 // ポップアップのようにモーダルビューを用いるときは、 // このように PushView と defer PopView をペアで使うのが推奨です。 goban.PushViewFunc(v) defer goban.PopView() goban.Show() es.ReadKey() }ポイントは
defer PopView
で、これを用いることによって関数を脱出した際に自動的にビューを削除することができます。このように、通常状態遷移が必要な処理も、このように非常にシンプルに書くことができます。グリッドレイアウト
gobanでは
Box
を用いてさまざまな描画を行います。その補助として、グリッドレイアウトを使用することができます。var ( grid = goban.NewGrid( " 1fr 1fr 1fr ", "1fr header header header", "3fr side content ", "1fr footer footer footer", ) ) func main() { goban.Main(app, view) } func app(_ context.Context, es goban.Events) error { goban.Show() es.ReadKey() return nil } func view() { b := goban.Screen().Enclose("") header := b.GridItem(grid, "header").DrawSides("", 0, 0, 0, 1) header.Prints("Header") // 以下略 }
NewGrid
関数にレイアウト文字列を渡して、直感的にグリッドレイアウトを作成できます。
- 長さの単位には
fr
またはem
を使用できます。- 1行目には列のサイズを、2行目以降は行のサイズ+エリア名を記述します。同じエリア名を連続させることで、範囲を連結させることができます。
goban.Box#GridItem
関数で、グリッドに相当する範囲のボックスを取得します。今回は文字列でグリッドを生成していますが、行と列の大きさを直接指定したり、範囲を直接指定して切り出したり(
goban.Box#GridCell
)することができます。ビューモデル
ここまで、viewはすべて関数として解説してきましたが、実際には
goban.View
というインターフェースとして定義されています。(関数を渡すと内部でインターフェースを満たす型にキャストされています。)このことを利用して、viewを状態を持つ型として定義することで、よりviewを強力にすることができます。
func main() { goban.Main(app) } func app(_ context.Context, es goban.Events) error { v := &menuView{ items: []string{ "foo", "bar", "baz", }, } goban.PushView(v) for { goban.Show() switch k := es.ReadKey(); k.Key() { case tcell.KeyUp: v.cursor-- case tcell.KeyDown: v.cursor++ } } } // goban.Viewを実装している。 type menuView struct { cursor int items []string } func (v *menuView) View() { b := goban.NewBox(0, 0, 50, 20).Enclose("menu") b.Puts("↑, ↓: move cursor") for i, item := range v.items { if i == v.cursor { b.Print("> ") } b.Puts(item) } }app側で
menuView
を作成し、viewとして追加しています。このパターンによって、
- カーソル位置などの状態を持ちやすくなる。
- appとviewをより独立に書くことができる。
- appでview modelのスコープを明確にできる(
defer PopView
/defer RemoveView(v)
と組み合わせてください)。などのメリットが得られます。
以上、基本的な機能の解説でした!
これから
鋭意開発中ですが、基本機能のAPIはそう変わらない予定です。
今後はより見た目を改善する機能や(中央揃えなど)、エスケープシーケンスへの対応を広げたいです。(追記) 試行錯誤中ですが、マウスサポートも追加しました。
また、拡張性を生かして、基本APIはそのままに別パッケージとして便利なウィジェットを多数提供する予定です。たとえば、
if confirm(events, msg) { /* 確認画面でOKされたときの処理 */ } // goban.Eventsを渡せば描画から確認完了までやってくれるのがポイント!など。(コードの見た目が Immediate GUI っぽくなりますね)
気になった方には是非examplesを覗いていただいて、気に入れば使っていただけると嬉しいです。p-rも歓迎です!
goban
、よろしくお願いします。
- 投稿日:2019-07-28T19:15:25+09:00
WebAssemblyから、Goのメソッドを呼び出す (2)
https://qiita.com/neko-suki/items/7fb822b9adfa6f1c12eb の続きです。
今回は、以下の2点を扱います。
- Goで定義した構造体の定義は変更せずに、WebAssemblyからGoで定義した構造体のメソッドを呼ぶ
- 構造体のメソッドの引数に、同じ構造体の別のインスタンスを渡す
両者とももっと賢く書く方法がある気がしますが、自分で調べた範囲では見つけられませんでした。
Goで定義した構造体の定義は変更せずに、WebAssemblyからGoで定義した構造体のメソッドを呼ぶ
特にいいやり方が思いつかなかったので、ラッパーを書きました。
構造体の定義
構造体の定義は元のままです。これをWebAssemblyから使えるようにします。元の構造体事態は何らかの事情で変更したくない場合を想定しています。
type Test struct { Num int } func (t *Test) Print() { fmt.Println(t) } func (t *Test) Twice() { t.Num *= 2 } func (t *Test) Add(val int) { t.Num += val } func (t *Test) GetNum() int { return t.Num }WebAssembly用の実装
WebAssembly側から呼び出せるようにラッパー関数をそれぞれ書きました。
package main import ( "syscall/js" ) func (t *Test) PrintWrapper(this js.Value, args []js.Value) interface{} { t.Print() return nil } func (t *Test) TwiceWrapper(this js.Value, args []js.Value) interface{} { t.Twice() return nil } func (t *Test) AddWrapper(this js.Value, args []js.Value) interface{} { t.Add(args[0].Int()) return nil } func (t *Test) GetNumWrapper(this js.Value, args []js.Value) interface{} { return js.ValueOf(t.GetNum()) } func registerCallbacks() { var test = &Test{ Num: 1, } js.Global().Set("test", js.ValueOf( map[string]interface{}{ "print": js.FuncOf(test.PrintWrapper), "twice": js.FuncOf(test.TwiceWrapper), "add": js.FuncOf(test.AddWrapper), "getNum": js.FuncOf(test.GetNumWrapper), }, )) } func main() { c := make(chan struct{}, 0) registerCallbacks() <-c }構造体のメソッドの引数に、同じ構造体の別のインスタンスを渡す
例えば、以下のように同じ構造体の別のインスタンスを受け取って内部で利用する場合を考えます。
func (t *Test) AddAnotherTest(t2 *Test) { t.Num += t2.Num }WebAssemblyの実装
まずは、関数登録部分です。構造体自信のポインタを取得するための関数
_ptr
を追加しています。これによって、登録した構造体のインスタンスがjavascript側から引数として渡されたときにポインタを取得できるようになります。_ptr
に直接ポインタの値を代入しないのはjavascript側で値を改変されないようにするためです。(プライベートメンバとして登録できれば必要ないはずですが、今回はそこまで調べていないです。)func registerCallbacks() { var test = &Test{ Num: 1, } js.Global().Set("test", js.ValueOf( map[string]interface{}{ "_ptr": js.FuncOf(test.getPtr), "print": js.FuncOf(test.PrintWrapper), "twice": js.FuncOf(test.TwiceWrapper), "add": js.FuncOf(test.AddWrapper), "getNum": js.FuncOf(test.GetNumWrapper), "addAnotherTest": js.FuncOf(test.AddAnotherTestWrapper), }, )) var test2 = &Test{ Num: 2, } js.Global().Set("test2", js.ValueOf( map[string]interface{}{ "_ptr": js.FuncOf(test2.getPtr), "print": js.FuncOf(test2.PrintWrapper), "twice": js.FuncOf(test2.TwiceWrapper), "add": js.FuncOf(test2.AddWrapper), "getNum": js.FuncOf(test2.GetNumWrapper), "addAnotherTest": js.FuncOf(test2.AddAnotherTestWrapper), }, ), ) }次にポインタ取得用関数の実装です。
unsafe.Pointer
をjs.ValueOf()
でjs.Value
型に変換して、javascript側に渡します。func (t *Test) getPtr(this js.Value, args []js.Value) interface{} { return js.ValueOf(unsafe.Pointer(t)) }最後に、構造体を受け取って処理する部分です。javascript側から
test.AddAnotherTest(test2)
という形で呼ばれる想定です。まず、
args[0].Get('_ptr')
でtest2._ptr
関数を呼び出しして、test2
のポインタを取得します。ただしこの段階ではjs.Value
型なので変換が必要です。unsafe.Pointer
はfloat64で実装されているようなので、.Float()
を呼び出します。その結果をunsafe.Pointer
型に変換して、最後にTest
のポインタに変換します。func (t *Test) AddAnotherTestWrapper(this js.Value, args []js.Value) interface{} { ptrJSValue := args[0].Call("_ptr") t2 := (*Test)(unsafe.Pointer(ptrJSValue.Float())) t.AddAnotherTest(t2) return nil }テスト用のhtml
テスト用に使うhtmlです。
addAnotherTest()
というのが追加されています。例えば、twice
,addAnotherTest
,&{4}
が表示されます。<!doctype html> <html> <head> <meta charset="utf-8"> <title>Go wasm</title> </head> <body> <script src="wasm_exec.js"></script> <script> const go = new Go(); WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then((result) => { go.run(result.instance); }); </script> <script> function getNum(){ console.log(test.getNum()) } function addAnotherTest(){ console.log(test2) test.AddAnotherTest(test2) } </script> <button onClick="test.print();" id="runButton" >print</button><br> <button onClick="test.twice()" id="runButton">twice</button><br> <button onClick="test.add(1)" id="runButton">add</button><br> <button onClick="getNum()" id="runButton">get test.Num</button><br> <button onClick="addAnotherTest()" id="runButton"> addAnotherTest</button><br> </body> </html>ソース
下に置いてあります。
https://github.com/neko-suki/wasm_struct
- 投稿日:2019-07-28T14:43:53+09:00
gRPC UIを使ってみんなが触れるgRPCの遊び場を作りました
gRPC
は近年非常によく使われるようになったRPCフレームワーク1ですが、その柔軟なメッセージフォーマットに対応するリッチなGUIクライアントに欠けていました。REST APIでいうPostman的な存在です。このようなツールがなければgRPCを利用した開発が面倒なのですが、その問題は現在においてはほぼ払拭されたといっても過言ではありません。gRPC UIが登場したからです。(本記事は自分のブログからの転載記事です。)
TL;DR
- gRPC UIで作成した遊び場はこちらです
gRPC UI playground
- gRPC UIはgRPCサーバへの要求と応答がWebで簡単にできるので、今後のgRPCを利用した開発に広く使われるツールとなりそうです
- gRPC UI playgroundは以下のサービスを利用して、無料で立ち上げました。当面はみんなが遊べるようにしておくつもりです
はじめに
本記事は
gRPC
のWeb UIであるgRPC UI
の紹介記事です。gRPCに興味がある方、gRPCを利用している方を対象にしています。実際にgRPC UIを触れる遊び場も作ったのでgRPCに興味がある方は遊んでいってください。また本記事ではこの遊び場をどうやって作ったのかも簡単に解説します。gRPCとは
gRPCはGoogleが開発してオープンソースとして公開したRPCフレームワークです。IDL2としてProtocol Buffers3が利用可能です4。gRPCは基本的には以下の図ように単純で、クライアントから要求(
Proto Request
)を送ってサーバが応答(Proto Response
)を返すというものです。クライアントやサーバの記述には、Protocol Buffersが対応している様々な言語(C++, Ruby, Java等)が利用可能です。What is gRPCより引用
gRPCは上記のように単純な要求/応答型以外にも様々なやりとりに対応できます。具体的には以下の4つです。
- Unary RPC(単純な要求/応答型)
- Server streaming RPC(サーバが複数の応答を返すことができる)
- Client streaming RPC(クライアントが複数の応答を返すことができる)
- Bidirectional streaming RPC(クライアントとサーバがそれぞれ複数の要求/応答ができる。一般的な双方向通信)
上記のような柔軟な呼び出しはHTTP/25の基盤に支えられて実現されています。
gRPC UIについて
gRPC UIはgRPCサーバとブラウザでやりとりできるツールです。百聞は一見にしかず。以下の画面を御覧ください。
この画面を見て震えたのはやはり要求の入力画面(
Request Form
)の充実っぷりです。gRPCは自分でデータの型を定義して入れ子のようなメッセージを定義できますが、そのような入力も簡単にできます。上記の図のではtest.TestMessage
というメッセージの中にperson(test.Person
というメッセージの型)が内包されています。また複数のデータが入力可能なフィールドは「Add Item
」で簡単に追加できます。その他の注目すべき点は赤字で説明を入れておいたので参考にしてください。基本的にはgRPC(Protocol Buffers)で定義されている入力形式はほぼ網羅されています。また、驚いたのはgRPCには基本的なスカラ値(
string
やint64
等)以外にも「Well-Known Types」と呼ばれる型が定義されていますが、gRPC UIにはこれらにもリッチなインタフェースが提供されていたことです。以下の画面はtimestamp
型に対するインタフェースです。またリッチなフォームだけではなく、JSON形式でリクエストを投げることもできます。
JSON形式で便利なのはコピペが容易なところです。また省略されているフィールドは出力されないので見やすくなります。フォーム型の編集画面とも連動しているので、非常に使いやすいです。リクエストは「Invoke」ボタンを押すことでサーバに投げることができます。このボタンはフォーム型のリクエストにも一番下にあります。以下が「Invoke」を押して返ってきた応答になります。
この画面も特に説明がいらないくらい分かりやすいものになっています。gRPC UIで対応できないのは双方向の複雑なやり取りです。これは従来どおりCUI等を利用して行うしかありません。
gRPC UIで遊ぼう! 〜 gRPC UI playground 〜
さて、ここまで来たらgRPC UIで遊んでみたくなったと思います。以下にみんなが遊べるように「遊び場」を立ち上げたのでぜひいろいろ遊んでみてください。
gRPC UIはバックグラウンドで起動しているgRPC UIのテストサーバ(testsvr)とやりとりしています。プロトコルは「test.proto」、サーバの実装は「testsvr.go」に記述されています。実際にgRPC UIを動かしながらソースやIDLを読むとgRPCの理解が深まると思います。
メソッドに関しては「
DoManyThings
」が全部詰めになっていて、残りのメソッドはそれを機能単位で分割したものになっています。たぶん・・・grpcui-playgroundの作り方
さて、ここからはおまけです。上記の遊び場(以降、
grpcui-playground
と記載)の構築方法の興味がある方を対象に簡単に説明したいと思います。grpcui-playgroundは3つのサービスを利用して無料で立ち上げました。以下がその構成になります。上の図をざっくり説明すると、基本的にはDockerを使ってサービス提供しています。一つのコンテナの中にgPPC UIとgRPC Server(
testsvr
)という二つのプロセスが動いています。一つのコンテナに二つのプロセスはあまりお行儀がよくありませんが、今回は遊び場ということと無料で立ち上げることを重視したのでこのような構成になっています。
利用したコンテナホスティングサービスはArukas6になります。今回はFreeプランを利用しています。Arukasにコンテナイメージを提供するためにはDocker Hubも利用しました。Docker HubにはGitHubと連携しておくとGitHubの変更を検知してコンテナをビルドする機能があるのでそれも利用しています。Dockerfileを書く
Dockerfileを書くときの注意点は二つです。まず、ビルドイメージを小さくするためにAlpine Linux7をベースイメージにします。次にgRPC UIが提供しているテストサーバ(
testsvr
)はgoで書かれているのでgoのビルド環境が必要です。ただしgoのビルド環境は動作には必要ないのでマルチステージビルドを活用して、なるべく小さなイメージにするようにしましょう8。DockerfileFROM golang:1.12.7-alpine as build-env MAINTAINER hinastory WORKDIR /testsvr COPY testsvr /testsvr RUN apk update && apk add --no-cache git RUN go build RUN go get -x github.com/fullstorydev/grpcui RUN go install -x github.com/fullstorydev/grpcui/cmd/grpcui FROM alpine WORKDIR / COPY --from=build-env /testsvr/testsvr /bin/testsvr COPY --from=build-env /go/bin/grpcui /bin/grpcui COPY start.sh /start.sh EXPOSE 8080 ENTRYPOINT [ "/start.sh" ]GitHubでDockerfileを公開する
ここは特に説明はしませんが、GitHubに公開レジストリを作成し、Dockerfileと
testsvr
のソースと起動スクリプトをpushします。pushした自分のリポジトリは以下になります。
![]()
Docker Hubでイメージをビルド & 公開する
Docker Hubでイメージをビルドするのには、GitHubと連携しておいたほうが楽です。GitHubと連携しておけばGitHubのレポジトリをDocker HubのUIから選択できるようになり、GitHubにpushするだけでDockerfileをビルドしてくれるようになります。
注意すべき点はDockerイメージのタグの付け方です。デフォルトだとブランチを監視してlatestタグを付けるようになっていますが、Arukasはイメージをキャッシュする場合があるようなのでバージョンが入った適切なタグをつけることが望ましいです。そしてDockerのイメージタグはGitHubのタグと連動しているとよいと思われます。
Docker Hubでその設定をするのは簡単でDockerHubのリポジトリの
Builds
の画面からビルドルールを設定でき、Source Type
をTag
にして、Source
とDocker Tag
を正規表現で記述することでお望みのタグが構成できると思います。またここでDockerfileの位置やビルドコンテキストも指定可能なので、少し複雑なリポジトリ構成でも対応出来ます。ここの設定が終わったらGitHubにタグをpushしてビルドが始まるか確認します。基本的には検知はすぐに行われて
In Progress
の状態にすぐに変わりますがビルドが終わるまでには時間がかかるので、コーヒーでも飲んでまったりと待ちましょう(笑)。自分のコンテナイメージは以下に公開してあります。ArukasでDockerコンテナを公開する
最後にArukasuでコンテナを公開します。FreeプランはO.1vCPUと128MB RAMの非力な環境ですが、今回のような遊び場には充分だと思われます。また「転送量課金」がないのでDDoSとかの心配がまったくいらないのも嬉しいところです。もちろん最悪サービスは落とされますが、料金に怯える心配は無いわけです。ちなみにFreeプランでも電話認証とクレジットカードの登録は必須です。このアカウント登録が最大の難関でコンテナの起動は驚くほどあっけなく終わりました。アプリ起動の詳細は「アプリの作成 – Arukas Help Center」を参照してください。以下は起動したコンテナの管理画面です。
Endpoint
はアプリの再起動で変わることが無いURLです。Port
はインスタンスの起動毎に変わります。ちなみにgRPC UIは8080ポートでHTTPでサービスを公開していますが、エンドポイントではHTTPSになっているので自動でArukasがHTTPSに包んでくれるみたいです。まとめ
本記事では、gRPCサーバとブラウザでやり取りできるgRPC UIを紹介しました。gRPC UIは非常に便利なので今後gRPC関連の開発で広く使われていくものと思われます。そして実際にgRPC UIを触れる遊び場を作成して以下に公開しました。
- 遊び場
- gRPC UI playground
- プロトコルは「test.proto」、サーバの実装は「testsvr.go」
- 遊び場は当面は公開予定ですが、ある日突然告知なしで死ぬ可能性があるのでご容赦ください
- Dockerfile
- コンテナイメージ
また、おまけとしてその遊び場の作り方を(無料でコンテナサービスを立ち上げる手順)を簡単に説明しました。
本記事がgRPCを理解し、より便利に使えるようになるための一助になれば幸いです。参考文献
- gRPC Documentation
- Developer Guide | Protocol Buffers
- RFC 7540 - Hypertext Transfer Protocol Version 2 (HTTP/2)(日本語訳)
- Docker Hub Documentation
- about Alpine Linux
- Arukas Help Center
- 遠隔手続き呼出し - Wikipedia
- インタフェース記述言語 - Wikipedia
RPC(Remote Procedure Call)は、簡単に言えばプログラムの中から内部の関数を呼ぶのと似たような感覚で、外部のネットワーク上の関数や手続きを呼べるようにする技術のことです。他のRPCにはSOAPやJSON-RPCなどがあります。 ↩
IDLはインタフェース記述言語(Interface Description Language)のことです。簡単に言えば関数呼び出しの宣言部分を定義する際に使う言語です。 ↩
Protocol BuffersはGoogleが開発したシリアライズフォーマットです。IDLは独自の言語でシンプルで分かりやすいのが特徴です。IDLのファイルは
.proto
の拡張子を持っており、protoc
というコンパイラを用いてIDLファイルからGo言語やRuby、Python、Java等様々な言語のバインディングを生成できます。現在のProtocol Buffersのバージョンは3になります。 ↩JSONも利用可能みたいですが、自分は使ったことはありません。 ↩
HTTP2はHTTP/1.1の後継バージョンでRFC7540で定義されています。HTTP/1.1と比べてヘッダーの圧縮やバイナリメッセージ構造、双方向通信のサポート等様々な改善がなされています。 ↩
Arukasはさくらインターネットが提供しているDockerのホスティングサービスです。Arukasを反対から読むと・・・ ↩
Alpine Linuxは軽量、シンプル、セキュアをコンセプトにしたLinuxディストリビューションです。組み込み系で実績のあるmusl libcとbusyboxをベースにしています。その軽量さからコンテナのベースイメージとしてよく利用されています。 ↩
最初は無邪気にベースイメージを
stretchに
してシングルステージでビルドしたため、イメージのサイズが400MBを超えてしまいました・・・現在のサイズは 18MBです。特にパブリックなレジストリに登録する際はコンテナイメージのサイズに充分気を配り、リソースを無駄にしないように心掛けましょう(自戒)。 ↩
- 投稿日:2019-07-28T12:42:49+09:00
WebAssemblyから、Goのメソッドを呼び出す
試したこと
WebAssemblyで、Goで宣言した構造体のメソッドを呼び出します。環境は、
go1.12.7
+ Chromeで確認しています。ちなみに、
syscall/js
を使っているので、GOOS=js GOARCH=wasm
以外で使うことはたぶん難しいです。構造体の定義
確認用に、
Num
というメンバを持ったTest
という構造体を使います。メソッドは、Testを出力するPrint()
と、Numの値を2倍にするTwice()
、Test.Num
に値を足すAdd()
、値を取得するGetNum()
を用意します。type Test struct { Num int } func (t *Test) Print() { fmt.Println(t) } func (t *Test) Twice() { t.Num *= 2 } func (t *Test) Add(val int) { t.Num += val } func (t *Test) GetNum() int { return t.Num }コマンドラインで実行して
Test
構造体を使ってみます。package main import ( "fmt" ) type Test struct { Num int } func (t *Test) Print() { fmt.Println(t) } func (t *Test) Twice() { t.Num *= 2 } func (t *Test) Add(val int) { t.Num += val } func (t *Test) GetNum() int { return t.Num } func main() { var test = &Test{ Num: 1, } test.Print() // result should be &{1} test.Twice() test.Print() // result should be &{2} test.Add(1) test.Print() // result should be &{3} fmt.Println(test.GetNum()) // result should be 3 }実行結果は↓のようになります。
&{1} &{2} &{3} 3WebAssembly用にコードを修正
コードの全体です。
package main import ( "fmt" "syscall/js" ) type Test struct { Num int } func (t *Test) Print(this js.Value, args []js.Value) interface{} { fmt.Println(t) return nil } func (t *Test) Twice(this js.Value, args []js.Value) interface{} { t.Num *= 2 return nil } func registerCallbacks() { var test = &Test{ Num: 1, } js.Global().Set("test", js.ValueOf( map[string]interface{}{ "Print": js.FuncOf(test.Print), "Twice": js.FuncOf(test.Twice), }, )) } func main() { c := make(chan struct{}, 0) registerCallbacks() <-c }コードの解説
WebAssemblyから呼び出せるように関数の定義を変更する
WebAssemblyから呼び出すときの作法に従い、引数を
js.Value
とargs[]js.Value
、 返り値をinterface{}
にします。func (t *Test) Print(this js.Value, args []js.Value) interface{} { fmt.Println(t) return nil }引数を参照する
引数の値を直接Go側で使うことはできないので、
args[0].Int()
を呼び出して整数型に変換します。func (t *Test) Add(this js.Value, args []js.Value) interface{} { t.Num += args[0].Int() return nil }返り値を戻す
返り値はそのまま返せないので、
js.ValueOf()
で返します。func (t *Test) GetNum(this js.Value, args []js.Value) interface{} { return js.ValueOf(t.Num) }構造体のメソッドのjavascriptへの登録
js.Global()
を呼び出して、javascriptの空間にtest.Print
、test.Twice
、test.Add
、test.GetNum
をそれぞれ登録します。func registerCallbacks() { var test = &Test{ Num: 1, } js.Global().Set("test", js.ValueOf( map[string]interface{}{ "print": js.FuncOf(test.Print), "twice": js.FuncOf(test.Twice), "add": js.FuncOf(test.Add), "getNum": js.FuncOf(test.GetNum), }, )) }関数の登録
関数の登録を呼び出します。main関数が終了しないように、チャネルを使って処理をブロックします。
func main() { c := make(chan struct{}, 0) registerCallbacks() <-c }テストする
以下の、htmlファイルでテストをします。
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Go wasm</title> </head> <body> <script src="wasm_exec.js"></script> <script> const go = new Go(); WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then((result) => { go.run(result.instance); }); </script> <script> function getNum(){ console.log(test.getNum()) } </script> <button onClick="test.print();" id="runButton" >print</button><br> <button onClick="test.twice()" id="runButton">twice</button><br> <button onClick="test.add(1)" id="runButton">add</button><br> <button onClick="getNum()" id="runButton">get test.Num</button><br> </body> </html>WebAssemblyの実行に使う wasm_exec.js (go1.12.7用)は、公式から落とせます。
また、Goのファイルは、
GOOS=js GOARCH=wasm go build -o test.wasm
でビルドしておきます。上記のhtmlファイル、
wasm_exec.js
,test.wasm
をサーバ上に置いてアクセスすると、以下のようにボタンが表示されます。html
,wasm_exec.js
,test.wasm
コマンドラインで確認したときと同様に、
twice
、add
、get test.Num
を押すと、ブラウザのコンソールに以下のように表示されました。&{1} &{2} &{3} 3構造体は直接渡せない?
構造体を直接登録するのは、試してみましたができないようでした。
panic: ValueOf: invalid value
が出ます。js.ValueOf()の実装を見ると、任意の構造体は扱えない感じになっていました。func registerCallbacks() { var test = &Test{ Num: 1, } js.Global().Set("test", js.ValueOf(test)) }ソース
githubに置いておきました。
- 投稿日:2019-07-28T09:07:48+09:00
GolangとDBを接続して株価データをチャート表示する
酒井潤さんのUdemy講座を受講していて、
自分でもGolangとDBを接続して、株価データをチャート表示したくなったので、方法をまとめてみました。講座ではsqlite3を使用されていましたが、僕はSQLServerを使用しています。基本的にGolangでは一般的なDB(msSqlとか)の接続方法は大体同じだと思います。
※ソース内のコードはエラーハンドリングとかimportとかは省略していますので、悪しからず。
実装例(4565:そーせいのチャート)
テーブル構造
main.go
main.gofunc main() { controllers.StartWebServer() }webserver.go(contorllers>webserver.go)
webserver.govar templates = template.Must(template.ParseFiles("app/views/data.html")) func dataHandler(w http.ResponseWriter, r *http.Request) { //4565:そーせいのデータを表示 df, _ := models.GetData("4565") err := templates.ExecuteTemplate(w, "data.html", df) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func StartWebServer() error { http.HandleFunc("/data/", dataHandler) return http.ListenAndServe(":8080", nil) }template.Must(template.ParseFiles("app/views/data.html"))
template.Mustはtemplate.ParseFiles(~)で返されたtemplateのポインタ値をエラーをラッピングして、
templateだけを返してくれます。df, _ := models.GetData("4565")
GetDataに株価コードを渡すようにしました。
stockdata.go(models>stockdata.go)
stockdata.gotype StockData struct { StockCode string `json:"stockcode"` Prices []Price `json:"price"` } type Price struct { Time time.Time `json:"time"` Open float64 `json:"open"` Close float64 `json:"close"` High float64 `json:"high"` Low float64 `json:"low"` Volume int32 `json:"volume"` } func GetData(stockcode string) (df *StockData, err error) { db, err := sql.Open("sqlserver", "server=.;user id=sa;password=password;database=Stock") defer db.Close() cmd := fmt.Sprintf(`SELECT ~`) rows, err := db.Query(cmd) df = &StockData{} for rows.Next() { var price Price rows.Scan(&price.Time, &price.Open, &price.Close, &price.High, &price.Low, &price.Volume) df.Prices = append(df.Prices, price) } }db, err := sql.Open("sqlserver", "server=.;user id=sa;password=password;database=Stock")
sql.Openの第一引数に"sqlserver"、第二引数に接続文字列をセットします。
data.html
data.html<!DOCTYPE html> <html> <head> <title>Page Title</title> <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script> <script type="text/javascript"> google.charts.load('current', {'packages':['corechart']}); google.charts.setOnLoadCallback(drawChart); function drawChart() { var data = google.visualization.arrayToDataTable([ {{ range .Prices }} ['{{.Time}}'.substring(0, 10),{{ .Low }},{{ .Open }},{{ .Close }},{{ .High }}], {{end}} ], true); //お好みで設定 var options = {...} var chart = new google.visualization.CandlestickChart(document.getElementById('chart_div')); chart.draw(data, options); } </script> </head> <body> {{ .StockCode }} <div id="chart_div" style="width: 900px; height: 500px;"></div> </body> </html>[options]に設定する内容はこちら
{{ range .Prices }}
Prices([]Price)をループさせます。
['{{.Time}}'.substring(0, 10),{{ .Low }},{{ .Open }},{{ .Close }},{{ .High }}],
ループ中の現在のPrice構造体から日付、安値、始値、終値、高値を順にセットします。
※{{.Time}}から日付(yyyy/MM/dd)だけを抽出する良い方法が浮かばなかったので、substringで対応しました。(良い方法をご存知の方は教えていただけると幸いです){{end}}
ループを終わります。
これで株価データのチャート表示ができるようになりました。今後はフロントエンド、バックエンド部分の機能拡張を進めていきます。
- 投稿日:2019-07-28T00:54:02+09:00
Goで設計に役立つEnumの実装を考える
はじめに
JavaでEnumを使った設計パターンはよくあるんですが、Goで同等のことをどうやるか考えてみました。
料金区分を例に
現場で役立つシステム設計の原則ではJavaのEnumを使った料金区分の設計と実装が紹介されています。
この例を参考にGoで適用するとどうなるか考えてみました。まずは本と同様に料金区分をinterfaceで実装します。
type Fee interface { Yen() int Label() string } type adultFee struct{} func (adultFee) Yen() int { return 1500 } func (adultFee) Label() string { return "大人" } type childFee struct{} func (childFee) Yen() int { return 1000 } func (childFee) Label() string { return "子供" }料金区分(Fee)をinterface
大人料金(adultFee)と子供料金(childFee)はFeeのinterfaceを満たしています。設計でJavaのEnumを使う魅力
- 定義したものだけを呼べて、定義していないものを呼び出すとコンパイルエラーになる。
- valueOfメソッドで文字列からenumの値を取得できる
GoでのEnum実装 - 変数で定義
Goで実装してみます。
var FeeType = struct { Adult Fee Child Fee ValueOf func(s string) (fee Fee, err error) }{ Adult: adultFee{}, Child: childFee{}, ValueOf: func(s string) (fee Fee, err error) { switch s { case "adult": fee = adultFee{} case "child": fee = childFee{} default: err = fmt.Errorf("未定義の料金タイプ %s", s) } return }, }ポイントは定義したものだけを呼び出せるようにstructのメンバーとして定義済みの値を用意しました。
また、valueOfについてもstructのメンバーとして追加しています。GoでのEnum実装 - 関数で定義
変数での定義が気になる方は、こんな方法も
func FeeType() struct { Adult Fee Child Fee ValueOf func(s string) (fee Fee, err error) } { return struct { Adult Fee Child Fee ValueOf func(s string) (fee Fee, err error) }{ Adult: adultFee{}, Child: childFee{}, ValueOf: func(s string) (fee Fee, err error) { switch s { case "adult": fee = adultFee{} case "child": fee = childFee{} default: err = fmt.Errorf("未定義の料金タイプ %s", s) } return }, } }変数と違い関数なので間違っても上書きされないので安心です。
まとめ
今回はGoでJavaのEnum相当の機能を作り込む方法を考えてみました。
こんなやり方もあるよという方がいればコメント欄などで教えてもらえると嬉しいです。参考