> Source: https://www.ymotongpoo.com/works/goblog-ja/appengine-gopath/


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

[The App Engine SDK and workspaces (GOPATH)](https://go.dev/blog/appengine-gopath) by Andrew Gerrand

## はじめに

Go 1をリリースしたとき、私たちは[goツール](https://go.dev/cmd/go/)と、それに伴うワークスペースという概念を導入しました。
ワークスペース(GOPATH環境変数で指定されます)は、Goパッケージの取得、ビルド、インストールを簡単にするためのコード構成の慣習です。
ワークスペースに馴染みがない方は、続きを読む前に[この記事](https://go.dev/doc/code.html)を読むか、
[このスクリーンキャスト](http://www.youtube.com/watch?v=XCsL89YtqCs)を見ることをおすすめします。

つい最近まで、App Engine SDKに含まれるツールはワークスペースを認識していませんでした。
ワークスペースがなければ[`go get`](https://go.dev/cmd/go/#hdr-Download_and_install_packages_and_dependencies)
コマンドは機能しないため、アプリの開発者は依存関係を手動でインストールし、更新しなければなりませんでした。これは煩わしい作業でした。

この状況はApp Engine SDKのバージョン1.7.4によって一変しました。
[dev_appserver](https://developers.google.com/appengine/docs/go/tools/devserver)と
[appcfg](https://developers.google.com/appengine/docs/go/tools/uploadinganapp)の両ツールが、
ワークスペースを認識するようになったのです。
アプリをローカルで実行するとき、あるいはアップロードするとき、これらのツールはGOPATH環境変数で指定された
ワークスペース内から依存関係を検索するようになりました。
つまり、App Engineアプリを開発する際にも `go get` を使えるようになり、環境や作業の習慣を変えることなく、
通常のGoプログラムとApp Engineアプリを行き来できるようになったということです。

例えば、リモートサービスとの認証にOAuth 2.0を使うアプリを作りたいとしましょう。
Go向けの人気のOAuth 2.0ライブラリに[oauth2パッケージ](https://godoc.org/golang.org/x/oauth2)があり、
これは次のコマンドでワークスペースにインストールできます。

```shell-session
go get golang.org/x/oauth2
```

App Engineアプリを書くときには、通常のGoプログラムと同じようにoauthパッケージをインポートします。

```go
import "golang.org/x/oauth2"
```

これで、dev_appserverでアプリを実行する場合でも、appcfgでデプロイする場合でも、
ツールはワークスペース内のoauthパッケージを見つけてくれます。ただそれだけで動くのです。

## スタンドアロンとApp Engineのハイブリッドアプリ

Go向けのApp Engine SDKは、Webリクエストを処理するためにGo標準の[net/http](https://go.dev/pkg/net/http/)
パッケージを土台にしています。その結果、多くのGo製Webサーバーはわずかな変更だけでApp Engine上で動かせます。
例えば、[godoc](https://go.dev/cmd/godoc/)はGoの配布物にスタンドアロンのプログラムとして含まれていますが、
これはApp Engineアプリとしても実行できます(実際、godocはApp Engine上で[golang.org](https://go.dev/)を配信しています)。

しかし、スタンドアロンのWebサーバーとApp Engineアプリの両方を兼ねるプログラムを書けたら素敵だと思いませんか？
[ビルド制約](https://go.dev/pkg/go/build/#hdr-Build_Constraints)を使えば、それが可能です。

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

```go
// +build !windows
```

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

```go
// +build appengine
```

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

```go
// +build !appengine
```

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

[goprotobuf](http://code.google.com/p/goprotobuf/)ライブラリはこの仕組みを使って、
エンコードとデコードの仕組みの重要な部分について2つの実装を提供しています。
[pointer_unsafe.go](http://code.google.com/p/goprotobuf/source/browse/proto/pointer_unsafe.go)は
[unsafeパッケージ](https://go.dev/pkg/unsafe/)を使っているためApp Engineでは使えない高速な版で、
[pointer_reflect.go](http://code.google.com/p/goprotobuf/source/browse/proto/pointer_reflect.go)は
unsafeを避けて[reflectパッケージ](https://go.dev/pkg/reflect/)を代わりに使う低速な版です。

単純なGo製Webサーバーを例にとって、これをハイブリッドアプリに変えてみましょう。まずはこのmain.goです。

```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です。

```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」というビルド制約を指定しています。

```go
// +build !appengine

package main

import "net/http"

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

より複雑なハイブリッドアプリの例を見たい方は、[presentツール](https://godoc.org/golang.org/x/tools/present)をご覧ください。

## 結論

これらの変更により、外部依存のあるアプリの開発が容易になり、また、スタンドアロンプログラムと
App Engineアプリの両方を含むコードベースの保守がしやすくなることを願っています。

By Andrew Gerrand

