🙆

GoのBuilderパターンについて

に公開

現在携わっているプロジェクトで初めてBuilderパターンで実装したので備忘録も兼ねて記述。

Builderパターンとは?

オブジェクトの生成処理を行うためのデザインパターンの1つ。
他にもコンストラクタ関数(Factoryパターン)やFunctional Optionsパターンなどが存在する。

実装方法

package entity

import (
	"errors"
)

type User struct {
	id        int
	name      string
	email     string
	isActiive bool
}

// Getter
func (u *User) ID() int        { return u.id }
func (u *User) Name() string   { return u.name }
func (u *User) Email() string  { return u.email }
func (u *User) IsActive() bool { return u.isActiive }

// ドメインルールの定義
var userDomainRules = DomainRules[User]{
	{
		Description: "ユーザー名が10文字以内であること",
		RuleFunc: func(u User) error {
			if len(u.name) > 10 {
				return errors.New("ユーザー名は10文字以内である必要があります")
			}
			return nil
		},
	},
}

// Builderへ変換
func (u *User) ToBuilder() *UserBuilder {
	return &UserBuilder{user: *u}
}

// Builder構造体
type UserBuilder struct {
	user User
}

func NewUserBuilder() *UserBuilder {
	return &UserBuilder{user: User{}}
}

func (b *UserBuilder) SetID(id int) *UserBuilder {
	b.user.id = id
	return b
}

func (b *UserBuilder) SetName(name string) *UserBuilder {
	b.user.name = name
	return b
}

func (b *UserBuilder) SetEmail(email string) *UserBuilder {
	b.user.email = email
	return b
}

func (b *UserBuilder) SetIsActive(isActive bool) *UserBuilder {
	b.user.isActiive = isActive
	return b
}

func (b *UserBuilder) Build() (*User, error) {
	// ドメインルールを適用
	if err := userDomainRules.Apply(b.user); err != nil {
		return nil, err
	}
	return &b.user, nil
}

使用側では以下のように使用する。

package main

import (
	"fmt"
	"log"
	"my_project/entity"
)

func main() {
	user, err := entity.NewUserBuilder().
		SetID(1).
		SetName("Taro").
		SetEmail("taro@example.com").
		SetIsActive(true).
		Build()
	if err != nil {
		log.Fatalf("ユーザー作成エラー: %v", err)
	}

	fmt.Printf("ユーザー: ID=%d, 名前=%s, Email=%s, Active=%v\n",
		user.ID(), user.Name(), user.Email(), user.IsActive())
}

上記のように、パラメータを設定し、Build()でドメインルールを通した後にオブジェクトを生成できる。

また、以下のようにToBuilder()メソッドを使用して、一部のプロパティだけ差し替えてentityを再生成できる。

updatedUser, err := existingUser.ToBuilder().
	SetName("Takashi").
	Build()

何が良いか

実際に使ってみて、以下の点が良いと感じた。

Entity生成時にドメインルールを通す

→ 不正な状態のEntityが生成されにくい。

一部のプロパティだけ差し替えて柔軟に再生成できる

→ 特に更新処理との相性が良い。

  • データ更新APIを実装する場合
    • repositoryからデータ取得しentityを生成
    • リクエストパラメータの値をToBuilder()メソッドを通してセット
    • repositoryにentityを渡し更新処理実施(一部更新メソッドなど実装しなくて良さそう)

フィールドをプライベートにできるため、不変性を保てる

→ 一度生成したEntityが勝手に変更されることがない。

Discussion