はじめに
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:embed
を使って、「メッセージ一覧が定義されたjsonファイルを読み込み、そのメッセージを取得する」というモジュールの実装を解説したいと思います!
完成形
完成形だけ知りたい方向けにこちらにコードを貼っておきます。
(YOUR_DIRECTORY)
├─ message/
| ├─ message.go
| └─ messages.json
└─ main.go
{
"key1": "go:embed使ってみたよ!",
}
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
}
func main() {
// 一度だけ呼べばOK
message.Read()
// メッセージを取り出す
fmt.Println(message.Get("key1"))
}
$ go run .
go:embed使ってみたよ!
解説
embedとは
embed
は埋め込みという意味です。
名前の通り、ファイルを変数に埋め込むことができます。
ファイルの読み込み自体は今までもできましたが、複数の書き方があったり、やや冗長な感じがありました。
しかし、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のmsgs
にmsgJSON
を代入します。
【補足】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の方でも、モダンな技術習得やサービス開発の様子を発信したりしているので良かったらチェックしてみてください!
2月にリリースされたGo1.16の新機能「embed」を使ってファイルの読み込みをしてみました!
— やぎぬ😇行動力エンジニア (@yagi_eng) March 15, 2021
メッセージが定義されたjsonファイルを読み込んでみたのですが、結構いい感じに使えて便利でした
Go1.16の新機能が気になる方やファイル読み込みに悩んでいる方は読んでみてください😇https://t.co/SIp6KqJspl
また、BOT開発を通じてGoとLINE BOTにまとめて入門する記事をZennに掲載していますので、良かったらそちらもご覧ください!
ZennでGoとLINE BOTの記事を書いてみました
— やぎぬ😇行動力エンジニア (@yagi_eng) November 7, 2020
⬇️BOT開発を通じてGoとLINE BOTにまとめて入門するhttps://t.co/QqsEESJMKa
5ステップに分け、「Hello Worldから始めて、飲食店検索ができるLINE BOTの実装まで」を解説しています
GoやLINE BOTに興味のある人は是非読んでみてください😇
24,000字超え😇