Hori Blog

フリーランスでバックエンドエンジニアとして活動している Ryota Hori のブログです。
最近はテック系記事より雑記ブログ気味。

DDD がしたいので Go の Accessor を go generate する go-genaccessor を作った

Go で DDD をやっていくため、 struct に getter setter を生やせる go-genaccessor を作りました。

hori-ryota/go-genaccessor

  • 対象ディレクトリ内のファイルを走査して accessor を生成する
  • getter setter の struct tag を検知して自動生成
  • タグのパラメータに指定することで alias 対応
  • デフォルトがカレントディレクトリなので //go:generate go-genaccessor を対象ディレクトリに記述すれば go generate で起動する

動作サンプル

こんな感じで定義します。

person.go
package example

import (
	"encoding"
)

//go:generate go-genaccessor

type Person struct {
	id   string                 `getter:""`
	name string                 `getter:"" setter:"Rename"`
	tags []string               `getter:"" setter:""`
	text encoding.TextMarshaler `getter:"" setter:""`
}

こんな感じで出力されます。

accessor_gen.go
// Code generated by go-genaccessor; DO NOT EDIT.

package example

import (
	"encoding"
)

func (m Person) ID() string {
	return m.id
}

func (m Person) Name() string {
	return m.name
}

func (m *Person) Rename(s string) {
	m.name = s
}

func (m Person) Tags() []string {
	return m.tags
}

func (m *Person) SetTags(s []string) {
	m.tags = s
}

func (m Person) Text() encoding.TextMarshaler {
	return m.text
}

func (m *Person) SetText(s encoding.TextMarshaler) {
	m.text = s
}

モチベーション

  • ドメイン駆動設計(以下 DDD )をやっていきたい
  • DDD ではドメイン層で対象領域の概念をコードで表現することが重要
  • Go で readonly を表現するのがつらすぎる
    • field はプライベートにし、必要な field に適切な getter を実装する必要がある
      • 概念の表現に集中するには作業ノイズが大きすぎる
      • コード量が増えるため概念の読み取りにノイズが大きすぎる

ということで go generate で自動生成することにしました。超捗る。

コードジェネレータでの生成はフレームワークと違って用途が合わなくなっても過去の生成物はそのまま利用できるのが美味しいですね。

こだわりポイント

  • DDD なので概念がシンプルに表現できていて欲しい
    • struct tag への記載による定義とすることで、何が読み取り可 or 書き込み可なのかわかりやすく、表現力が高い
    • struct tag で表現できているので accessor の実装はノイズになる。よって 1 ファイルにまとめて生成。
  • 概念を表現する作業に集中したい
    • 型解析ではなく ast での parse とすることで、コンパイルエラーが生じる状態のコードでも generator を実行可能にした
  • setter の乱用は防ぎたいが、シンプルな値置換はサポートしたい
    • alias を導入することで Rename のような表現力が高いシンプルな値置換を generate 対象にした
      • 副産物として getter も alias 設定が可能となり、ダックタイピング等がしやすくなった
  • ライブラリにロックインしそうな定義記述はできれば避けたい。実現したいことを定義で表現し、ツールは何でもいいとしたい。

単に楽ができればいいというわけではなく、表現力を高めるための一助となることを目的としました。

以上

ドメイン層とアプリケーション層ではそれぞれドメイン概念とアプリケーション概念を的確に表現することがとても重要なので、本質的な表現以外は全てノイズと考えて人間は気にしないでいいようにしていきたいです。

私事ですが、諸事情で動画配信エンジニアは引退して開発効率おじさんに業務転換することにしました。 まずは正しい DDD の浸透にモチベーションがあるので、ちょこちょこ発信していければと思います。

手始めに generator を色々と整えているのですが、直近では

  • required を表現するための go-genconstructor
  • エラーを表現するための go-generror
  • 結果整合性のために用いるアプリケーションイベントのための go-genappevent

の公開を予定しています。乞うご期待。