Goのflagでダブルダッシュ(--hoge)なフラグを許可する

それヘルパー関数書くだけで出来ますよ!

と言う訳で書いた

double-dash hyphen

この実装が実現するのは

  • "-hoge" と "--hoge"を変数hoge *stringに受け取れる
  • "-str"と"--str"を変数str stringに受け取れる
  • UsageはflagのデフォルトFlagSetのものを使っているので、ダブルダッシュ版のFlagはUsageに出てこない
./flag2 -h
Usage of ./flag2:
  -hoge="default-value": First string option
  -str="default-value": Second string option

GoのHTTPサーバーを80番や443番ポートでListenする方法を調べた

1024以下の番号のポートでサーバーをListenするには、rootで実行する必要がある。それはもちろん嫌なので、GoのWebサーバーを80番ポートでサービスするためにどういう方法があるのか調べた。

root権限で起動してListenしてからSetuidで権限降格

最初はroot権限で起動して、80番ポートをListenしてからsetuidで権限降格するやりかた。 Node.jsでもそんな感じだったし、まあそういう感じだろうと当たりをつけて調べたら、どうもLinuxではうまくいかないらしい。

コメント欄でのid:methaneさんの指摘によると、Linuxのsetuidシステムコールは、実行したスレッドにしか効力が無い。Goは自動的に複数スレッドに分散されるので、権限降格されないまま実行されるGoroutineが出てくる事になり、望ましくない。さらにGo 1.4ではLinuxでのSetuid/Setgidが禁止されるらしい。Oh...

という訳で他の方法を探す。

rootで起動した起動スクリプトからポートのfdをもらってExec

Go で 1024 以下のポートを Listen するアプリを作る - methaneのブログ

id:methaneさんの実装。外部スクリプトで80番をListenして、そのポートのfdをコマンドラインオプションで与えてGoのサーバーをexecする。サーバの方は、ポートで起動する場合はgoprogram -port 8080、fd受け渡しの場合はgoprogram -fd 4みたいな感じで起動できるように実装を変更する必要がある。外部スクリプトの方はCircusのようなツールを利用することもできるようだ

トリッキーな起動オプションの実装が追加されるのは若干気持ち悪い気がするが、それさえ気にしなければ実装コストも少ないしパフォーマンスペナルティも多分ない。ソケット管理ツールを書くなり、既存のツールを学習するコストはそこそこ面倒くさそうで気になる。

iptablesでポートフォワーディングする

この方法であればサーバー側実装には全く手を入れる必要は無い。起動用のツールをrootで立ち上げる必要が無いのでクリーンな感じもする。ひとつのサーバーを動かすためにシステム設定に手を入れるのは気が引けるとか、運用コストが上がりそうとか思うが、Chefでiptablesの書き換えすればOKみたいな環境であれば手軽なのでは。

Deploying Go servers with Docker - The Go Blogとかを読んでも、Dockerを使うならDockerにポートフォワーディングを任せればいいみたいな感じがするので、環境側でなんとかする方向はありっぽい。

ローカルにHTTP層のReverse Proxyを使うという手もある。この場合プロキシを挟むのでパフォーマンスペナルティや、プロキシに使うサーバとの相性などが気になる。

実際、Nginxを挟む事によるパフォーマンスペナルティはちょっと無視できない。

あるいは今気づいたけど、もともとReverse Proxy用のサーバを別に立てるつもりなら、そもそも気にしなくてよい問題ではある。

GoバイナリにウェルノウンポートをListenするケーパビリティを設定する

ケーパビリティ で権限を少しだけ与える - いますぐ実践! Linuxシステム管理 / Vol.183

ケーパビリティとは、ファイルまたはスレッドに対して、root権限の一部を付与したり剥奪したりできるもの。実行ファイルに対してsetcapコマンドで任意の権限を与えられる。GoWebの人たちはこの方法を使っているらしい。この場合は以下のようになる。

setcap 'cap_net_bind_service=+ep' /path/to/program

Why not use Goweb? · Issue #49 · stretchr/goweb · GitHub

簡単にcapabilityを試してみた。通常のlsでは/root配下が覗けないが、読み込み時パーミッションチェックをスキップするCAP_DAC_READ_SEARCHケーパビリティをlsのコピーに付与したところ、sudoもなしに一般ユーザーで/root配下が参照できる。

[vagrant@localhost ~]$ ls /root
ls: ディレクトリ /root を開くことが出来ません: 許可がありません
[vagrant@localhost ~]$ cp /usr/bin/ls ./ls.copy
[vagrant@localhost ~]$ getcap ./ls.copy
[vagrant@localhost ~]$ sudo setcap 'CAP_DAC_READ_SEARCH=+ep' ./ls.copy
[vagrant@localhost ~]$ getcap ./ls.copy
./ls.copy = cap_dac_read_search+ep
[vagrant@localhost ~]$ ./ls.copy /root
anaconda-ks.cfg
[vagrant@localhost ~]$ 

ただ、このcapability、手元で検証した限りではcpなどで複製すると権限は失われてしまうようなので、デプロイ先の環境でsudo setcapしてやる必要があるようだ。

まとめ

今回候補に挙げた手法は以下の通り。

  • Listen後にsetuidで権限降格する
    • Linuxでは問題がある
  • 起動スクリプト(カスタムメイド or Circus)からfdをもらう
    • ソケット管理ソフトウェアを用意する必要がある
    • サーバー側にもfdによるListenに対応するコードを追加する必要がある
  • iptablesなど、OSやコンテナ側でポートフォワーディングする
    • システム側に手を加える必要がある
  • ローカルでProxyを立てる
    • パフォーマンスペナルティがある
  • ケーパビリティを設定する
    • デプロイ先ホストにバイナリを配置後、sudo setcapする必要がある。

