5
4

More than 1 year has passed since last update.

golangはじめました

Posted at

概要

素人としてこの1ヶ月間Golangでアプリケーション作ることになりましたので、所感や実際調べたことについて共有し、少しでも参考になれれば幸いです。

とりあえず現時点でのディレクトリ構成を貼ります

左がrootディレクトリで右がsrcディレクトリ配下です。
srcというディレクトリを切るかどうかで結構悩んでいましたが、とりあえず切ることにしました。

IDE、開発環境、実行環境

IDEは下記の理由でVSCodeにしました。
- VSCodeにはGolang開発チームが提供している(元々はMicrosoftがメンテしてたが、Golang開発チームに移管された)pluginがあるので、実質VSCodeがstandardらしい
- JetBrainsのGoLandというのがあるが、有料なので、今回はスキップ(30日間無料トライアルはある)
- Intellij Ultimate使う場合はaws toolkitもあり

Docker化
Golangアプリケーションは最終的にはLinuxのELF形式のバイナリ1個にまとまりますので、気軽にdocker化できます。
Golangアプリケーションを動かすためのdocker imageを探した結果、googleがメンテしているDistrolessイメージ群を発見し、良さそうでしたので、今回使うことにしました。参考サイト
そして、今回はDockerのmulti stage buildを使って一つのDockfileにてbuildとアプリケーションの実行まで書いてシンプルな構成にしました。

multi-stage-build例
FROM golang:1.17.3-bullseye as builder ## -- buildするイメージ

COPY src/ /go/src/
WORKDIR /go/src

RUN GOOS=linux GOARCH=amd64 go build -o hoge main.go

FROM gcr.io/distroless/base:latest ## -- アプリケーション実行するイメージ & 最終的に出来上がるイメージ

COPY --from=builder /go/src/hoge /

ENTRYPOINT [ "/hoge" ]

パッケージ構成、アーキテクチャについて

私自身、長らくJavaでAPI開発とかやってきた者としては、SpringフレームワークをベースにしたMVCパターンに馴染みがありますが、GolangにはSpringみたいな強力なフレームワークが存在しませんので、プログラミングの原点に戻ってクリーンアーキテクチャを検討することにしました。

ディレクトリ構成画像からわかるように完全にクリーンアーキテクチャに出てくるレイヤーを元にディレクトリを切っています。クリーンアーキテクチャも今回初めてですし、うまく説明する自信がないですので、下記の記事を参考いただければと思います。
https://qiita.com/nrslib/items/a5f902c4defc83bd46b8

ディレクトリ構成や、アーキテクチャについて色々先駆者達が試行錯誤しているような印象があって、これが正解だというのがないですので、チーム内で話し合って今の構成に決めました。(クリーンアーキテクチャのレイヤーで切ってたけど、後で直したよっていう経験談もあったが将来的に壁にぶつかったらまた考えることに)

WebフレームワークはGin

アーキテクチャの次にフレームワークの選定です。
githubのスター数だけではすべての指標ではないですが、Ginが一番スター数があるのは事実、そして活発的に開発されるし、ググったらたくさん参考できる日本語サイトも出てきますので、良いのではないかというところでGinにしました。
他にGinの特徴はこんなところかなと思います。

  • 速い
  • 軽量

JetBrainsさんがアンケートベースで出したレポートでも1位取ってますね こちら
フレームワーク比較もご参考にどうぞ

コンパイル

GolangがサポートしているOS/CPUアーキテクチャや下記のコマンド確認できるので、環境ごとにコンパイルすることもできます。

サポートOS/CPU
go tool dist list
...(一部省略)
linux/386
linux/amd64
linux/arm
linux/arm64
...(一部省略)

今回本番環境はAWSのECS Fargateを想定していて、つい先日arm64もサポートするリリースがありましたので、linux/amd64とlinux/arm64のコンテナを用意することにしました。(実際どちらを使うかはまだ決めてないですが、arm64の方が省エネで料金節約になるらしいです。)

本番用と開発用のコンパイルオプションについて
-race : thread-safeをチェックするみたいなもの(実際つけるとアプリケーションが遅くなるみたいな記事もあったので、チェックは開発環境だけにして、本番用にコンパイルする時はつけないようにしています)
-w -s : このオプションをつけないとDebug用の情報も入るので、本番はつけてないです。

コンパイル例
// 開発用
go build -race -o hoge main.go

// 本番用
go build -o hoge main.go -ldflags "-w -s"

クロスコンパイルときの注意点
クロスコンパイルでは-raceは使えないです。詳細はこちら
例:amd64のサーバーでarm64用のコンパイルをする時

Makefileにについて
Golangはコンパイラ言語としてMakefileの利用が主流になっています。
buildコマンドや環境構築コマンドをMakefileにまとめておくと結構便利です。

例えば本番用と開発用のコンパイルオプションについてで紹介した開発環境と本番環境のコンパイル差別化もMakefileに下記のようにまとめておくとmakeコマンド一つで実行できるようになります。

