golang に関する投稿を表示しています

Google Cloud Spanner のスロークエリ(的なもの)を集める spanner-query-stats-collector を作った

こちらです。
sters/spanner-query-stats-collector: Google Cloud Spanner's query stats collector


Google Cloud Spanner には発行されたクエリがどれくらい CPU をつかったのか、どれだけのスキャンをしたかといった情報が記録されていくテーブルがあります。
Query statistics tables | Cloud Spanner | Google Cloud

このテーブルは GCP コンソール Spanner 上からでも参照できますし、実際に存在しているテーブルのようなので SELCT して取得することもできます MySQL でいうところのスローログをテーブルに書き出しているような状態です。
これを見ることによって CPU をよく使うクエリが出ていないか、スキャンが多すぎるのでインデックスの最適化ができるのではないか、などといった Spanner を利用することに関して改善していくことができるようになります。または、開発/検証環境として使える Spanner データベースがあれば、そこでの様子を定期的にウォッチすることで、本番に出す前のクエリの怪しさも一定見ることができる、かもしれません。
(本番の場合はレコード数や発行回数が異なりすぎて、参考にならないかもしれませんが。。)

と、いうところから、これを作りました。

cmd 以下の main では 1 分おきに 1 分間のクエリ統計テーブルを参照し JSON で標準出力へと書き出します Dockerfile として作ったものはそれの docker 化したものです。
Credential さえ入ればクエリが発行できるので、例えば GCP の各種コンピューティングリソース(GCE GKE CloudRun などなど)からの利用は容易にできるはずです Spanner を参照できる権限を付与したサービスアカウントがあれば使えると思います(手元の認証情報でしか試してないので。。)

LTSV なデータを SQL で探索する ltsvq を作った

sters/ltsvq: ltsvq is LTSV Queryer that written in Go

Labeled Tab-separated Values (LTSV) なアクセスログのデータをパースし、少しだけ複雑な条件で集計する必要があった。
ELKStackなんかで集計する仕組みを整えて構築するよりも、さくっとできるソリューションでよかった。
ぱっと調べても便利に使えそうなのはなさそうに見えたので、ぱっとひらめいたアイデアで行けるかどうか、おためし的に作った。

実装は GitHub - Songmu/go-ltsvGitHub - mattn/go-sqlite3: sqlite3 driver for go using database/sql がほぼすべて。
パースした結果を SQLite にオンメモリで入れて、リクエストされたクエリを実行し、取れたものを再び LTSV になおして出力する。
SQLite でできることはできるので、絞り込みも並び替えも、集計だってできる。

と、まあ作ること自体はすぐだったのだが GitHub - goreleaser/goreleaser: Deliver Go binaries as fast and easily as possible でリリースを作ろうとしたら数時間積んでいた。
色々調べた結果、必要なライブラリが詰まった Docker イメージを準備し、その上でスタティックリンクになるように ldflags を指定してビルドするのが正解らしい CGO も効いているはず、きいてないと mattn/go-sqlite3 が動かんのでは…?

このあたり cmd/go: build: add -static flag · Issue #26492 · golang/go

ストリーミングなデータの tail -f みたいなずっと流れてくるデータをパイプして使うようなものは、どうしたらいいのかわからなかったので諦めてしまった。また今度挑戦してみよう。

-----

追記: ググり方が悪かったようで lltsv なるものがすでにあることを教えてもらった。
GitHub - sonots/lltsv: List specified keys of LTSV (Labeled Tab Separated Values)

静的コンテンツをシュッと共有する何かの onstatic を作った

ざっくりというと Web サイト(not Web アプリケーション)を作ったときに、公開はまだなんだけど共有したいなっていうのをなんとかする良さげソリューションがないような気がして自作した。

sters/onstatic: onstatic is static page hosting controller.

ビルドされたバイナリを動かしさえすればどこでも出来る。
都合によって CGI 対応をしたので、恐らく FTP 的なことしか出来ないようなレンタルサーバーでも使えるはず。少なくとも Apache で CGI するように設定したら動く。

