【Golang】Goaで「HELLO WORLD」する。

## まえがき
Goaの環境構築メモです。http://localhost:8008/hello/${name} で「HELLO ${name}」を返すAPIを実行できるところまでの構築手順となります。

下記にてソースコード公開してます。

https://github.com/daisuketakakuwa/inventory-system

参考

https://goa.design/learn/getting-started/

https://zenn.dev/ikawaha/books/goa-design-v3/viewer/getting-started#goa-%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB

## 手順1：Goのインストール

### Windowsユーザー
下記サイトよりインストールする。

https://go.dev/doc/install

### Macユーザー

```
brew install go
```

・`go version`でインストールされていることを確認
・今の最新バージョンは「1.17」
```zsh
% go version 
go version go1.17.5 darwin/amd64
```

## 手順2：モジュールフォルダ作成
・Goのワークスペースディレクトリ(GOPATH)/src配下で、モジュールフォルダを作成する。
・次記事で在庫関連のAPIとして実装していくので、「inventory-system」という名前でモジュールを作成する。
・プロジェクト配下で`go mod init <module名>`を実行し、`Go Modules`で外部パッケージをインストールできるようにする。
```
cd <Goのワークスペースディレクトリ>/src
mkdir inventory-system
cd inventory-system
go mod init inventory-system
 → 「go.mod」というファイルが生成される。Mavenのpom.xml的なもの(だと思っている)
 → 「go.mod」の1行目に「module inventory-system」というように、module名が記される。
```

## 手順3：Goaのインストール
### Goaのインストール

```
go get -u goa.design/goa/v3
go get -u goa.design/goa/v3/...
```
### gRPCのインストール
・Goaは「gRPC」という技術を利用してコードを自動生成する。
・このgRPCが必要とするモジュールをインストールする必要がある。

https://qiita.com/oohira/items/63b5ccb2bf1a913659d6

#### Protocol Buffersのインストール
・gRPCで利用されるインタフェース記述言語(IDL)

##### Windowsの場合
・[ダウンロードサイト](https://github.com/protocolbuffers/protobuf/releases/)より環境に応じたZIPファイルをダウンロード
・ZIPファイルを解凍し、binフォルダをPATHに通す
・`protoc --version`でインストールされていることを確認
##### Macの場合
```zsh
$ brew update     # formula を更新
$ brew upgrade    # 更新があるパッケージを再ビルドする
$ brew install protobuf # protobufをインストール
$ brew upgrade protobuf # protobufをアップグレード
$ protoc --version
```

https://qiita.com/kitauji/items/fdbd052c19dad28ab067

#### Go向けのProtocプラグインをインストール
```
go install google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
```

https://qiita.com/kitauji/items/bab05cc8215abe8a6431


## 手順4：Designファイル作成
・Goaでコード生成する際の設計図となるのがこのDesignファイルである。
・**Goa DSL**を利用してAPIのINPUT/OUTPUTを定義する。

>The Goa DSL lets you describe the service methods as well as their input and output data structures.

https://pkg.go.dev/goa.design/goa/dsl

go-workspace/src/inventory-system/design/design.go
```go 
package design

import (
	. "goa.design/goa/v3/dsl"
)

// APIサーバ定義
var _ = API("inventory-system", func() {
    // API の説明（タイトルと説明）
    Title("Inventory System Service")
    Description("Service for maintaining inventory")

    // ホスト情報
    Server("inventory-system", func() {
        Host("localhost", func() {
            URI("http://localhost:8008") // HTTP REST API
            URI("grpc://localhost:8088") // gRPC
        })
    })
})

// サービス定義
var _ = Service("inventory", func() {
    Description("inventory service hello")
    // Helloメソッド
    Method("hello", func() {
        // 引数として「message」を受け取る
        Payload(func() {
            Field(1, "message", String)
            Required("message")
        })
        // "Hello ${message}" を返すAPIにする
        Result(String)

        HTTP(func() {
            GET("/hello/{message}")
        })

        GRPC(func() {
            Response(CodeOK)
        })
    })
})
```

## 手順5：コード生成
### goa gen &lt;module_directory_name>/~/&lt;designファイルがあるディレクトリ>
**・designが変わるたびに`goa gen`する必要あり。**
・generate the **boilerplate code**(編集されることのないコード)
```
cd go-workspace/src/inventory-system
goa gen inventory-system/design
```
・新たにgenフォルダが生成される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/37191125-c251-5525-f3b1-4cf61366e131.png)

### goa example &lt;module_directory_name>/~/&lt;designファイルがあるディレクトリ>
**・1度だけ実行すればOK。designが変わっても`goa example`しなおす必要はない。**
・generate **a basic implementation** of the service
```
goa example inventory-system/design
```
・cmdフォルダとinventory.goが生成される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/29411871-6fe4-ff7d-277a-66a2aadabdcc.png)

#### cmdフォルダ配下のmain.go = アプリ起動時に実行されるもの
・DB接続処理のような「アプリ起動時に1度だけ行いたい処理」は、main.go内に定義する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/3d6bc9fa-efce-76cb-835c-6868d32d3414.png)



#### "Hello ${message}" が返ってくるようにする
・inventory.goにHello関数が定義されているので、引数のmessageを参照して"Hello ${message}"がレスポンスとして返されるようにする。
```go
// Hello implements hello.
func (s *inventorysrvc) Hello(ctx context.Context, p *inventory.HelloPayload) (res string, err error) {
	s.logger.Print("inventory.hello")
	res = "HELLO " + p.Message // ここを追加
	return
}
```

### go build
・実行可能ファイルを生成する。
```zsh
# generate server code
go build ./cmd/inventory_system
# generate client code
go build ./cmd/inventory_system-cli
```
・今回はserver codeのみ生成する。
```
go build ./cmd/inventory_system
```
・inventory_system が生成される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653955/c6fa90b5-917e-0296-e93f-b9691a82fb1c.png)
## 手順6：アプリ実行
・実行すると次のようなログが出力される。
```zsh
% ./inventory_system
[inventorysystem] 17:31:04 HTTP "Hello" mounted on GET /hello/{message}
[inventorysystem] 17:31:04 serving gRPC method inventory.Inventory/Hello
[inventorysystem] 17:31:04 HTTP server listening on "localhost:8008"
[inventorysystem] 17:31:04 gRPC server listening on "localhost:8088"
```
・"HELLO WORLD"と出力してみる。
```zsh
 % curl http://localhost:8008/hello/WORLD
"HELLO WORLD"
```

# Design関連メモ（後書きしていく）
## Field = Attribute + metaタグ(rpc:tag)
### "rpc:tag"とは？
・gRPCが利用するプロトコルバッファでのシリアライズでは、事前にフィールドのシリアライズ順序が必要。この順序をmetaタグ(rpc:tag)で各フィールドに指定してあげる必要がある。

#### Attributeを使った書き方
・Attribute内でMetaを使って"rpc:
```golang
Method("hello", func() {
    Payload(func() {
        Attribute("message", String, func() {
            Meta("rpc:tag", "1")
        })
    }
)
```
#### Fieldを使った書き方
・Fieldの第1引数に指定したパラメータが自動的にrpc:tagの値として設定される。
```golang
Method("hello", func() {
    Payload(func() {
        Field(1, "message", String)
            Required("message")
        })
    }
)
```

以下Fieldの説明
>Field is syntactic sugar to define an attribute that defines a tag, e.g. for protobuf. The result is the same as calling Attribute with the "rpc:tag" meta set with the value of the first argument.

https://pkg.go.dev/goa.design/goa/dsl#Field