この中では、ポートフォワーディングかケーパビリティが筋が良さそうな気がしている。せっかくシングルバイナリデプロイができるGoなのだし、できればバイナリで完結する手法でなんとかしたいが、そういう方法は今は無いようだ。

Golangで関数をグローバル変数に代入してテスト時にスタブする

こんにちは、小野マトペです。タイトル全部です。

昨日、現在時刻によって条件分岐するロジックを含むGoプログラムを書いていて、どうテストするか困ったのですが、うまい(と思う)やり方を思いついたのでここに書いておきます。

書いていたのはまあだいたいこういうソースコードです。

// main.go
package main

import (
        "fmt"
        "time"
)

func main() {
        fmt.Println(Greet("マトペ"))
}

func Greet(n string) string {
        t := time.Now()
        if 6 <= t.Hour() && t.Hour() <= 18 {
                return fmt.Sprintf("こんにちは%sさん。今は%d時ですよ!", n, t.Hour())
        } else {
                return fmt.Sprintf("こんばんは%sさん。今は%d時ですよ!", n, t.Hour())
        }
}
go run main.go
こんにちはマトペさん。今は12時ですよ!

で、このGreet関数をテストしたいんですが、このままでは夜の挨拶を出すのに18時まで待つ必要があります。それでは困るのでtime.Nowの部分をなんとかちょろまかしていつでも全ての条件をテスト出来るようしないといけません。いわゆるスタブ化ですね。

すぐに思い浮かぶのは、Greet関数を下のようにDIっぽく依存性である現在時刻を引数から受け取るように変更する方法だと思います。

// main.go
func Greet(n string, t time.Time) string { // 引数に現在時刻を追加
        if 6 <= t.Hour() && t.Hour() <= 18 {
                return fmt.Sprintf("こんにちは%sさん。今は%d時ですよ!", n, t.Hour())
        } else {
                return fmt.Sprintf("こんばんは%sさん。今は%d時ですよ!", n, t.Hour())
        }
}

これなら任意の時間の場合のGreet関数のユニットテストが書けますが、現在時刻のテストのために本来不要な引数が増えるのは大げさで好ましくありません(好みの問題です)。それに、結局コールスタックのどこかでtime.Now()を呼ぶ必要があり、その呼び出しポイントを含むテストはスタブ化できません。

ここはやはりtime.Now関数をテスト実行時だけ動的に上書きしたいところですが、Golangはあまりスタブ化に優しい言語ではなく、例えばtimeパッケージをテスト時に動的に他のパッケージに差し替えるとかはできません。これが自作パッケージで提供している構造体かなにかであれば、interfaceでもなんでも使ってスタバブルにすればいいと思いますが…。