使う手順はこんな具合。

  1. /registerにリクエストし、Git リポジトリをクローンできる URL をアプリケーションに登録する
  2. アプリケーション内でローカル git リポジトリの準備と SSH キーが生成、保存される。公開鍵がレスポンスになるので、メモしておく
  3. 公開鍵をクローンできるように登録
  4. /pullへリクエストすると 2 で保存した秘密鍵を使って 1 で指定したリポジトリを取りに行く
  5. リポジトリ名をハッシュ化したものがレスポンスされる
  6. アプリケーション URL/ハッシュ値/ファイルパス 的な URL でアクセスできるようになる
  7. リポジトリを更新したら /pullを呼べば git pull をしてくれる

Github であれば DeployKey に登録してしまうのがお手軽。書き込みすることは想定していないし。


実装は go-git を使っていて、中で git クライアントな振る舞いをしている。

src-d/go-git: A highly extensible Git implementation in pure Go.

この中で使っている billy.Filesystem がすごい便利だった。ファイル操作するテストとか書くのちょっとつらそうだなあと思ったけれど、インターフェース化されていて memfs な実装もあった。

src-d/go-billy: The missing interface filesystem abstraction for Go

アプリケーションサーバへのアクセスは、一定のフィルタしたあとに http.FileServer.ServeHTTP をしている。

onstatic/handler.go at master · sters/onstatic

その他、Github Actionsを使ってみたりなどした。リリース作るのがほぼ設定いらずの簡単サクッといけるの便利。


でもって、静的ファイルをシュッと共有できて便利になったのだった
1 バイナリでただのアプリケーションサーバなので nginx でプロキシしたり、認証挟んだり、いろいろ便利、かもしれない。

まあ Netlify 的なものなどでいいといえばいい。
Netlify: All-in-one platform for automating modern web projects.

CREATE TABLE 文から Markdown のテーブル表記をつくる

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

ドキュメントにテーブルの説明を書くのがちょっと苦しかったので CREATE TABLE 文から Markdown のテーブル表記を作れるようにした。

GitHub - sters/createtable2Markdown

CREATE TABLE 文をパースできれば勝てるので別に MySQL 限定とかではない。特殊すぎる記載じゃなければ動くと思う。

GitHub - xwb1989/sqlparser: SQL Parser implemented in Go

これがすごくて、若干の挙動が怪しい不要な要素をスルーするようにしてるけれども、あとはパース結果を組み立てる以外なにもしてない。

標準入力で SQL な文字列を受け取って、標準出力で Markdown が出せるようにしているので、ダンプしたものをそのまま流したり、クリップボードを活用したり、コマンドラインでサクサク使うのにめちゃいい。
Readme にも書いたけどこういうことができる。

mysqldump -u database_user -p -d -n --compact database_name | createtable2Markdown

Google Cloud Build + kaniko を試す

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

Cloud Build のドキュメントを見ていたら、ビルドを高速化するのに kaniko を使うソリューションがあるよ、と書かれていたので、試してみる。

Kaniko キャッシュの使用 | Cloud Build | Google Cloud
GitHub - GoogleContainerTools/kaniko: Build Container Images In Kubernetes

kaniko っていうのはすごいざっくりと理解したのは Docker なしに k8s 上で Docker イメージをビルドできるよ、というもの。
で、そのとき対象の Dockerfile に関して、レイヤーごとに全部キャッシュを取ってくれるので、差分があった部分以降をビルドするだけになり docker build よりも高速化できる、という話っぽい。通常 docker build するときも、レイヤーごとの情報を持っていて、変わった部分以降をビルドすることができる。できるが、そのキャッシュのバックエンドとか、マルチステージにしたときのビルドステップの進め方とかをいい感じにしたっていう話なんじゃあないかなあ。(それ以上の理解はできていない)

さて、ビルドする対象となる Dockerfile はこういう具合。
マルチステージで go で書かれたアプリケーションサーバのビルド、からのそのアプリケーションコンテナのイメージを作る。

FROM golang:1.12 as builder
ARG GITHUB_TOKEN
ARG VERSION
ENV GO111MODULE=on

WORKDIR /go/src/github.com/foo/bar

COPY go.mod go.sum ./
RUN echo "machine github.com login ${GITHUB_TOKEN}" > ~/.netrc
RUN go mod download

COPY . ./
RUN CGO_ENABLED=0 GOOS=linux go install -v -ldflags "-X main.version=${VERSION}" github.com/foo/bar/cmd/server

FROM alpine:latest

RUN apk add --update --no-cache ca-certificates tzdata
COPY --from=builder /go/bin/server /bin/server
CMD ["/bin/server"]

