Let's Go "domain primitive" !

# はじめに

この記事は [Go Advent Calendar 2022](https://qiita.com/advent-calendar/2022/go) 11日目の記事です。
Go歴1年半くらいの @takokun778 が執筆します。

# what's domain primitive ?

タイトルにある`domain primitive`(ドメインプリミティブ)とは以下の特徴を持つものです。

- ドメイン・モデルを構築する最小の要素
- ドメイン・モデルの基礎を形成するもの
- 不変(`immutable`)であり有効となる場合にだけ存在

[セキュア・バイ・デザイン 安全なソフトウェア設計](https://book.mynavi.jp/ec/products/detail/id=124056)という本の第5章ドメインプリミティブで紹介されています。
こちらの本は`Java`を用いて実装例が示されています。

そんな実装をあえて`Go`で書いてみようと思ったのがこの記事の内容となります。
また、本の内容を`Go`で書き換えてみようというよりいろんなところで見聞きしたエッセンスが混ざっている @takokun778 流の実装になります。
とくにプロダクトコードで実績があるわけではない趣味レベルのコーディングになります。
「こんな実装もあるんだなぁ」くらいの視点で読んでいただけると幸いです。

# environment

本記事を記述する際に利用した環境は以下の通りです。

```bash
uname -a
Linux HP-Spectre-x360 5.4.72-microsoft-standard-WSL2 x86_64 x86_64 x86_64 GNU/Linux
```

```bash
go version
go version go1.19.4 linux/amd64
```

# interface!

`domain primitive`であることを宣言するために`interface`を以下のように用意します。
`domain primitive`に必要そうなメソッドを羅列します。

```go
package primitive

// Primitive interface.
type Primitive[T any] interface {
	New(T) (Primitive[T], error)
	Validate(T) error
	Value() T
	String() string
	Reconstruct(T) Primitive[T]
}

```

# duck typing!

`interface`を満たすために以下のように実装します。
文字列をラップした型`Name`ドメインプリミティブを考えます。
それぞれ実装ごとにポイントを紹介します。

```go
package sample

import (
	"domain/primitive"
)

// (1)
// Name for call New() Reconstruct() Validate().
var Name name //nolint:gochecknoglobals

// (2)
// name domain primitive struct.
type name struct {
	value string
}

// (3)
// New domain primitibe construct function.
func (name) New(v string) (primitive.Primitive[string], error) {
	if err := Name.Validate(v); err != nil {
		return name{}, err
	}

	return name{value: v}, nil
}

// (4)
// Reconstruct reconstruct domain primitive function.
func (name) Reconstruct(v string) primitive.Primitive[string] {
	return name{value: v}
}

// (5)
// Validate validation function.
func (name) Validate(v string) error {
	if len(v) == 0 {
		return primitive.NewValidationError("name is empty", nil)
	}

	return nil
}

// (6)
// Value get value.
func (n name) Value() string {
	return n.value
}

// (7)
// String get string mask primitive information.
func (n name) String() string {
	return "*****"
}

```

## (1) 空の大文字変数を宣言

`package名.大文字変数.メソッド`で呼び出すためにあえて大文字変数を宣言してみました。
こうしておくことで外部パッケージから呼び出したときに見た目がなんとなくきれいだなってなります。

```go
n, err := sample.Name.New("sample")
```

型宣言を大文字とすれば以下のような形式で呼び出せますが`{}`がちょっと見た感じ邪魔だなぁって思ったのでこんな謎の変数宣言をしてみました。

```go
n, err := sample.Name{}.New("sample")
```

`Value()`や`String()`メソッドも呼び出せてしまうので扱いには注意が必要となります。呼び出してた場合はデフォルト値(`string`の場合は空文字)が取得されます。

## (2) 小文字での型宣言

`immutable`とするために小文字としました。
構造体フィールドの`value`も小文字なので`New`メソッドでしか設定できないです。

## (3) New()メソッドでコンストラクタ

`value`に対してアクセスする必要はないので`(name)`だけとします。
また、返り値を`primitive.Primitive[string]`としておくことで`name`構造体が`duck typing`できているかどうかの確認ができます。
必要なメソッドが足りない場合はビルドエラーとなります。

```go
func (name) New(v string) (primitive.Primitive[string], error) {
    // ...
}
```

## (4) Reconstruct()メソッドでバリデーションを無視

安全に扱えることが確定している場合は`Reconstruct()`メソッドを利用します。
利用想定としてはDBに保存してある値を再構築したいときなどです。
`New()`を利用しても良いですが`error`を`_`で無視する実装を避けたかったので用意しています。

## (5) Validate()メソッドで妥当性確認

`New()`メソッドの中で呼び出すことを想定していますが外部からも呼び出せるようにしています。
また、こちらの実装が`domain primitive`の仕様となるので重要度が高い実装になります。

## (6) Value()メソッドで値を取得

`value`を小文字宣言して外部から隠蔽しているので値を取得する際は`Value()`メソッドを利用します。

## (7) String()メソッドで文字列表現を取得

どんな型でも文字列として扱いたいタイミングは発生するので用意しています。
また。名前などの個人情報を扱う場合はログなどで出力されないように`String()`メソッドの返り値はマスクした値`*****`を設定します。

# let's use code!

実際のコードで利用する場合は以下のような形式になります。

```go
package main

import (
	"fmt"
	"go-domain-primitive/internal/sample"
	"log"
)

func main() {
	name, err := sample.Name.New("new")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("name.String():\t\t %s\n", name.String())
	fmt.Printf("name.Value():\t\t %s\n", name.Value())

	name = sample.Name.Reconstruct("reconstruct")

	fmt.Printf("name.String():\t\t %s\n", name.String())
	fmt.Printf("name.Value():\t\t %s\n", name.Value())

	fmt.Printf("sample.Name.Value():\t\t %s\n", sample.Name.Value())
	fmt.Printf("sample.Name.String():\t %s\n", sample.Name.String())

	if _, err := sample.Name.New(""); err != nil {
		fmt.Printf("failed to new name error: %v\n", err)
	}
}
```
実行結果は以下となります。
```sh
name.String():           *****
name.Value():            new
name.String():           *****
name.Value():            reconstruct
sample.Name.Value():
sample.Name.String():    *****
failed to new name error: name is empty
```

# おわりに

今回作成したコードのすべてはこちらに置いておきます。

https://github.com/takokun778/go-domain-primitive

ほかにも専用のエラー型、テストコードを作成してあります。

`Go`の特性を利用して作成してみましたがコード量は多くなりました。
`Go`で`domain primitive`みたいなことをするなら以下のようなコードでいい気がします。

```go
package sample

type Name string

func (n Name) Validate() error {
    if n == "" {
        return errors.New("name is empty")
    }

    return nil
}

func (n Name) String() string {
    return "*****"
}

func (n Name) Value() string {
    return string(n)
}
```