(参考:go - Is there an easy way to stub out time.Now() globally in golang during test? - Stack Overflow

で、どうしたかというと、タイトル通りですがmain.goでtime.Nowの関数ポインタをグローバル変数に代入しておき、テスト時にmain_test.goで任意の時刻を返す関数にさしかえるという方法を使いました。

// main.go
package main

import (
        "fmt"
        "time"
)

var timeNowFunc = time.Now // 関数をグローバル変数に代入

func main() {
        fmt.Println(Greet("マトペ"))
}

func Greet(n string) string {
        t := timeNowFunc() // グローバル変数でtime.Now関数を呼び出し
        if 6 <= t.Hour() && t.Hour() <= 18 {
                return fmt.Sprintf("こんにちは%sさん。今は%d時ですよ!", n, t.Hour())
        } else {
                return fmt.Sprintf("こんばんは%sさん。今は%d時ですよ!", n, t.Hour())
        }
}

Goは関数が一級市民なのでシグネチャさえ合致すれば変数に代入できます。この場合、timeNowFunc変数は型推論func() time.Time型として宣言され、time.Now関数で初期化されます。で、これを次のようにテストします。

// main_test.go
package main

import (
        "testing"
        "time"
)

const timeformat = "2006-01-02 15:04:06" // timeのフォーマット指定文字列

// timeNowFuncグローバル変数の関数を入れ替える関数
func setNow(t time.Time) {
        timeNowFunc = func() time.Time { return t }
}

func TestMain(t *testing.T) {

        evening, _ := time.Parse(timeformat, "2014-08-14 14:10:00")
        night, _ := time.Parse(timeformat, "2014-08-14 22:30:00")

        // 昼のテスト
        setNow(evening)  // スタブ!timeNowFuncに昼時刻を返す関数をセット
        if ret := Greet("まとぺ"); ret != "こんにちはまとぺさん。今は14時ですよ!" {
                t.Errorf("Greet Fails. Got [%s]\n", ret)
        }

        // 夜のテスト
        setNow(night)  // スタブ!timeNowFuncに昼時刻を返す関数をセット
        if ret := Greet("まとぺ"); ret != "こんばんはまとぺさん。今は22時ですよ!" {
                t.Errorf("Greet Fails. Got [%s]\n", ret)
        }
}
$ go test
PASS
ok      _/Users/matope/dev/go/mock     0.006s

テスト内で宣言しているsetNow(time.Time)関数は、グローバル変数のtimeNowFuncに、任意の時刻を返す関数を代入する関数です。これでテストごとにtimeNowFuncの返す時刻をコントロールできるので、任意の時刻のテストが書けます。しかも面倒なスタブ用クラス実装や不要な引数の引き回しなしで、テスト時にのみ作用するスタブです。

グローバル変数と聞いてつらくなる人もいるかもしれませんが、Goのグローバル変数は実際にはパッケージスコープで、頭文字を小文字にしておけばパッケージの外からアクセスできないので、比較的クリーンです。Goはテストがコードと同じパッケージ空間で実行できるので、テストコードに対してのみExportされたパラメータとして扱えます。

この、テスト時にスタブしたい関数の呼び出しをグローバル変数でプロキシしておいてtest側の初期化で差し替える方法は、同一パッケージ内であればモック化のための追加の記述量が少なく、データベースのテストなど、いろいろ応用が利きそうだなと思いました。複数パッケージにまたがるtime.Now()を一括で差し替えたいような場合は、スタブプロキシ用のパッケージを作ってそこのExported(大文字から始まる)グローバル変数にセットしてやればよさそうですね。

ちなみに豆ですが、Grouped Globalを使えばこんな感じでちょっとかっこよくグローバル変数を呼べます。もはや紛らわしいような気もしますが。

// main.go
package main

import (
        "fmt"
        "time"
)

var _time = struct {
        Now   func() time.Time
        Since func(time.Time) time.Duration
}{
        time.Now,
        time.Since,
}

func main() {
        fmt.Println(_time.Now()) // ← ちょっとかっこいい
}

追記:同じやり方を解説してる記事 があったのでこっち読んだ方がよさそう

Go Concurrency Patterns: Context[翻訳]

7月29日付けのgolang.orgブログエントリーで context というパッケージが紹介されました。

Go Concurrency Patterns: Context - The Go Blog

参考: Go言語のcontextパッケージについてのやりとり - ワザノバ | wazanova

今現在、業務でGo言語を使ったWebサービスを書いているのですが、contextはリクエストのキャンセルとタイムアウトとリクエストスコープの変数を扱う、Google社内で利用が標準化されているパッケージだという事なので、エントリーを翻訳しました。

以下和訳。

Introduction

Goサーバーでは、入ってくる各リクエストは専用のgoroutineで処理されます。リクエストハンドラは、データベースやRPCサーバーにアクセスするためにしばしば追加のゴルーチンを開始します。リクエスト上のゴルーチンは、一般的にエンドユーザーのアイデンティティ、認証トークン、リクエストのデッドラインなどのリクエスト特有の情報にアクセスする必要があります。リクエストがキャンセルされるかタイムアウトした場合、システムが資源を再利用できるようにするため、そのリクエスト上で動作していた全てのゴルーチンは即座に終了すべきです。

Googleで私たちは、リクエストスコープの値、キャンセレーションシグナル、そしてAPI境界をまたぐデッドラインを、リクエスト処理に関わる全てのゴルーチンに渡す事を簡単にするためのcontextパッケージを開発しました。パッケージは code.google.com/p/go.net/contextで公開されています。この記事では、パッケージの使い方と完全な動作例を紹介します。

Context

contextパッケージの核となるのはContext型です:

// A Context carries a deadline, cancelation signal, and request-scoped values
// across API boundaries. Its methods are safe for simultaneous use by multiple
// goroutines.
type Context interface {
    // Done returns a channel that is closed when this Context is canceled
    // or times out.
    Done() <-chan struct{}

    // Err indicates why this context was canceled, after the Done channel
    // is closed.
    Err() error

    // Deadline returns the time when this Context will be canceled, if any.
    Deadline() (deadline time.Time, ok bool)

    // Value returns the value associated with key or nil if none.
    Value(key interface{}) interface{}
}

(この記述は要約です。godocが信頼できます)

DoneメソッドはContextを実行中の関数へのキャンセレーションシグナルとして振る舞うチャンネルを返します:そのチャンネルがクローズされたら、関数は自分たちの仕事を放棄してreturnするべきです。ErrメソッドはそのContextがキャンセルされた理由を示すerrorを返します。Pipelines and CancelationでDoneチャンネルイディオムについてより詳しく議論しています。

Contextには、Doneチャンネルがreceive-onlyであることと同じ理由によりCancelメソッドがありません:キャンセレーションシグナルを受信している関数は、通常そのシグナルを送信する関数と同じではないからです。特に、親オペレーションがサブオペレーションのためにゴルーチンを開始したとき、それらのサブオペレーションは親をキャンセルできるべきではありません。そのかわりに、WithCancel関数(後述)は新しいContext値をキャンセルする方法を提供します。

Contextは複数ゴルーチンによる同時利用に対して安全です。コードは単一のContextをいくつのゴルーチンにでも渡せますし、それら全てにシグナルしてそのContextをキャンセルする事も出来ます。

Deadlineメソッドにより、関数は自分が仕事を開始するべきかどうか判断することができます;もし残り時間がほとんどなければ、全くの無駄になるかもしれません。コードはI/Oオペレーションのタイムアウトを設定するのに使う事も出来ます。

Valueにより、Contextでリクエストスコープなデータを運ぶ事が出来ます。そのデータは複数ゴルーチンによる同時利用に対して安全である必要があります。

派生コンテキスト

contextパッケージは既存のContextから新しいContextを派生するための関数を提供します。これらの値はツリーを形成します; あるContextがキャンセルされたとき、そこから派生している全てのContextもまたキャンセルされます。

Backgroundは全てのContextツリーのルートです;決してcancelされることはありません。

// Backgroundは空のContextを返します。これは決してキャンセルされず、デッドラインを持たず、値も持ちません。
// Backgroundは一般的にmain,init,そしてtests内で、やってくるリクエストのトップレベルコンテキストとして使われます。
func Background() Context

WithCancelとWithTimeoutは、親Contextがキャンセルされるよりも早くキャンセルできる派生Contextの値を返します。到着したリクエストに関連づけられたContextは、一般的にはリクエストハンドラがreturnした時にキャンセルされます。WithCancelは複数レプリカ使用時に冗長なリクエストをキャンセルするのにも便利です。WithTimeoutは、バックエンドサーバーへのリクエストにデッドラインを設定するのに便利です:

// WithCancel は、parent.Doneがクローズされるかcancelが呼び出されると
// Doneチャンネルがクローズされるようなparentのコピーを返します
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

// CancelFuncはContextをキャンセルします
type CancelFunc func()

// WithTimeoutはparent.Doneがクローズされるかcancelが呼び出されるかtimeoutが
// 経過するとDoneチャンネルがクローズされるようなparentのコピーを返します。
// 新しいContextのDeadlineはnow+timeoutと、もしあればparentのdeadlineのうちの近い方です。
// もしtimerがまだ動いていれば、cancel関数はそのリソースをリリースします。
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

WithValueはrequest-scopedな値をContextに関連づける方法を提供します

// WithValue はValueメソッドがkeyに対するvalを返すようなparentのコピーを返します。
func WithValue(parent Context, key interface{}, val interface{}) Context

contextパッケージの使い方を見る一番の方法は、動いているサンプルを見る事です。

Example: Google Web Search

この例は、/search?q=golang&timeout=1s のようなURLを、クエリ"golang"をGoogle Web Search APIにフォワードして結果を表示するという風にハンドルするHTTPサーバーです。timeoutパラメーターはサーバーにこの時間が経過した後にリクエストをキャンセルするように指示します。このコードは三つの部分に分かれています:

  • server はmain関数と/search のハンドラを提供します
  • userip はユーザーIPアドレスをリクエストから取得してContextに関連づける関数を提供します
  • google はクエリをGoogleに送信するSearch関数を提供します

The server program

serverプログラムは/search?q=golang のようなリクエストに対して最初のいくつかのgolangGoogle検索結果を返します。ハンドラはctxと呼ばれる初期Contextを作成し、ハンドラがreturnした時にキャンセルされるように準備します。リクエストがtimeout URLパラメータを含んでいたら、Contextはtimeout経過後に自動的にキャンセルされます:

func handleSearch(w http.ResponseWriter, req *http.Request) {
    // ctxはこのハンドラのContextです。cancleを呼ぶと、このハンドラ
    // が開始したリクエストへのキャンセレーションシグナルである
    // ctx.Doneチャンネルがクローズします
    var (
        ctx    context.Context
        cancel context.CancelFunc
    )
    timeout, err := time.ParseDuration(req.FormValue("timeout"))
    if err == nil {
        // リクエストはタイムアウトを持つので、timeoutがexpireしたら
        // 自動的にキャンセルされるcontextを作ります
        ctx, cancel = context.WithTimeout(context.Background(), timeout)
    } else {
        ctx, cancel = context.WithCancel(context.Background())
    }
    defer cancel() // handleSearchがリターンしたらctxをキャンセル

ハンドラはリクエストからクエリを抜き出し、useripパッケージを呼び出す事でクライアントのIPアドレスを取得します。クライアントのIPアドレスはバックエンドリクエストに必要なので、handleSearchはそれをctxにくっつけます:

    // Check the search query.
    query := req.FormValue("q")
    if query == "" {
        http.Error(w, "no query", http.StatusBadRequest)
        return
    }

    // Store the user IP in ctx for use by code in other packages.
    userIP, err := userip.FromRequest(req)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    ctx = userip.NewContext(ctx, userIP)

ハンドラはctxとqueryでgoogle.Searchを呼び出します:

    // Run the Google search and print the results.
    start := time.Now()
    results, err := google.Search(ctx, query)
    elapsed := time.Since(start)

検索が成功したら、ハンドラは結果を表示します:

    if err := resultsTemplate.Execute(w, struct {
        Results          google.Results
        Timeout, Elapsed time.Duration
    }{
        Results: results,
        Timeout: timeout,
        Elapsed: elapsed,
    }); err != nil {
        log.Print(err)
        return
    }

Package userip

useripパッケージはリクエストからユーザーIPアドレスを抜き出してContextに関連づける関数を提供します。Contextはkey-valueマッピングを提供しますが、そのkeyとvalueはどちらもinterface{}型です。Key型は等価をサポートし、valueは複数ゴルーチンからの同時利用に対して安全である必要があります。useripのようなパッケージはこのマッピングの詳細を隠蔽し、特定のContext値への強く型付けされたアクセスを提供します。

キー衝突を避けるために、useripはexportされない型のkeyを定義してcontext keyとしてこの型の値を使います。

// The key type is unexported to prevent collisions with context keys defined in
// other packages.
type key int

// userIPkey is the context key for the user IP address.  Its value of zero is
// arbitrary.  If this package defined other context keys, they would have
// different integer values.
const userIPKey key = 0

FromRequestはuserIP値をhttp.Requestから抜き出します

func FromRequest(req *http.Request) (net.IP, error) {
    s := strings.SplitN(req.RemoteAddr, ":", 2)
    userIP := net.ParseIP(s[0])
    if userIP == nil {
        return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
    }

NewContextは与えられたuserIP値を運ぶ新しいContextを返します:

func NewContext(ctx context.Context, userIP net.IP) context.Context {
    return context.WithValue(ctx, userIPKey, userIP)
}

FromContextはuserIPをContextから抜き出します:

func FromContext(ctx context.Context) (net.IP, bool) {
    // ctx.Value returns nil if ctx has no value for the key;
    // the net.IP type assertion returns ok=false for nil.
    userIP, ok := ctx.Value(userIPKey).(net.IP)
    return userIP, ok
}

Package Google

google.Search関数はGoogle Web Search APIへのHTTPリクエストを作ってJSONエンコードされた結果をパースします。これはContextパラメータctxを受け入れ、もしリクエストを飛ばしている間にctx.Doneがクローズされたらすぐにリターンします。

Google Web Search APIリクエストはサーチクエリとユーザーIPをクエリパラメータに含みます:

func Search(ctx context.Context, query string) (Results, error) {
    // Prepare the Google Search API request.
    req, err := http.NewRequest("GET", "https://ajax.googleapis.com/ajax/services/search/web?v=1.0", nil)
    if err != nil {
        return nil, err
    }
    q := req.URL.Query()
    q.Set("q", query)

    // If ctx is carrying the user IP address, forward it to the server.
    // Google APIs use the user IP to distinguish server-initiated requests
    // from end-user requests.
    if userIP, ok := userip.FromContext(ctx); ok {
        q.Set("userip", userIP.String())
    }
    req.URL.RawQuery = q.Encode()

Searchはヘルパー関数httpDoを使ってHTTPリクエストを発行し、もしリクエストかレスポンスが処理中にctx.Doneがクローズされたらキャンセルします。SearchはHTTPレスポンスを処理するクロージャをhttpDoハンドルに渡します:

    var results Results
    err = httpDo(ctx, req, func(resp *http.Response, err error) error {
        if err != nil {
            return err
        }
        defer resp.Body.Close()

        // Parse the JSON search result.
        // https://developers.google.com/web-search/docs/#fonje
        var data struct {
            ResponseData struct {
                Results []struct {
                    TitleNoFormatting string
                    URL               string
                }
            }
        }
        if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
            return err
        }
        for _, res := range data.ResponseData.Results {
            results = append(results, Result{Title: res.TitleNoFormatting, URL: res.URL})
        }
        return nil
    })
    // httpDo waits for the closure we provided to return, so it's safe to
    // read results here.
    return results, err

httpDo関数は新しいゴルーチンの中でHTTPリクエストを実行してレスポンスを処理します。もしゴルーチンがexitする前にctx.Doneがクローズしたらキャンセルします。

func httpDo(ctx context.Context, req *http.Request, f func(*http.Response, error) error) error {
    // Run the HTTP request in a goroutine and pass the response to f.
    tr := &http.Transport{}
    client := &http.Client{Transport: tr}
    c := make(chan error, 1)
    go func() { c <- f(client.Do(req)) }()
    select {
    case <-ctx.Done():
        tr.CancelRequest(req)
        <-c // Wait for f to return.
        return ctx.Err()
    case err := <-c:
        return err
    }
}

コードをContextに適合する

多くのサーバーフレームワークがリクエストスコープな値を運ぶためのパッケージと型を提供しています。私たちは、既存のフレームワークとContextパラメータを期待するコードとの間をブリッジする新しいContextインターフェイスの実装を定義する事が出来ます。

例えば、Gorillaのgithub.com/gorilla/contextパッケージは、HTTPリクエストからkey-valueペアへのマップを提供する事でハンドラにデータをリクエストに関連づける事を許します。gorilla.goにおいて、私たちはGorillaパッケージ内で特定のHTTPリクエストに関連づけられた値を返すValueメソッドを持つContext実装を提供します。

他のパッケージは、Contextに似たキャンセレーションサポートをサポートしてきました。例えば、TombはDyingチャンネルをcloseすることでキャンセルをシグナルするKillメソッドを提供します。Tomはまたこれらのゴルーチンが終了するのを待つ、sync.WaitGroupに似たメソッドも提供します。tomb.goでは、私たちは親のContextがキャンセルされるか与えられたTombがkillされるかの場合にキャンセルされるContext実装を提供します。

結論

Googleでは、私たちはGoプログラマーにContextパラメーターをリクエストの到着から返却までの間に呼ばれる全ての関数の第一引数にContextパラメーターを渡す事を要求しています。これは、たくさんの異なるチームで開発されたGoのコードに良好な相互運用性をもたらします。タイムアウトとキャンセレーションのシンプルな制御を導入し、セキュリティクレデンシャルのようなクリティカルな値が適切にGoプログラム内を移動する事を確かなものにします。

Context上に構築したいサーバーフレームワークは、それらのパッケージとContextパラメータを期待するものの間をブリッジするContext実装を提供するべきです。そうすればそれらのクライアントライブラリは呼び出しコードからContextを受け入れることでしょう。リクエストスコープなデータとキャンセレーションのための共通インターフェイスを確立する事により、Contextはパッケージ開発者がスケーラブルなサービスを作るためのコードを共有するのを簡単にするのです。

By Sameer Ajmani

Patterns of Enterprise Application Architecture読書メモ(データソース編)

Martin FowlerのPatterns of Enterprise Application ArchitectureすなわちPoEAA、何年か前に原著をKindleで買って読もうとして序盤で挫折していた。最近またデータソースアーキテクチャについて翻訳版と原著を合わせて読んでいるので、整理のためメモを残しておく。

パターン

本書で紹介されるパターンはアプリケーションアーキテクチャから通貨の値オブジェクトまで、粒度もレイヤーも様々。今回はデータソースアーキテクチャパターンを理解するのに必要な箇所だけ調べた。

ドメインロジックパターン

  • トランザクションスクリプト
    • ほとんど構造化されてない書き下しなロジックのこと。ちょろいスクリプトならこれでもいいけど、ちょっと複雑になるとすぐに破綻する。
  • ドメインモデル
    • 業務上の概念をオブジェクト化して、それらを組み合わせて複雑なロジックを構成する。データマッパーサービスレイヤーパターンと組み合わせると良い。
  • テーブルモジュール
    • 上記二つの中間。RDBの1テーブルに対応するクラスでドメインロジックを構成するため、あまり複雑なアプリケーションには適合できないが、RDBなどとの相性は良い。
  • サービスレイヤ -

データソースアーキテクチャパターン

データベース操作のSQLとアプリケーションロジックの分離の仕方、ModelがControllerとデータベースの変換をどう扱うのかという話題。

  • テーブルデータゲートウェイ
  • 行データゲートウェイ
  • アクティブレコード
  • データマッパー

の4パターンが紹介されている。

テーブルデーゲートウェイ

P of EAA: Table Data Gateway

  • ひとつのデータベーステーブルがひとつのオブジェクトインスタンスになるゲートウェイ。テーブルへの操作(find,update,insert,delete)を備える。
  • Record Set(ジェネリックなデータ構造)やTable Module パターンとの併用に適している。
  • データベースの戻り値のRecord Setを返してもいいし、ドメインオブジェクトにマッピングして返してもいい。Record Setを返してデータ変換オブジェクトを用いれば、ユーザーが好きな方を選べて良いのではないか
  • ドメインモデルを使っている場合、テーブルデータゲートウェイドメインオブジェクトを返せるが、双方向の依存性が発生するのであんまり良くない
  • テーブルモジュール

// この例ではFindメソッドの戻り値にジェネリックなRecordSetを返却しているが、上の議論のように別にPersonオブジェクトを返しても問題はない。
class PersonGateway {
  Find(id): RecordSet
  FindWithLastNames(String): RecordSet
  Update(id, lastname,firstname, numberOfDependents)
  Insert(lastname, firstname, numberObDependents)
  Delete(id)
}
...
RecordSet r = personGateway.Find(1)
printf( r.Column("lastName") )
personGateway.Insert(2,"John","Doe",3)
personGateway.Update(2,"John","Doe",4)
personGateway.Delete(2)

行データゲートウェイ

P of EAA: Row Data Gateway

  • クエリ結果の行ひとつひとつがオブジェクトのインスタンスになる。ドメインオブジェクトがp.update()とか振る舞うのでオブジェクト指向感がある。
  • でもインメモリのオブジェクトにデータベースのアクセスコードが仕込まれる必要があるので、データベースとオブジェクトが密に結合してテストがやりにくかったりするデメリットがある。
  • トランザクションスクリプトに最適。
  • Findの動作をどこに定義するのかが問題になりがち
  • 行データゲートウェイに含まれるのはデータベースアクセスロジックだけ。ドメインロジックが含まれたらアクティブレコードパターンに進化する。

//static Findメソッドは別クラス(e.g. PersonFinder)に実装してもよい

class PersonGateway{
  lastname
  firstname
  numberOfDependents

  static Find(id): PersonGateway
  static FindWithLastName(String): PersonGateway[]

  insert()
  update()
  delete()
}
...
p1 = new PersonGateway()
p1.firstname = "John"
p1.lastname = "Doe"
p1.insert()

p = PersonGateway.Find(2)
p.numberOfDependents = 10
p.update()
p.delete()

アクティブレコード

P of EAA: Active Record

  • 行データゲートウェイのクラスにドメインロジックを追加したようなもの。
  • 行データゲートウェイテーブルデータゲートウェイドメインモデルとあまり相性が良くない。ドメインモデルであればアクティブレコードが向いてる(複雑なものでなければ)。
  • アクティブレコードクラスは以下のメソッドを持つ
    • SQLの結果行からアクティブレコードインスタンスを構築
    • 新しいインスタンスを構築
    • 静的なfindメソッドを使用してアクティブレコードオブジェクトを返却
    • データベースにアクティブレコードデータを挿入
    • フィールド取得、設定
    • ビジネスロジックの一部(バリデーションとか)
  • フィールドの型はテーブル内の各列と一致しなければならない。get/set時に他の効率のよい型への変換が認められる。
  • findメソッドは多くの場合静的に実装されるが、別に独立したクラスに分離してもよい。(テストもそのほうがしやすい)

class PersonGateway{
  lastname
  firstname
  numberOfDependents

  static find(id): PersonGateway
  static findWithLastName(String): PersonGateway[]

  insert()
  update()
  delete()

  getExemption()
  isFlaggedForAudit()
  getTaxableEarnings()  
}

データマッパー

P of EAA: Data Mapper

オブジェクトとデータベーステーブルは所詮別物(継承、コレクション、Joinの有無など)なので、1:1の対応でハンドリングするには限界がある。両者を変換するマッパがあれば複雑なドメインロジックと複雑なデータベーステーブルをハンドル出来るよ、的なパターン。

  • データマッパードメインオブジェクトとデータベーステーブルの間のデータの受け渡し(ロード/ストア)を責務とするソフトウェアレイヤ。ドメインモデルとデータベースを完全に分離し独立を保つ。ドメインオブジェクトはデータベースについて一切関知しなくてよくなる。
  • 全てのDB通信とマッピングデータマッパーが担うため、DBスキーマドメインオブジェクトそれぞれ独立して設計・開発が進められるメリット。
  • データマッパーがゲートウェイをラップすることはあり得る
  • 本書で紹介される「オブジェクトリレーショナルマッピングパターン」は、全てデータマッパーでオブジェクトとテーブルを接合させるためのストラテジ

class PersonMapper{
  insert(Person)
  update(Person)
  delete(Person)
}
class Person{
  lastName
  firstName
  numberOfDependents

  getExemption()
  isFlaggedForAudit()
  getTaxableEarnings()
}

データソースレイヤーアーキテクチャパターンの選び方

この本によると

  • ドメインモデルを使わない場合→ゲートウェイ系パターンでもなんとかなる
  • シンプルなドメインモデルを使う場合→アクティブレコードがよい
  • 複雑なドメインロジックを使う場合→データマッパーじゃないときつい

関連する基本パターン ゲートウェイマッパーの違い

ゲートウェイとマッパーはともに、ドメインオブジェクトと外部サービス(データベースなど)のインターフェイスとなる似たパターンだが、依存の方向性が逆方向になる。

ゲートウェイ

  • 外部サービスをラッピングして他のドメインオブジェクトが簡単に使えるようにする。
  • テストのためのサービススタブや、将来的に他のサービスに切り替えるためのポイントにもなる。間接化によって柔軟性を提供する。
  • PofEAA's Wiki - Gateway

マッパー

  • サブシステム間の通信を取り持つ分離レイヤー。サブシステム間の相互作用をハンドリングする。
  • PofEAA's Wiki - Mapper
    • 図のように、 マッパーが全ての関連サブシステムに依存している。サブシステムからマッパーへの依存はない。

その他のパターン

(今回は調べてない)

  • オブジェクトリレーショナル振る舞いパターン
  • オブジェクトリレーショナル構造パターン
  • オブジェクトリレーショナルメタデータマッピングパターン
  • Webプレゼンテーションパターン
  • 分散パターン
  • オフライン平行性パターン
  • セッションステートパターン
  • 基本パターン

エンタープライズ アプリケーションアーキテクチャパターン (Object Oriented Selection)

エンタープライズ アプリケーションアーキテクチャパターン (Object Oriented Selection)

Patterns of Enterprise Application Architecture

Patterns of Enterprise Application Architecture

Golangでの文字列・数値変換

覚えられなくて使うたびにググってしまうので、以後楽をするためにスニペットを記す。

パッケージ

strconvパッケージを使う。

文字列 → 数値変換(パース)

func Atoi(s string) (i int, err error)

文字列を10進数のint型にパースする。ParseInt(s, 10, 0)の省略形。

  var i int
  i, _ = strconv.Atoi("255")
  fmt.Println(i)  // => 255
func ParseBool(str string) (value bool, err error)

文字列をbool型にパースする。

  var b bool
  b, _ = strconv.ParseBool("true")
  fmt.Println(b) // => true

受け付ける値は1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False

func ParseInt(s string, base int, bitSize int) (i int64, err error)

文字列を任意の基数(2進数〜36進数)・任意のビット長(8〜64bit)のIntにパースする。

  var i32, i64, ib16, ib0 int64
  i32, _ = strconv.ParseInt("255", 10, 32)
  i64, _ = strconv.ParseInt("255", 10, 64)
  ib16, _ = strconv.ParseInt("ff", 16, 16)
  ib0, _ = strconv.ParseInt("0xff", 0, 16)
  fmt.Println(i32, i64, ib16, ib0)   // => 255 255 255 255

baseは変換に用いる基数(2〜36)。0の場合、s文字列の書式から判断する(0x接頭詞がついていたら16進数など)。bitSizeは0,8,16,32,64(それぞれint, int8, int16, int32, and int64に該当)。どのbitSizeでも戻り値の型自体はint64だが、それぞれの型に値を変えずに変換できる。

func ParseUint(s string, base int, bitSize int) (n uint64, err error)

文字列を任意の基数(2進数〜36進数)・任意のビット長(8〜64bit)のUintにパースする。ParseIntと同様。

  var ui uint64
  ui, _ = strconv.ParseUint("255", 10, 32)
  fmt.Println(ui) // => 255
func ParseFloat(s string, bitSize int) (f float64, err error)

文字列を任意のビット長(32,64bit)のUintにパースする。

bitSizeは32か64(float32とfloat64に相当)。戻り値の型自体はfloat64だが、それぞれの型に値を変えずに変換できる

  var f32, f64 float64
  f32, _ = strconv.ParseFloat("3.14159265359", 32)
  f64, _ = strconv.ParseFloat("3.14159265359", 64)
  fmt.Println(f32, f64) // => 3.1415927410125732 3.14159265359
ParseXXX関数のエラーハンドリング

ParseXXX関数のerrorの具象型はNumErrorで、(NumError).Errで範囲エラーか書式エラーかを判別できる。

  _, e = strconv.ParseInt("Bad number", 10, 32)
  if e != nil {
    if enum, ok := e.(*strconv.NumError); ok {
      switch enum.Err {
      case strconv.ErrRange:
        log.Fatal("Bad Range Error")
      case strconv.ErrSyntax:
        log.Fatal("Syntax Error")
      }
    }
  }

数値 → 文字列(フォーマット)

func Itoa(i int) string

func FormatBool(b bool) string

func FormatInt(i int64, base int) string

func FormatUint(i uint64, base int) string

各数値型を文字列にフォーマットする。

  fmt.Println(strconv.Itoa(255)) // => 255
  fmt.Println(strconv.FormatBool(true)) // => true
  fmt.Println(strconv.FormatInt(255, 10)) // => 255
func FormatFloat(f float64, fmt byte, prec, bitSize int) string

float64型を文字列にフォーマットする。

  fmt.Println(strconv.FormatFloat(1234.56789, 'b', 4, 64)) // => 5429687001335527p-42
  fmt.Println(strconv.FormatFloat(1234.56789, 'e', 4, 64)) // => 1.2346e+03
  fmt.Println(strconv.FormatFloat(1234.56789, 'E', 4, 64)) // => 1.2346E+03
  fmt.Println(strconv.FormatFloat(1234.56789, 'f', 4, 64)) // => 1234.5679
  fmt.Println(strconv.FormatFloat(1234.56789, 'g', 4, 64)) // => 1235
  fmt.Println(strconv.FormatFloat(1234.56789, 'G', 4, 64)) // => 1235
  fmt.Println(strconv.FormatFloat(1234.56789, 'G', 3, 64)) // => 1.23E+03

fmtは出力書式で'b','e','E','f','g','G'のいずれか。

precは桁数。eEFのとき小数点以下桁数。gGのとき全体の桁数。(g,Gはprecがfの少数点以上の桁数を満たしていればfまたはFになるらしい)

おまけ

たしかに。strconv.Atoiとfmt.Sprintだけ覚えておけば日常生活では支障無いですね。

デモ

Go Playground

Go言語のコマンドライン引数の使い方(サブコマンド等)

go言語でのコマンドライン引数の使い方でやや混乱したのでまとめる。

  • まとめること
    • flagの仕様
    • flagでgit push -fのようにオプション引数より前に非オプションの引数を置く方法・サブコマンドの例
    • go-flagパッケージのサブコマンドを試す

flagの仕様

Golang標準のflagパッケージは以下のように使う。

package main

import (
  "flag"
  "fmt"
  "os"
)

func main() {
  // -hオプション用文言
  flag.Usage = func() {
    fmt.Fprintf(os.Stderr, `
Usage of %s:
   %s [OPTIONS] ARGS...
Options\n`, os.Args[0],os.Args[0])
    flag.PrintDefaults()
  }

  var (
    opt1 = flag.String("opt1", "default-value", "First string option")
    opt2 = flag.String("opt2", "default-value", "Second string option")
  )
  flag.Parse()
  fmt.Println("opt1:", *opt1)
  fmt.Println("opt2:", *opt2)
  fmt.Println("args:", flag.Args())
}
$ ./argtest -h
Usage of ./argtest:
   ./argtest [OPTIONS] ARGS...
Options
  -opt1="default-value": First string option
  -opt2="default-value": Second string option

$ ./argtest -opt1 aaa -opt2 bbb AAA BBB 
opt1: aaa
opt2: bbb
args: [AAA BBB]

flag.Parse()関数は、"-"で始まらない(非フラグな)コマンドライン引数に到達すると、未解釈の引数をflag.Args()に格納し、パースをストップしてしまう。

なので、例:./argtest -opt1 aaa -opt2 bbb FILE1 FILE2...のようにオプション群を非オプション引数より前に置くのなら簡単にパースできる

flagでコマンドラインオプションをコマンドライン引数の後ろに置きたい

では、例:./argtest FILE1 -opt1 aaa -opt2 bbbという風にオプションを引数の後ろには書けないのだろうか。たとえばgit push -fみたいなサブコマンドをやりたいときに必要になる。

そういうときは、FlagSet型を使う。

これは複数のFlag設定を切り替えられるようにする仕組みだけど、FlagSetのParse()メソッドはパースする範囲をスライスとして渡せるので、これを利用して先頭に非フラグの引数を置いたり、サブコマンドを実現したりできる。

  fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
  var (
    opt1 = fs.String("opt1", "default-value", "First string option")
    opt2 = fs.String("opt2", "default-value", "Second string option")
  ) 

  arg1 := os.Args[1]
  fs.Parse(os.Args[2:])
  fmt.Println("arg1:", arg1)
  fmt.Println("opt1:", *opt1)
  fmt.Println("opt2:", *opt2)
  fmt.Println("args:", fs.Args())
$ ./argtest XXX -opt1 AAA -opt2 BBB YYY ZZZ
arg1: XXX
opt1: AAA
opt2: BBB
args: [YYY ZZZ]

サブコマンドの実装についてはVegetaが参考になった。各サブコマンド内で別々のフラグを設定している。See vegeta.

一応、こんなかんじになる。

subcommand.go

go-flagsを試してみる

調査の一環で非標準パッケージのgo-flagsを試してみたので感想

  • フラグ・非フラグの順序関係なくすべてパースできる。便利だけど、逆に位置を固定したいときにどうすればいいのか分からなかった。
  • サブコマンドをサポートしているようなので試してみた(下記)けど、コマンドがgo-flagsに依存してしまうしちょっと大げさだった
  • 使わなくていいかも。
package main

import (
  "fmt"
  "github.com/jessevdk/go-flags"
  "log"
  "os"
)

type Options struct {
  Option1 string     `short:"1" long:"opt1" description:"First string option"`
  Option2 string     `short:"2" long:"opt2" description:"Second string option"`
  Put     PutCommand `description:"Command to put something" command:"put" subcommands-optional:"true"`
  Get     GetCommand `description:"Command to put something" command:"get" subcommands-optional:"true"`
}

var opts Options
var parser = flags.NewParser(&opts, flags.Default)

type PutCommand struct {
  PutOption string `short:"p" long:"put-opt" description:"First string option"`
}
type GetCommand struct {
  GetOption string `short:"g" long:"get-opt" description:"First string option"`
}

func (x *PutCommand) Execute(args []string) error {
  fmt.Printf("Putting someting (opt=%v): %#v\n", x.PutOption, args)
  return nil 
}
func (x *GetCommand) Execute(args []string) error {
  fmt.Printf("Getting someting (opt=%v): %#v\n", x.GetOption, args)
  return nil 
}

func main() {
  if _, err := parser.Parse(); err != nil {
    os.Exit(1)
 }
}
$ ./command --opt1=AAA put XXX YYY ZZZ --put-opt=OPT
Putting someting (opt=OPT): []string{"XXX", "YYY", "ZZZ"}

$ ./command --opt1=AAA get XXX YYY ZZZ --get-opt=OPT 
Getting someting (opt=OPT): []string{"XXX", "YYY", "ZZZ"}

$ ./command --opt1=AAA
Please specify one command of: get or put

./command VVV --opt1=AAA get XXX YYY ZZZ --get-opt=OPT
Getting someting (opt=OPT): []string{"VVV", "XXX", "YYY", "ZZZ"} // <- argsにgetより前の引数が含まれている

参考