2
0

More than 3 years have passed since last update.

Go1.16新機能embedを使ってメッセージが定義されたファイルを読み込む

Last updated at Posted at 2021-03-15

はじめに

Go1.16リリース

Go1.16が2021年2月にリリースされましたね!

様々なアップデートがありましたが、主要でわかりやすい変更点は以下の通りでしょうか。

  • M1 Macに対応
  • 処理速度やメモリ使用量の改善
  • go:embedの追加

Go 1.16 adds support of 64-bit ARM architecture on macOS (also known as Apple Silicon) with GOOS=darwin, GOARCH=arm64.

For a representative set of large Go programs, linking is 20-25% faster than 1.15 and requires 5-15% less memory on average for linux/amd64, with larger improvements for other architectures and OSes. Most binaries are also smaller as a result of more aggressive symbol pruning.

The go command now supports including static files and file trees as part of the final executable, using the new //go:embed directive.

【引用】Go 1.16 Release Notes

この記事でやること

今回はこの中でもgo:embedを使って、「メッセージ一覧が定義されたjsonファイルを読み込み、そのメッセージを取得する」というモジュールの実装を解説したいと思います!

完成形

完成形だけ知りたい方向けにこちらにコードを貼っておきます。

ディレクトリ構成
(YOUR_DIRECTORY)
├─ message/
|   ├─ message.go
|   └─ messages.json
└─ main.go
messages.json
{
    "key1": "go:embed使ってみたよ!",
}
message.go
package message

import (
    // import embed
    _ "embed"
    "encoding/json"

    "github.com/labstack/gommon/log"
)

//go:embed messages.json
var msgJSON []byte

var msgs map[string]string

// Read メッセージ一覧を読み込む
func Read() {
    if err := json.Unmarshal(msgJSON, &msgs); err != nil {
        panic("Cannot read messages.json")
    }
}

// Get keyからメッセージを取得する(keyがなければ空を返す)
func Get(key string) string {
    msg, exists := msgs[key]
    if !exists {
        log.Errorf("Cannnot find this message key: %s", key)
    }
    return msg
}
main.go
func main() {
    // 一度だけ呼べばOK
    message.Read()

    // メッセージを取り出す
    fmt.Println(message.Get("key1"))
}
実行結果
$ go run .
go:embed使ってみたよ!

解説

embedとは

embedは埋め込みという意味です。
名前の通り、ファイルを変数に埋め込むことができます。

ファイルの読み込み自体は今までもできましたが、複数の書き方があったり、やや冗長な感じがありました。

【参考】[Golang] ファイル読み込みサンプル

しかし、embedを使うことにより、上記message.goのようにものの数行で書けるようになりました。

また、embedで読み込んだファイルはビルドされたバイナリにも埋め込まれます。
これには以下のようなメリットがあります。

これはGoの利点の一つである、単一の実行ファイルとしてビルドできることで、展開先の依存関係をシンプルに保つことができるという利点を強力に後押しします。設定ファイルや各種アセットをビルドに含めることで、バージョン管理やリリース作業を一層シンプルに整理できることが期待できます。

先ほど紹介した簡易WEBサーバーで例えると、WEBサーバーとコンテンツとなるHTML、CSS、Javascriptが分離している場合、ローカル環境で動いたものを実際の環境にデプロイする場合、実行バイナリと各種アセットをデプロイ対象の環境で適宜整理する必要があります。

これらを全て単一のバイナリに含めることができた場合、作業は実行バイナリを一つコピーして起動するだけになります。
新しいサーバーにデプロイする際の運用フローの整備や、プロダクション向けの構成でコンテナを構築するDockerfileを書いていく事を考えると、go:embedで極限まで簡略化できる部分が想像できるかもしれません。

【引用】Go 1.16からリリースされたgo:embedとは

コード解説

message.goの解説をしていきます。

import (
    // import embed
    _ "embed"
)

embedパッケージをimportします。

コード中でembed.~~のような使い方をしないので、ブランクインポートをします。(正確なニュアンスではないですが)

//go:embed messages.json
var msgJSON []byte

ここがembedのキモですね。
message.goと同じディレクトリにあるmessages.jsonを変数msgJSONに埋め込みます。
もちろん必ず同じディレクトリに配置する必要はなく、相対パスで指定することも可能です。

var msgs map[string]string

// Read メッセージ一覧を読み込む
func Read() {
    if err := json.Unmarshal(msgJSON, &msgs); err != nil {
        panic("Cannot read messages.json")
    }
}

よく見かけるコードですね。
mapのmsgsmsgJSONを代入します。

【補足】mapで定義した理由

構造体を定義しておく手法が一般的かと思いますが、messages.jsonの中身が増えるたびに構造体のプロパティを追加していくのが面倒だったのでmapに代入するようにしました。
今回はjsonファイルに文字列しか登場しておらず、map[string]stringで済むのも、mapで定義した理由の1つです。
数字など他の型も考慮する必要がある場合、map[string]interface{}にする必要があり、型安全ではなくなってしまいます。
このように複数の型を扱う場合や、ちゃんと型で守りたい場合は、構造体を使った方が良いと思います。

【参考】Go言語でJSONを扱う

// Get keyからメッセージを取得する(keyがなければ空を返す)
func Get(key string) string {
    msg, exists := msgs[key]
    if !exists {
        log.Errorf("Cannnot find this message key: %s", key)
    }
    return msg
}

ここはただmapからkeyでvalueを取り出しているだけです。
jsonファイルに存在しないkeyが引数で渡ってきた場合log出力するようにしています。
ちなみに存在しない場合、msgは空文字となります。

さいごに

Twitterの方でも、モダンな技術習得やサービス開発の様子を発信したりしているので良かったらチェックしてみてください!

また、BOT開発を通じてGoとLINE BOTにまとめて入門する記事をZennに掲載していますので、良かったらそちらもご覧ください!

参考

Go 1.16からリリースされたgo:embedとは

2
0
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
2
0