それでもとの cloudbuild.yaml はこうなっている。
変数をバケツリレーしていく。

steps:
- name: gcr.io/cloud-builders/docker
  args:
  - build
  - -t
  - $_IMAGE
  - --build-arg
  - GITHUB_TOKEN=$_GITHUB_TOKEN
  - --build-arg
  - VERSION=$_VERSION
  - -f
  - $_DOCKERFILE_PATH
  - .
images:
  - $_IMAGE

で、これを kaniko に対応させるとこうなる。

steps:
- name: 'gcr.io/kaniko-project/executor:latest'
  args:
  - --destination=$_IMAGE
  - --dockerfile=$_DOCKERFILE_PATH
  - --cache=true
  - --cache-ttl=6h
  - --build-arg=GITHUB_TOKEN=$_GITHUB_TOKEN
  - --build-arg=VERSION=$_VERSION

その他 GCP 設定や Dockerfile などなどはそのままで実行することはできた。

できたが結果はちょっと微妙で、めっちゃ早くなったかというとそうでもない。。。

ビルド時、主に時間がかかるのが go mod download と go install (実際は go build が時間かかる)のふたつで、アプリケーションのコードを変えただけの場合は go mod のキャッシュが効いてくれるが、レイヤーを持ってくる(?)のにやっぱり時間がかかるので、プラスマイナスみて、ややマイナス、みたいな状態。。

具体的な時間としては kaniko を使う前の元々ので 5 分くらい。 kaniko してフルキャッシュ状態(再実行しただけ)で 1-2 分。 go mod のキャッシュが効いてて 2-4 分くらい。キャッシュがまったくなし = go.mod が変更された場合で 5-7 分くらい。ちゃんと測ってなくて数回ためした程度で、実はもっと早いかもしれない。そこは試す余地がある。

あとは単純にお財布で戦って、マシンサイズを上げて強い子にすると早くなるんじゃないかなあ~。


そもそも、コンテナイメージの作成を早くするのが目的なら CI 上でクロスコンパイルしたバイナリを Docker にいれちゃうのがよさそう。
というか CI 上でも golang:1.12 なイメージでビルドするならば別にクロスコンパイルでもないのか。

マルチステージな Dockerfile でアプリケーションのビルドもまるっと!と Cloud Build でビルドすると、余計なゴミが入りにくい、という良い点はありそう。

とはいえ、ワークスペースをアタッチしない、かつ go mod だけ共有してビルドしちゃえば Cloud Build でやるのとほぼ同じ環境になりそうな気がする。そうしてできたバイナリをコンテナ内に COPY して、コンテナレジストリに登録しちゃえばいいんじゃないかな。

grpc-go の interceptor を理解した

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

元実装はここ
GitHub - grpc/grpc-go: The Go language implementation of gRPC. HTTP/2 based RPC

Interceptor の例としてはここ
GitHub - grpc-ecosystem/go-grpc-middleware: Golang gRPC Middlewares: interceptor chaining auth logging retries and more.

要はなにかというと Laravel をやっていたときに使っていた Middleware と自分では理解した。
Middleware - Laravel - The PHP Framework For Web Artisans

リクエストを処理する前後に、任意の処理を差し込めるもの。

リクエストを中断したり、ウェイトしたり、前処理したり。
あるいはリクエスト処理の後に、かかった時間やステータスについてのメトリクスを取ったり、といったことができる。

これはサーバだけでなく、クライアント側にも差し込めるので、クライアントリクエストする前に何処に対してリクエストしようとしているか、とか、なんか色々できそうではある。

自分でも書いてみる。

func AccessLogUnaryServerInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
		clientIP := "unknown"
		if p, ok := peer.FromContext(ctx); ok {
			clientIP = p.Addr.String()
		}

		ua := ""
		if md, ok := metadata.FromIncomingContext(ctx); ok {
			if u, ok := md["user-agent"]; ok {
				ua = strings.Join(u, ",")
			}
		}

		ctxzap.AddFields(
			ctx,
			zap.String("access.clientip", clientIP),
			zap.String("access.useragent", ua),
		)

		return handler(ctx, req)
	}
}

これは UnaryServerInterceptor で、つまりは単一リクエスト用のもの。
peer を使ってリクエスト元 IP と Metadata に入っている UserAgent を取得し、それを ctxzap を使って追加フィールドとして設定しておくもの。

