PHPerに贈る、GoでWebサーバー開発環境構築

## はじめに

ひょんなことから、職場でGoのプロジェクトをはじめることになってしまいました。
APIサーバーだけなので、なんとかなると信じています。

小生、生粋のPHPer(Symfony2メイン)です。
PHPの知見をフル活用するべく、PHPライクな環境を構築します。

ソースは[Github](https://github.com/tarokamikaze/PHPerGo)にあげてます。

## Goの文法など

[Go言語の初心者が見ると幸せになれる場所　#golang](http://qiita.com/tenntenn/items/0e33a4959250d1a55045)が良記事です。熟読してください。
[はじめてのGo](http://gihyo.jp/dev/feature/01/go_4beginners)はわかりやすいですね。

## 各種選定とインストール

知識がほぼゼロなので、選定はカンとGoogle先生頼りです。

### Go自体のインストール

Macなので、Homebrewでカンタン。Windows? 知らない子ですね。

```
$ brew install go
```

### IDE

PHPStormの置換に慣れきっているので、[Go language plugin](https://github.com/go-lang-plugin-org/go-lang-idea-plugin)を使います。
[IntelliJ IDEAでGo言語を書く(golang plugin alpha)](http://yutaszk23.hatenadiary.jp/entry/2015/04/24/163541)に詳細なやりかたがあるので、この通りにインストール。

### ライブラリ管理

Composerのようなライブラリ管理の仕組みは、Go標準のものやベンダーツール等いろいろあります。
私はYamlスキーなので、[glide](https://github.com/Masterminds/glide)にしました。
これもHomebrewでラクラク。Win(ry

```
$ brew install glide
```

### Webフレームワーク

[echo](https://echo.labstack.com/)にしました。Google Trends によると、最近人気があるようなので。それ以上の理由はありません、残念ながら。 ![trends.png](https://qiita-image-store.s3.amazonaws.com/0/103900/c566b775-738f-f16b-d5fa-a7abeeec9bf5.png)

ざっと見、Laravelというか、Rumenチックですね。
マイクロではあるのでしょうが、工夫すればいろいろ機能を盛れそうです。

そして公式ドキュメントが英語だけです。

> Symfonyを使うくらいリテラシーのある人は、英語くらい読めるっしょwww
> by Symfonyユーザー会のすごいひと

アッハイ。

インストール方法等は後述します。

### 環境ファイル

echoには環境別設定管理の機能がない...だと...
DBのID/Passとかどうすんねん！ というわけで、用意します。

yamlを食べるようなライブラリを使うのも手ですが、Herokuで動かす予定なので
環境変数で指定できるほうが便利そうです。

開発環境では[direnv](https://github.com/direnv/direnv)を使います。
${projectRoot}/.envrc に書けば、よしなに環境変数にしてくれます。
Goのアプリは環境変数を食べることとします。

これもHomebrewで(ry

```
$ brew install direnv
```

### DIコンテナ

echoにはDIコンテナがない...だと...

ダメです。そんな人生は耐えられません。
というわけでGoogle先生にきいたところ、[goldi](https://github.com/fgrosse/goldi)がヒット。

> If you are used to frameworks like Symfony you might want to define your types in an easy to maintain yaml file.You can do this using goldigen.
> Symfonyとか使ってたんなら、yamlでサービスとか管理したいっしょ？ goldigen っていうやつ使えばできるから。(乱暴訳)

**はい先生！　それ私のことです！**
というわけで、一も二もなく採用。

### ORM etc...

現在のところDBは使う予定がないので割愛。
[GORM](http://jinzhu.me/gorm/)とかいいんじゃないですかね、名前がシンプルで。

## 環境変数まわり

```
$ vi ~/.bash_profile
------
export GOPATH=$HOME/_go       #仮のGOPATHが必要らしい
export PATH=$PATH:$GOPATH/bin #PATHを通す
export EDITOR=/usr/bin/vi     #direnv editコマンドから呼ばれるエディタ
eval "$(direnv hook bash)"    #direnvを有効にするおまじない
------

# 設定を読み込む
$ source ~/.bash_profile 
```

## プロジェクトフォルダを作る

```
$ mkdir ${projectRoot}
$ cd ${projectRoot}
$ vi .envrc
------
export GOPATH=`pwd`    # GOPATHは"ここ"！
export TEST_PARAM=test # パラメータを渡せるかテスト用
------

# DIの設定をパースしてくれるくんのインストール
$ go get github.com/fgrosse/goldi/goldigen

# DIの設定ファイル置き場
$ mkdir -p src/app/config

# DIの設定パース後ファイル置き場
$ mkdir src/app/lib

$ cd src/app

# echo のインストール
$ glide get github.com/labstack/echo#~3.0 

# goldi のインストール
$ glide get github.com/fgrosse/goldi#~1.0
```

## IntelliJ IDEA(PHPStorm)側設定

[IntelliJ IDEAでGo言語を書く(golang plugin alpha)](http://yutaszk23.hatenadiary.jp/entry/2015/04/24/163541)に従ってプロジェクトを作れば問題ありません。

ただ、下記2点は注意。

### 1. vendor系の補完がきかないよ問題

Preferences > Directories で、Excluded Folderに``${projectRoot}/src/app/vendor``を追加すると、補完がきかないみたいです。
Excluded Folderから外して、Preferences > Languages & Frameworks > Go Libraries で、Project libraries に ``${projectRoot}/src/app/vendor``を追加しましょう。

### 2. IntelliJ IDEAはdirenvを無視する？ 問題

GOPATHが${projectRoot}になりませんでしたので、設定。
Preferences > Languages & Frameworks > Go Libraries で、Global libraries に``${projectRoot}``を追加。

## サンプルソース

やっとGoを書き始められる環境が整いました！
DIまわりの設定が不安なので、サンプルを作って実験します。

### 注入するサービス
```go:src/app/service/injected.go
package service

type Injected struct{}

func (Injected) Call(arg string) string {
	return "injected!"
}
```

### 注入されるサービス
```go:src/app/service/service.go
package service

type Service interface {
	Call(arg string) string
}

type ServiceImpl struct {
	Injected *Injected // ここに注入される予定
}

func (s *ServiceImpl) Call(arg string) string {
	return "mainServie " + arg + " " + s.Injected.Call("dummy")
}
```

### 設定ファイル

```yaml:src/app/config/types.yml 
types:
    injected:                 # サービス名
        package: app/service  # ソースのpackage節
        type: Injected        # 構造体(Class的なやつのGo版)の名前
    service:
        package: app/service
        type: ServiceImpl
        arguments:            # Symfony2と同じ記法(！)で引数を定義
            - "@injected"     # 引数に渡すサービス名。%parameter_name%で値も渡せるらしい
```

### 設定ファイルをGoソースに変換

```
$ cd ${projectRoot}/src/app
$ ../../bin/goldigen --in config/types.yml --out lib/dependency_injection.go
```

で、``${projectRoot}/src/app/lib/dependency_injection.go``にファイルができます。このコマンドは設定ファイルを更新するたびに実行する必要があります。

### bootstrapファイル

コンテナを作る部分を外出しします。

```go:src/app/lib/bootstrap.go
// Bootstrap file.
// Ready container.

package lib

import (
	"github.com/fgrosse/goldi"
	"github.com/fgrosse/goldi/validation"
	"os"
	"strings"
	"regexp"
)

func Bootstrap(envPrefix string) *goldi.Container {
	registry := goldi.NewTypeRegistry()
	RegisterTypes(registry)

	config := generateConfig(envPrefix)

	container := goldi.NewContainer(registry, config)
	validator := validation.NewContainerValidator()
	validator.MustValidate(container)

	return container
}

func generateConfig(envPrefix string) map[string]interface{} {
	reg, _ := regexp.Compile("^" + envPrefix + "(.+)$")
	config := map[string]interface{}{}

	for _, e := range os.Environ() {
		pair := strings.Split(e, "=")

		if len(pair) == 2 && reg.MatchString(pair[0]) {
			// Removing prefix from the key.
			key := reg.ReplaceAllString(pair[0], "$1")
			config[key] = pair[1]
		}
	}

	return config
}
```

### 実行ファイル

```go:src/app/server.go
package main

import (
	"net/http"

	"github.com/labstack/echo"
	"app/lib"
	"app/service"
)

// 食べさせる環境変数の接頭語
var envPrefix string = "TEST_"
func main() {
	container := lib.Bootstrap(envPrefix)

	e := echo.New()
	e.GET("/", func(c echo.Context) error {
		// サービスをとってきて、メソッドを叩いてみる
		service := container.MustGet("service").(service.Service)
		str := container.Config["PARAM"].(string) + "!"
		return c.String(http.StatusOK, service.Call(str))
	})
	e.Logger.Fatal(e.Start(":1323"))
}
```

その後、下記でサーバーを立ち上げ。

```
$ cd ${projectRoot}/src/app
$ go run server.go
```

で、 http://localhost::1323 にアクセス。

![test.png](https://qiita-image-store.s3.amazonaws.com/0/103900/21063c1a-15ef-8013-b263-c4e38c1d1b55.png)

と表示されれば成功です。

繰り返しですが、ソースは[Github](https://github.com/tarokamikaze/PHPerGo)にあげてます。

## ToDo

* ~~　環境変数をまとめてDIコンテナにつめるのを作る。~~ Done!
* さっさとAPIをつくる。
* 社内で布教する。