Nix Flakes による Go + Protocol Buffers 開発環境の紹介
概要
Nix パッケージマネージャの機能の一つである Nix Flakes を使って, Go 言語における Protocol Buffers を扱える開発環境を構築したときのメモです.理解した内容の整理も兼ねて公開します.
Nix に関連する日本語の情報はとても少ないため,布教の目的で Nix 自身についての簡単な紹介もします.
実用的な使用例をみてもらうことで,興味を持っていただける人がいたら嬉しいです.
それに加えて Protocol Buffers についての紹介を行い (#技術的背景),本題に入ります.
※この記事の内容は筆者の理解で書かれています.
自分自身使いながら覚えていっているので,もし間違っている点などあればご指摘いただけると嬉しいです.
技術的背景
Nix / NixOS / Nix Flakes, Protocol Buffers についての紹介を行います.
Nix 関連については過去にも似たような 紹介記事 を公開しているので,興味があればそちらもご参照ください.
本記事では軽い紹介にとどめ1,詳しい使い方やインストール方法については引用先の公式ページ等をご覧ください 2.
Nix
Nix とは,ざっくり言うと「色々なことを宣言的に管理して便利にしよう!」という思想のもと開発されているソフトウェア群です.
一般に「Nix」と呼ばれるものには大きく三種類あります 3.
-
Nix 言語
- 純粋関数型言語である
- 分類としては YAML, JSON に近い
-
Nix パッケージマネージャ
- NPM における
package.json
のようなものを Nix 言語で宣言的に記述するパッケージマネージャである- macOS の brew や Debian 系 OS の apt のような存在
- ビルドの再現性が高いこと, Docker のように環境の隔離を行えること (nix-shell) などが特徴
- NPM における
-
NixOS
- Linux ディストリビューションである
- Nix 言語で多様なシステム設定を宣言的に行うことができる
- Nix パッケージマネージャが標準で付属している
この三つをごちゃ混ぜに「Nix」と呼ぶことが多いと思います.
そもそも,役割としてこの三つが強く結びついているので,セットとして意図的に「Nix」と単に呼ぶ場合もあります.
Nix Flakes
先述した Nix パッケージマネージャの一つの機能です.
パッケージを,より再現性と発見可能性の高い (discoverable) 形 = Flake で定義し,取り扱うことができます.
その再現性は,Git のコミットハッシュやソースコードの SHA256 ハッシュを結びつける形で依存関係を整理することなどで実現されています.
Nix Flakes は,例えば以下のような機能を持ちます 4.
- パッケージのビルド方法を定義し,出力を得る (
nix build
) -
docker run ... bash
のように,隔離された環境の開発用シェルを使うことができる (nix develop
) - 他人の作った Flake の出力コマンド等をインストールする (
nix profile install
) - NixOS のシステム環境設定を Flake として記述する
この特性から,開発環境として Docker の代わりに使ったり, CI/CD 用途やサーバ用途にも適しています.
自分もまだ全容を掴んでいるわけではありませんが,今のところとても便利です.
最近のプロジェクトでは,実験的に Docker の代わりとして使うようにしています.
Nix Flakes の使用例
使用例は本記事の本題として紹介する他,筆者が過去に公開した Nix Flakes の紹介記事 にも例があります.
Protocol Buffers
Protobuf とも呼ばれます.
Google が作った (?),言語・プラットフォーム非依存な,データ構造をシリアライズするための仕組みです.
やることは XML や JSON に似ていますが,それらと比べて smaller, faster, and simpler とのことです.
定義されたデータ構造は,通信プロトコルやデータ保存に活用することができます.
Protobuf はデータ構造の定義をサポートしますが,これに遠隔手続き呼び出し (Remote Procedure Call) の定義を加えたものが gRPC のようです.
Protobuf を使ってデータの構造を定義すると,各プログラミング言語のライブラリを使って簡単にシリアライズ・デシリアライズすることができます.
Protobuf によるデータ構造の定義は, .proto
ファイルに記述されます.
protoc
コマンドがこの .proto
ファイルを読み出し,定義されたデータを各言語で読み書きするためのコードを自動生成してくれます.
.proto
ファイルの例
公式の example にあるものの抜粋です.
syntax = "proto3";
package tutorial;
import "google/protobuf/timestamp.proto";
message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
google.protobuf.Timestamp last_updated = 5;
}
// Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}
この protobuf 定義ファイルでは, Person
というデータの構造 (message
) を定義しています.
冒頭のオプション関係を除けば,全ての内容が Person
に関連する構造を定義しているものです.
プログラミング言語っぽい見た目をしているため,眺めればなんとなく意味をつかめると思います.
ただし, = 1
などの部分はいわゆる代入ではありません.
ある message
に存在する各フィールドには,ユニークな識別子 tag がついている必要があり, =
記号でこの tag を結びつけているのです.
シリアライズなどの際にこの tag を使用するようで,番号の重複や使い回しが許されないなど,制約を持ちます.
Nix Flakes による Go + Protocol Buffers 開発環境
本題に入っていきます.
Nix Flakes を使った Go + Protocol Buffers 開発環境の作り方です.
といっても, Nix Flakes が有効な環境であれば,準備は以下の flake.nix
を作るだけです.
{
description = "";
inputs = {
nixpkgs = { url = "github:NixOS/nixpkgs/nixpkgs-unstable"; };
flake-utils = { url = "github:numtide/flake-utils"; };
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
inherit (nixpkgs.lib) optional;
pkgs = import nixpkgs { inherit system; };
in
{
# Go コンパイラ, Go 向け Protobuf コンパイラが有効な開発用シェル
devShell = pkgs.mkShell {
buildInputs = [
pkgs.go
pkgs.protobuf
pkgs.protoc-gen-go
];
};
});
}
このような flake.nix
がある状態で nix develop
とすれば, go
, protoc
コマンドが有効な開発用シェルに入ることができます.
開発環境の作り方としては以上ですが,ここからは実際に開発を行う際の手順をざっくり説明していきます.
開発の際の手順
実際に開発を行う際の手順をざっくり説明していきます.
なお,貼ってあるサンプルコードは GitHub に公開しています.
1. flake.nix
を作る
上にある flake.nix
を,対象 Go プロジェクトのディレクトリに貼り付けます.
もし既に flake.nix
が存在する場合は,うまいことマージします.
大事なのは devShell = ...
の部分です.
2. Protobuf ファイルを書く
扱いたいデータの定義を含んだ .proto
ファイルを作成します.
例は技術的背景の章でも出しましたが,よりシンプルな例なら以下のようなものです.
syntax = "proto3";
package sample;
option go_package = "./main";
message Test {
string some_text = 1;
}
option go_package
には,生成される Go コードがどのパッケージに属するかを書きます.
今回は ./main
としています 5.
3. 開発用シェルに入り,自動生成の Go コードを得る
Go コードの自動生成は, protoc
コマンドを使って行います.
protoc
などのコマンドを使うために, flake.nix
で定義した開発用シェルに入ります.
nix develop
この時,依存パッケージのダウンロードなどが入ります.
それが終わってシンプルな bash プロンプトが現れたら,以下のコマンドを打ちます.
bash-5.1$ protoc -I=. --go_out=paths=source_relative:. sample.proto
すると,以下のように自動生成された Go コードを含む sample.pb.go
が生成されます.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.21.8
// source: sample.proto
package main
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
// ...
ちなみに, ^D
で開発用シェルを抜けると, protoc
コマンドも go
コマンドも使えないことがわかります 6.
このように,そのプロジェクト専用の隔離環境を作るツールとして Flake は便利です.
bash-5.1$ ^D
$ go
zsh: command not found: go
$ protoc
zsh: command not found: protoc
$ which go
go not found
$ which protoc
protoc not found
4. 構造体を Go プログラムで扱う
3. で自動生成した Go プログラムを読み込むことで,定義したデータ構造を扱えるようになっています.
適当に以下のような Go プログラムを実行してみます.
同じパッケージである sample.pb.go から Test
構造体の定義を読み込み, protobuf の機能を使って読み書きするものです.
package main
import (
"fmt"
"google.golang.org/protobuf/proto"
)
func main() {
test1 := &Test{
SomeText: "hello",
}
// []byte に変換
out, _ := proto.Marshal(test1)
test2 := &Test{}
// []byte から再度 Test{} に変換
_ = proto.Unmarshal(out, test2)
fmt.Println(test2.SomeText)
}
bash-5.1$ go run .
hello
このように, proto.Marshal
/ Unmarshal
を使って構造体を []byte
と相互変換できます.
今回は []byte
との相互変換のみ行いましたが,このバイト列をファイルに書き込んだり,ネットワークを通じて送受信するなど,さまざまな使用方法が考えられます.
まとめ
本記事では, Nix Flakes によって Go 言語 + Protocol Buffers 開発環境を構築する方法を紹介しました.
また, Nix / Nix Flakes, Protocol Buffers についての簡単な紹介を行いました.
本記事の目的の一つは,Nix / Nix Flakes の実用的な使用例を紹介することで,興味を持ってくれる方を増やすことでした.
一つ flake.nix
ファイルを書くだけなので,とても簡単だと感じていただけたのではないかと思います.
また, Protocol Buffers を初めて触る機会があったので,覚えたことの整理も兼ねていました.
Nix 関連の記事はまた書くつもりでいるので,よければそちらも読んでみていただけると嬉しいです.
以上です.読んでいただきありがとうございました.
参考文献
- https://developers.google.com/protocol-buffers/docs/gotutorial: Go + Protobuf のチュートリアル
- https://developers.google.com/protocol-buffers/docs/proto3: Protobuf 3 の言語ガイド
- https://developers.google.com/protocol-buffers/docs/reference/go-generated: 自動生成された Go コードについての説明
- https://pkg.go.dev/google.golang.org/protobuf/proto: proto ライブラリの GoDoc
- https://github.com/golang/protobuf/issues/872: 自動生成コードの出力先をカレントディレクトリにする方法