なので go-grpc-middleware の grpc_zap.UnaryServerInterceptor が設定されていないと使えない。(そこで ctxzap が設定されるので)
go-grpc-middleware/logging/zap at master · grpc-ecosystem/go-grpc-middleware · GitHub

peer はここ。リクエスト元についての情報が簡単に入っている。
grpc-go/peer at master · grpc/grpc-go · GitHub

Metadata についてはここ。ざっくりとは HTTP Header みたいなやつ、と理解したけど大体あってそう。
grpc-go/grpc-metadata.md at master · grpc/grpc-go · GitHub

ctxzap は先に出した go-grpc-middleware で定義されるもの。
go-grpc-middleware/logging/zap/ctxzap at master · grpc-ecosystem/go-grpc-middleware · GitHub

gRPC リクエストの context に zap.Logger を出し入れできるもので、リクエストごとに Logger が設定されているような状態にできる。
ので、例えばリクエストの認証情報を詰めたりとかできて便利ちゃん。

実際に組み込もうとするとこんな感じ。

func NewGRPCServer() *grpc.Server {
	return grpc.NewServer(
		grpc_middleware.WithUnaryServerChain(
			grpc_zap.UnaryServerInterceptor(zap.L()),
			interceptor.AccessLogUnaryServerInterceptor(),
		),
	)
}

で、この interceptor を使った gRPC サーバに対してリクエストを送ってみると、こんな感じにログがでる。よさそう

{
  "level": "info",
  "ts": 1562940752.853247,
  "caller": "zap/server_interceptors.go:40",
  "msg": "finished unary call with code OK",
  "grpc.start_time": "2019-07-12T23:12:32+09:00",
  "system": "grpc",
  "span.kind": "server",
  "grpc.service": "helloworld.Greeter",
  "grpc.method": "SayHello",
  "access.clientip": "127.0.0.1:65516",
  "access.useragent": "my-user-agent/1.0.0 grpc-go/1.19.1",
  "grpc.code": "OK",
  "grpc.time_ms": 0.3019999861717224
}

ちなみにクライアントから UserAgent をどうやって設定するかはこうする。

client, err := grpc.DialContext(ctx, address, grpc.WithUserAgent("my-user-agent/1.0.0"), ...)

後ろに grpc-go/1.19.1 的な文言がついてしまうのはそうなんだけど、まあ、いいんじゃないかな。


あと余談なのだけど grpc-go と go-grpc-middleware とで grpc と go が入れ替わってていつもどっちだ!?!?となる


ワンソースでドッと試したのはこんなかんじ。
example にある helloworld な proto をそのまま利用するとお手軽簡単に gRPC 周辺の様子みれて便利。

package main

import (
	"context"
	"net"
	"strings"
	"time"

	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
	"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	pb "google.golang.org/grpc/examples/helloworld/helloworld"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/peer"
)

// server is used to implement helloworld.GreeterServer.
type server struct{}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	ctxzap.Extract(ctx).Info("Received", zap.Any("in.name", in.Name))
	return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

func main() {
	port := "127.0.0.1:13000"

	l, _ := zap.NewDevelopment()
	zap.ReplaceGlobals(l)

	// server
	go func() {
		logger := zap.L().Named("Server")

		listener, err := net.Listen("tcp", port)
		if err != nil {
			logger.Error("failed to listen: %v", zap.Error(err))
		}
		defer listener.Close()

		s := grpc.NewServer(
			grpc_middleware.WithUnaryServerChain(
				grpc_zap.UnaryServerInterceptor(logger),
				accessLogUnaryServerInterceptor(),
			),
		)
		defer s.Stop()

		pb.RegisterGreeterServer(s, &server{})

		if err := s.Serve(listener); err != nil {
			logger.Error("failed to serve: %v", zap.Error(err))
		}
	}()

	// client
	go func() {
		time.Sleep(2 * time.Second)
		ctx := context.Background()
		logger := zap.L().Named("Client")

		client, err := grpc.DialContext(
			ctx,
			port,
			grpc.WithUserAgent("my-user-agent/1.0.0"),
			grpc.WithInsecure(),
		)
		defer client.Close()

		if err != nil {
			logger.Error("failed create client", zap.Error(err))
		}

		greeterClient := pb.NewGreeterClient(client)

		logger.Info("Do request")

		response, err := greeterClient.SayHello(ctx, &pb.HelloRequest{Name: "sters"})

		logger.Info("", zap.Any("response.Message", response.Message), zap.Error(err))
	}()

	time.Sleep(5 * time.Second)
	zap.L().Info("Shutdown")
}

func accessLogUnaryServerInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
		clientIP := "unknown"
		if p, ok := peer.FromContext(ctx); ok {
			clientIP = p.Addr.String()
		}

		useragent := ""
		if md, ok := metadata.FromIncomingContext(ctx); ok {
			if ua, ok := md["user-agent"]; ok {
				useragent = strings.Join(ua, ",")
			}
		}

		ctxzap.AddFields(
			ctx,
			zap.String("access.clientip", clientIP),
			zap.String("access.useragent", useragent),
		)

		return handler(ctx, req)
	}
}

最近パッケージ読み込み出来るようになったって聞いたので The Go Playground で実行できる。
ココ にある

前後の記事

Next:
Prev:

Confuluence を Markdown で書く

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

Confuluence の世界に Markdown で書いてたものを持っていくとか Markdown でサクッと書きたいなあ、などというのが微妙に不便しているので、誰かベストソリューションを教えてほしい。

Markdown for Confluence | Atlassian Marketplace

これは Confuluence のページに Markdown なコンテンツを埋め込める、というもの。
Markdown で Confuluence のページを書けるといえばそう。けど、これだと Markdown の世界と Confuluence の世界がつながらないので、ちょっと微妙…、かも?

Confluence Wiki マークアップ - アトラシアン製品ドキュメント

マークアップ機能はでっかい Markdown や、やや処理にクセがあるっぽく、うまく出来ないときの修正の仕方がいまいちわからない。
ただ Confuluence Wiki 記法はうまく処理されるっぽい。
というわけで Markdown から Confuluence Wiki 記法にするそれっぽいのを書いてみて、手元だけはすこし楽できるようにしてみた。

GitHub - sters/md2cw: Markdown syntax to Confluence Wiki syntax

converter を見てもらうとわかるけれど Markdowm のパーサーやレンダラーとして動いてくれる blackfriday というのがある。

GitHub - russross/blackfriday: Blackfriday: a Markdown processor for Go

ぼくはそれぞれの要素が Confuluence でいうどれにあたるかを書いたくらいしかしていないくらいに、世の中は便利。


git(というよりは svn が近いか)のそれみたいに、プル、プッシュ、マージが出来るようなクライアントとかあってもいいかもなーとか思った。
そうなるともはや Confuluence である必要はそんなにないかもしれないけれども。。


それはそうとして、このリポジトリ、go getでうまくいれられないくない?(日本語崩壊)
go modulesとgo getのお気持ちがちょっとまだわからない…

grpc のリクエストをガシガシ送るツールghz

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

gRPC なエンドポイントのパフォーマンスをみたり、負荷試験をしたいなあという話をしたら ghz というツールがあるよと教えてもらったので、これを使ってみる。

ghz · Simple gRPC benchmarking and load testing tool

公式にはリリースページからインストールしてくれやと書かれている。
が go get でとれるのでこれでいいのでは… latest を使うことになるのでリリースされてるものと微妙に差異があるかも。

go get github.com/bojand/ghz/cmd/ghz/

ab ライクに使えるので、ヘルプを見たらまあ問題なく使えるはず。
Usage · ghz

Kubernetes 上で動くアプリケーションに対して実施するとこんな感じ。
ポートフォワードしてから、

kubectl port-forward svc/foo 5000

※Service に向けてポートフォワードしてますが kubectl コマンド実行時に宛先 Pod を解決してしまうので、ロードバランスしないです。

別ターミナルで、こういう具合 proto の指定がないときは Server Reflection して勝手にやってくれる、超便利。
そっちの話題はここにある。 grpc/server-reflection.md at master · grpc/grpc · GitHub

$ ghz \
    --call foo.bar.Service.RPC \
    -d "$(jo foo='bar')" \
    -m "{\"Authorization\":\"BEARER $TOKEN\"}" \
    -q 4 \
    -c 2 \
    -n 10 \
    --insecure \
    localhost:5000

Summary:
  Count:    10
  Total:    1.37 s
  Slowest:    243.03 ms
  Fastest:    111.49 ms
  Average:    149.98 ms
  Requests/sec:    7.29

