Contents
Actively use Value Objects for variables representing important domain concepts. (重要なドメイン概念を表す変数には、Value Objectを積極的に使用する)
解説
プリミティブ型(文字列、数値など)だけでドメイン概念を表現すると、型安全性が失われ、無効な値の混入リスクが高まります。Value Objectを使用することで、ドメインルールをカプセル化し、型システムの力でバグを防止できます。重要なドメイン概念(メールアドレス、金額、ユーザーIDなど)をValue Objectとして定義することで、コードの表現力と安全性が向上します。
具体例
// 悪い例(プリミティブ型のみ使用)
type User struct {
ID string
Email string
}
func CreateUser(id, email string) (*User, error) {
// バリデーションが散在
if id == "" || email == "" {
return nil, errors.New("invalid input")
}
return &User{ID: id, Email: email}, nil
}
func SendEmail(email string) error {
// emailの妥当性チェックが毎回必要
if !strings.Contains(email, "@") {
return errors.New("invalid email")
}
return emailClient.Send(email, "Hello")
}
// 良い例(Value Object使用)
type UserID struct {
value string
}
func NewUserID(id string) (UserID, error) {
if id == "" {
return UserID{}, errors.New("user ID cannot be empty")
}
return UserID{value: id}, nil
}
func (u UserID) String() string {
return u.value
}
type Email struct {
value string
}
func NewEmail(email string) (Email, error) {
if !strings.Contains(email, "@") {
return Email{}, errors.New("invalid email format")
}
return Email{value: email}, nil
}
func (e Email) String() string {
return e.value
}
type User struct {
ID UserID
Email Email
}
func CreateUser(id UserID, email Email) *User {
// Value Objectは既に検証済みなので追加チェック不要
return &User{ID: id, Email: email}
}
func SendEmail(email Email) error {
// Emailは既に妥当性が保証されている
return emailClient.Send(email.String(), "Hello")
}
