You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

领域驱动设计:能否将值对象用作模型辅助工具?

Absolutely! Value objects are perfect for this kind of modeling helper in Domain-Driven Design (DDD)—especially for scenarios like password changes that involve multiple validation rules and data consistency checks. They let you encapsulate business rules and validation logic directly in your domain model, keeping other layers (like API handlers or application services) clean and focused on their core jobs.

Let me walk through how you can apply this to your specific scenario:

1. Create a Value Object for the Password Change Request

Your incoming POST /users/id/password request has three fields that need validation upfront: old password, new password, and repeated new password. Instead of scattering this validation logic in your API layer or service, wrap it into a PasswordChangeRequest value object.

This value object will enforce basic invariants (like matching new passwords, non-empty fields, and password complexity) before the request ever reaches your User aggregate root. Here's a TypeScript example:

class PasswordChangeRequest {
  public readonly oldPassword: string;
  public readonly newPassword: string;

  // Make the constructor private to enforce creation via the static factory method
  private constructor(oldPassword: string, newPassword: string) {
    this.oldPassword = oldPassword;
    this.newPassword = newPassword;
  }

  public static create(
    oldPassword: string,
    newPassword: string,
    newPasswordRepeating: string
  ): PasswordChangeRequest {
    // Enforce validation rules directly in the value object
    if (!oldPassword.trim() || !newPassword.trim()) {
      throw new Error("Old password and new password cannot be empty");
    }
    if (newPassword !== newPasswordRepeating) {
      throw new Error("New password and repeated password do not match");
    }
    if (newPassword.length < 8) {
      throw new Error("New password must be at least 8 characters long");
    }
    // Add other complexity rules here (e.g., mix of letters, numbers, symbols)

    return new PasswordChangeRequest(oldPassword, newPassword);
  }
}

By using this, you guarantee that any PasswordChangeRequest instance passed to your domain is already valid—no need to recheck these rules elsewhere.

2. Turn LocalAuth.password into a Value Object

Your User aggregate's auth.password field is a perfect candidate for a HashedPassword value object. This lets you encapsulate password hashing and verification logic, keeping your User aggregate focused on domain behavior instead of technical details like cryptography.

Example implementation:

class HashedPassword {
  public readonly value: string;

  private constructor(hashedValue: string) {
    this.value = hashedValue;
  }

  // Create a hashed password from a plaintext string
  public static createFromPlaintext(plaintext: string): HashedPassword {
    // Wrap your hashing logic here (e.g., using bcrypt, Argon2)
    const hashed = bcrypt.hashSync(plaintext, 10);
    return new HashedPassword(hashed);
  }

  // Verify a plaintext password against the hashed value
  public async verify(plaintext: string): Promise<boolean> {
    return bcrypt.compare(plaintext, this.value);
  }
}

Update Your User Aggregate to Use These Value Objects

Now your User aggregate can use these value objects to enforce its business rules safely:

class User {
  public readonly id: string;
  public readonly username: string;
  public readonly email: string;
  private auth: { password: HashedPassword };

  constructor(
    id: string,
    username: string,
    email: string,
    initialPassword: HashedPassword
  ) {
    this.id = id;
    this.username = username;
    this.email = email;
    this.auth = { password: initialPassword };
  }

  public async changePassword(request: PasswordChangeRequest): Promise<void> {
    // Verify the old password using the HashedPassword value object
    const isOldPasswordValid = await this.auth.password.verify(request.oldPassword);
    if (!isOldPasswordValid) {
      throw new Error("Old password is incorrect");
    }

    // Replace the old password with a new hashed instance
    this.auth.password = HashedPassword.createFromPlaintext(request.newPassword);
  }
}

Why This Works So Well in DDD

  • Single Responsibility: Validation and cryptography logic lives where it belongs—closer to the data it concerns. Your API layer only needs to parse the request and call PasswordChangeRequest.create(); your application service only needs to pass the valid request to the User aggregate.
  • Invariant Protection: Your User aggregate never has to deal with invalid data. The value objects act as a guardrail, ensuring only valid state changes are allowed.
  • Immutability: Value objects are immutable (no setters), so you never have to worry about accidental state changes breaking your domain rules.
  • Reusability: You can reuse HashedPassword anywhere in your domain where passwords are handled, and PasswordChangeRequest can be extended or adapted for similar password-related operations.

So to answer your question directly: Yes, value objects are excellent modeling helpers in DDD. They help you build a cleaner, more maintainable domain model by encapsulating rules and ensuring data integrity.

内容的提问来源于stack exchange,提问作者Simon Bruneaud

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 10:08:50