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

The App Engine SDK and workspaces (GOPATH) by Andrew Gerrand

はじめに

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はApp Engine上でgolang.orgを配信しています)。

しかし、スタンドアロンのWebサーバーとApp Engineアプリの両方を兼ねるプログラムを書けたら素敵だと思いませんか? ビルド制約を使えば、それが可能です。

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

// +build !windows

App Engine SDKは新しいビルド制約の項として「appengine」を導入しています。次のように指定されたファイルは、

// +build appengine

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

// +build !appengine

App Engine SDKからは無視され、goツールは何の問題もなくビルドします。

goprotobufライブラリはこの仕組みを使って、 エンコードとデコードの仕組みの重要な部分について2つの実装を提供しています。 pointer_unsafe.gounsafeパッケージを使っているためApp Engineでは使えない高速な版で、 pointer_reflect.goは unsafeを避けてreflectパッケージを代わりに使う低速な版です。

単純な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の呼び出しを取り除き、代わりにinit関数 (main関数より先に実行されます)内でハンドラを登録します。これが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アプリの両方を含むコードベースの保守がしやすくなることを願っています。

By Andrew Gerrand