Go ブログ

App Engine SDK とワークスペース (GOPATH)

Andrew Gerrand
2013年1月9日

はじめに

Go 1 をリリースしたとき、go ツールと、それに伴いワークスペースの概念を導入しました。ワークスペース(GOPATH 環境変数で指定)は、Go パッケージの取得、ビルド、インストールを簡素化するコードを整理するための規約です。ワークスペースに馴染みがない場合は、こちらの記事を読むか、こちらのスクリーンキャストを見てから読み進めてください。

最近まで、App Engine SDK のツールはワークスペースを認識していませんでした。ワークスペースがないと、"go get" コマンドが機能せず、アプリの作成者はアプリの依存関係を手動でインストールおよび更新する必要がありました。それは面倒でした。

これはすべて、App Engine SDK のバージョン 1.7.4 で変わりました。dev_appserverappcfg ツールは、ワークスペースを認識するようになりました。ローカルで実行したり、アプリをアップロードしたりすると、これらのツールは GOPATH 環境変数で指定されたワークスペースで依存関係を検索するようになりました。これは、App Engine アプリをビルドする際に "go get" を使用でき、環境や習慣を変えることなく通常の Go プログラムと App Engine アプリを切り替えることができるようになったことを意味します。

たとえば、リモートサービスで認証するために OAuth 2.0 を使用するアプリをビルドするとします。Go の一般的な OAuth 2.0 ライブラリは、oauth2 パッケージです。このパッケージは、次のコマンドでワークスペースにインストールできます。

go get golang.org/x/oauth2

App Engine アプリを記述するときは、通常の Go プログラムと同様に oauth パッケージをインポートします。

import "golang.org/x/oauth2"

これで、dev_appserver でアプリを実行する場合でも、appcfg でアプリをデプロイする場合でも、ツールはワークスペース内の oauth パッケージを見つけることができます。問題なく動作します。

ハイブリッドスタンドアロン/App Engine アプリ

Go App Engine SDK は、Web リクエストを処理するために Go の標準 net/http パッケージをベースに構築されており、その結果、多くの Go Web サーバーはわずかな変更を加えるだけで App Engine 上で実行できます。たとえば、godoc はスタンドアロンプログラムとして Go ディストリビューションに含まれていますが、App Engine アプリとしても実行できます(godoc は golang.org を App Engine から提供しています)。

しかし、スタンドアロンの Web サーバーと App Engine アプリの両方であるプログラムを作成できれば素晴らしいと思いませんか?ビルド制約を使用することで、それが可能になります。

ビルド制約は、ファイルがパッケージに含めるかどうかを決定する行コメントです。これらは、さまざまなオペレーティングシステムやプロセッサアーキテクチャを処理するコードで最もよく使用されます。たとえば、path/filepath パッケージには、Windows システム(シンボリックリンクがない)でビルドされないようにするためのビルド制約を指定する symlink.go ファイルが含まれています。

// +build !windows

App Engine SDK は、新しいビルド制約用語「appengine」を導入します。

// +build appengine

を指定するファイルは、App Engine SDK によってビルドされ、go ツールによって無視されます。逆に、次の指定をするファイル

// +build !appengine

は、App Engine SDK によって無視されますが、go ツールはそれらを正常にビルドします。

goprotobuf ライブラリは、このメカニズムを使用して、エンコード/デコード機構の主要部分の 2 つの実装を提供します。pointer_unsafe.go は、unsafe パッケージを使用するため App Engine で使用できない高速バージョンですが、pointer_reflect.go は、代わりに reflect パッケージを使用することで unsafe を回避する低速バージョンです。

簡単な Go Web サーバーを作成し、それをハイブリッドアプリにしてみましょう。これは main.go です。

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe("localhost:8080", nil)
}

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello!")
}

これを go ツールでビルドすると、スタンドアロンの Web サーバー実行可能ファイルが得られます。

App Engine インフラストラクチャは、ListenAndServe に相当するものを実行する独自の main 関数を提供します。main.go を App Engine アプリに変換するには、ListenAndServe の呼び出しを削除し、(main の前に実行される) init 関数にハンドラーを登録します。これは app.go です。

package main

import (
    "fmt"
    "net/http"
)

func init() {
    http.HandleFunc("/", handler)
}

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello!")
}

これをハイブリッドアプリにするには、App Engine 固有の部分、スタンドアロンのバイナリ固有の部分、および両方のバージョンに共通の部分に分割する必要があります。この場合、App Engine 固有の部分はないため、2 つのファイルに分割するだけです。

app.go はハンドラー関数を指定して登録します。これは上記のコードリストと同じであり、プログラムのすべてのバージョンに含める必要があるため、ビルド制約は必要ありません。

main.go は Web サーバーを実行します。スタンドアロンバイナリをビルドする場合にのみ含める必要があるため、"!appengine" ビルド制約が含まれています。

// +build !appengine

package main

import "net/http"

func main() {
    http.ListenAndServe("localhost:8080", nil)
}

より複雑なハイブリッドアプリについては、present ツールをご覧ください。

結論

これらの変更により、外部依存関係を持つアプリでの作業が容易になり、スタンドアロンプログラムと App Engine アプリの両方を含むコードベースを維持しやすくなることを願っています。

次の記事: 並行処理は並列処理ではない
前の記事: 最近のGoに関する2つの講演
ブログインデックス