ファクトリ関数の解釈
役に立った事例:UUIDの生成とテスト対応
背景
以下はドメイン層における uuid.go
のコードである。
// domain/uuid.go
package domain
// UUIDGenerator は UUID を生成するインターフェース
type UUIDGenerator interface {
NewUUID() (UUID, error)
}
// UUID は UUID を表す値オブジェクト
type UUID struct {
value string
}
// Value は UUID の値を返す
func (u UUID) Value() string {
return u.value
}
UUID の生成方法が外部に依存しないよう、UUIDGenerator
はインターフェースとして定義している。
インフラ層やテストコードでは、このインターフェースを実装する必要がある。
問題点
テストコード内で以下のように UUIDGenerator
をモック実装した。
// モックUUIDGenerator実装
type mockUUIDGenerator struct{}
func (mockUUIDGenerator) NewUUID() (domain.UUID, error) {
// テスト用の固定UUIDを返す
return domain.UUID{value: "550e8400-e29b-41d4-a716-446655440000"}, nil
}
しかし、以下のようなエラーが発生した。
usecase/create_account_test.go:40:24: cannot refer to unexported field value in struct literal of type domain.UUID
これは Go 言語のカプセル化(非公開フィールド)のルールにより、外部から value
フィールドを直接参照できないことが原因である。
値オブジェクトが「生成時の一貫性保証」という特性を持つ以上、これは自然な振る舞いであるとも言える。
だがこのままでは、ドメイン外から UUID
を生成できず、インターフェースの実装が不可能になる。
解決策:ファクトリ関数の導入
この問題は、ファクトリ関数を用いることで解決できる。
修正後の uuid.go
package domain
import "errors"
var (
ErrInvalidUUID = errors.New("invalid UUID format")
)
// UUIDGenerator は UUID を生成するインターフェース
type UUIDGenerator interface {
NewUUID() (UUID, error)
}
// UUID は UUID を表す値オブジェクト
type UUID struct {
value string
}
// Value は UUID の値を返す
func (u UUID) Value() string {
return u.value
}
// ファクトリ関数:バリデーション付きで UUID を生成する
func NewUUID(value string) (UUID, error) {
if !isValidUUID(value) {
return UUID{}, ErrInvalidUUID
}
return UUID{value: value}, nil
}
// プライベートなバリデーション関数(簡易的なUUIDv4形式チェック)
func isValidUUID(value string) bool {
return len(value) == 36 &&
value[8] == '-' &&
value[13] == '-' &&
value[18] == '-' &&
value[23] == '-'
}
※ UUIDGenerator
のメソッドとファクトリ関数が同名であるが、それぞれのスコープが異なるため問題はない。
テストコードの修正
テストコードでは、ファクトリ関数を通じて UUID を生成するように変更する。
// モックUUIDGenerator実装
type mockUUIDGenerator struct{}
func (mockUUIDGenerator) NewUUID() (domain.UUID, error) {
return domain.NewUUID("550e8400-e29b-41d4-a716-446655440000")
}
まとめ
ドメイン層の構造体にカプセル化された非公開フィールドがある場合、外部から直接生成することができない。
そのような状況でも、ファクトリ関数を導入することで、生成ロジックの一貫性を保ちつつ、外部からのインスタンス生成が可能になる。
特にテストやモックの場面で、値オブジェクトの生成に制限がある場合には、ファクトリ関数が非常に有効な手段となる。