Response time histogram:
  111.491 [1]    |∎∎∎∎∎∎∎∎
  124.645 [5]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  137.799 [0]    |
  150.953 [2]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  164.107 [0]    |
  177.261 [0]    |
  190.415 [0]    |
  203.569 [0]    |
  216.723 [0]    |
  229.877 [0]    |
  243.031 [2]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎

Latency distribution:
  10% in 118.75 ms
  25% in 119.66 ms
  50% in 123.21 ms
  75% in 243.01 ms
  90% in 243.03 ms
  0% in 0 ns
  0% in 0 ns

Status code distribution:
  [OK]   10 responses

リクエストパラメータつくるのに jo を使っている。
GitHub - jpmens/jo: JSON output from a shell

上記の例だと 最大 4 req/sec になるように、 2 並列でリクエストし、 10 リクエスト送ったら終わる、というオプションになっている。
なので、レスポンスタイム次第ではあるが、サクッとレスポンスがあれば 1 秒ちょっとで完了する。

レスポンスタイムの分布であったり、パーセンタイルもでたり、サクッと負荷かけて様子を見るのに便利そう。

もちろん、オプションの指定が必要だけど 1 リクエストだけ送ることもできるので、普段使いにもいいんじゃなかろうか。

また Go のパッケージとしても使えるっぽい。
Package · ghz
ちょっと用途が思い浮かんでないけど、例えば、複数の RPC があって、順番にテストしたいんだよね〜〜〜みたいなときとか CI 的に負荷をかけたいんだよね〜〜とか、(シェルスクリプトでさくっと ghz ラッパーでもいいのでは感)

Go で 定期的に Ping する

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

疎通確認であったり、コネクションのkeepalive的な用途として、定期的に "ping" をしたいんだよなあ、という話があり
ぐるぐる回しっぱなしでよきに使えそうな感じのコードを書いてみた。

GitHub - sters/pinger: Ping somethings for use warmup and standby or others.

いまは土台と、HTTPしかないけど、MySQLとかなんかまあ他にもできそう

使い方はここを見てもらうのがわかりやすいかも
pinger/http_test.go at master · sters/pinger · GitHub

実践的にはこういう感じになるのかな。

ctx := context.Background()

fooPinger := pinger.NewWorker(...)
barPinger := pinger.NewWorker(...)

eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error { return fooPinger.Run(ctx) })
eg.Go(func() error { return barPinger.Run(ctx) })

if err := eg.Wait(); err != nil {
    ...
}

すくなくとも、いま主に触っているソレだとこういう感じで書くことになると思う。

余談だけど...

  • type Hoge interface{}を書いて使ったりしていると、これがダックタイピングかとどっしり理解したので、やっばりやってみないとわからんなという気持ち
  • sync パッケージ、使うものの並列にアクセスされる可能性あるやろなあ、ロックするか、くらいの感覚しかまだわかってないので要修行
  • goroutine はちょっとわかってきた感じがある。おしごとでもぼちぼち書いたりしてる
  • これまで簡単なツールをサクッとつくるの Node.js か PHP 使ってたことが多いけどもう Go 言語でよいのでは、と思えるくらいには書けるようになってきた感じ。
  • よく使うようなライブラリとかツール群はまだイマイチなので都度 :innocent:

Withなんちゃらなオプション( Functional Option Pattern )

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

Go 文化なのかわからないけど New とか構造体作れる関数や初期化する関数へのオプションを設定するためのコールバック関数を設定するキメ文句をよく見るような気がする。

func WithInsecure() func(*Config) {
    return func(cfg *Config) {
        cfg.insecure = true
    }
}

func WithUserAgent(ua String) func(*Config) {
    return func(cfg *Config) {
        cfg.userAgent = ua
    }
}

func main() {
    req := NewRequest(
        WithInsecure(),
        WithUserAgent(),
    )
    req.Send()
}

こういう感じのやつ。

オプションを外のパッケージに対して隠せるっていうメリットがあるのかな。
流れに乗っておくと、雰囲気で利用できて便利そうだなーって思った。


追記:2019年3月4日
これは Functional Option Pattern だ、という話をまったく違う流れで社の Slack で知った。

Functional Option Pattern

Config 構造体でがんばって持つのもいいけど、文量増えてきたり、外に公開するような API であった場合にちょっと不便かなあと思っていたので、このデザインはいいなと思った。

1 2 3