Makefile例
# disable symbol table and dwarf
GO_LDFLAGS_SYMBOL:=
ifeq ($(HOGE_ENV),prod)
    GO_LDFLAGS_SYMBOL:=-w -s
endif

GO_LDFLAGS:=$(GO_LDFLAGS_SYMBOL)

# race detector
GO_BUILD_RACE:=-race
ifeq ($(HOGE_ENV),prod)
    GO_BUILD_RACE:=
endif

# go build
GO_BUILD:=$(GO_BUILD_RACE) -ldflags "$(GO_LDFLAGS)"

.PHONY: build
build:
    GOOS=linux GOARCH=amd64 go build -o hoge $(GO_BUILD) main.go
makeコマンド例
# 開発環境用build
HOGE_ENV=dev make build

# 本番環境用build
HOGE_ENV=prod make build

使用ライブラリ

viper

JSON, YAML, TOMLなどをサポートする設定ファイル用ライブラリ
参考できる資料はたくさんありますので、こちらでは割愛しますが、どんなふうに使っているかだけを簡単に紹介します。go:embedと組み合わせて使ってます。
go:embedは静的ファイルをコンパイル済みELF形式バイナリファイルに含める機能を提供しています

configディレクトリ
├── config.go
└── yaml
    ├── config.prod.yml
    └── config.dev.yml
config.xxx.yml例
database:
  postgres:
    name: hoge
    host: localhost
    port: 5432
    user: user
    pass: password

上記のようなディレクトリ構造で環境ごとの設定(DB設定など)を保持しているとするとconfig.goの中身はこうなります。

config.go
type Config struct {
    Database struct {
        Postgres struct {
            Name string
            Host string
            Port string
            User string
            Pass string
        }
    }
}

var (
    //go:embed yaml/*
    staticYamlDir embed.FS
)

func LoadConfig() *Config {
    setDefaultEnv()
    hogeEnv := viper.GetString("env")
    fileName := "yaml/config." + hogeEnv + ".yml"
    viper.SetConfigName("config." + hogeEnv)
    viper.SetConfigType("yaml")
    viper.AddConfigPath("config/yaml")

    b, err := staticYamlDir.ReadFile(fileName)

    if err != nil {
        panic("Failed to read config.")
    }

    if err := viper.ReadConfig(bytes.NewReader(b)); err != nil {
        panic("Failed to load config.")
    }

    var c Config

    if err := viper.Unmarshal(&c); err != nil {
        panic("Failed to unmarshal config.")
    }

    return &c
}

func setDefaultEnv() {
    viper.SetEnvPrefix("hoge")
    viper.BindEnv("env")
    viper.AutomaticEnv()
    viper.SetDefault("env", "dev")
}

これでアプリケーション起動時にHOGE_ENVという環境変数を設定することで読み込むconfigファイルがそれぞれ違うことになるので、環境別の設定をファイル分けて管理することができます。

sqlboiler

ORMライブラリです。使い方は割愛します。
有名なGORMとかありますが、今回sqlboilerを採用したのは下記の観点からです。

  • schemaからコード生成がやりやすい
  • 静的に型付けされており、実行時にリフレクションを使う必要がないため高速
  • migration機能はないがflywayとかでカバーできる
  • 生queryがかける
  • relationもサポート

air

Golangはコンパイラ言語ですので、開発時に修正、ビルド、実行のサイクルを頻繁に行わないと行けないです。
airはそのサイクルを自動化してくれるいわゆるホットリロードライブラリとしてローカルでの開発効率を上げてくれます。

所感

Javaみたいにアノテーションつけておけばよしなにやってくれるみたな書き方ができないですが、とにかく素直にコードを書くところがGolangの特徴かなと思います。(JavaはそもそもSpringという強力な武器があるので、言語だけの比較にはならないですが)

良いところ:

  • 新卒でもコード見ればある程度理解できるようなわかりやすさ
  • 軽量、高速
  • クラウドやコンテナ技術、マイクロサービスなどと親和性が高い

面倒なところ: Golangには例外という概念がなく、常にif err != nilしないと行けないこと

Golangは何でもシンプルで行こうという設計思想から他の言語と差別化をし、たくさんの支持を得ていますので、使いにくいところもあるかもしれませんが、今後も仲良くなって行けそうな雰囲気でしたー。

最後に

新しい言語を勉強する時にネットにたくさんの情報が散らがっていて、実際そのとおりにやって見たらなんか古いやり方だったり、アンチパターンで作ったりするかもしれませので、一番先にオフィシャルドキュメントを一読することをおすすめします。特にGolangの場合はオフィシャルドキュメントが結構しっかりしていて、読みやすいので、ぜひご確認ください。

Golangの目玉機能: goroutine, channelについては興味ありますが、まだ触れてないので、今後このあたりも攻略したいと思います。

おわり。

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4