验证不可变值对象的良好实践

Mik*_*378 4 java validation domain-driven-design

假设 MailConfiguration 类指定发送邮件的设置:

public class MailConfiguration {

  private AddressesPart addressesPart;

  private String subject;

  private FilesAttachments filesAttachments;

  private String bodyPart;

  public MailConfiguration(AddressesPart addressesPart, String subject, FilesAttachments filesAttachements,
    String bodyPart) {
    Validate.notNull(addressesPart, "addressesPart must not be null");
    Validate.notNull(subject, "subject must not be null");
    Validate.notNull(filesAttachments, "filesAttachments must not be null");
    Validate.notNull(bodyPart, "bodyPart must not be null");
    this.addressesPart = addressesPart;
    this.subject = subject;
    this.filesAttachements = filesAttachements;
    this.bodyPart = bodyPart;
  }
  // ...  some useful getters ......

}
Run Code Online (Sandbox Code Playgroud)

因此,我使用两个值对象:AddressesPart 和 FilesAttachment。

这两个值对象具有相似的结构,因此我只在这里公开 AddressesPart :

public class AddressesPart {

  private final String senderAddress;

  private final Set recipientToMailAddresses;

  private final Set recipientCCMailAdresses;

  public AddressesPart(String senderAddress, Set recipientToMailAddresses, Set recipientCCMailAdresses) {
    validate(senderAddress, recipientToMailAddresses, recipientCCMailAdresses);
    this.senderAddress = senderAddress;
    this.recipientToMailAddresses = recipientToMailAddresses;
    this.recipientCCMailAdresses = recipientCCMailAdresses;
  }

  private void validate(String senderAddress, Set recipientToMailAddresses, Set recipientCCMailAdresses) {
    AddressValidator addressValidator = new AddressValidator();
    addressValidator.validate(senderAddress);
    addressValidator.validate(recipientToMailAddresses);
    addressValidator.validate(recipientCCMailAdresses);
  }

  public String getSenderAddress() {
    return senderAddress;
  }

  public Set getRecipientToMailAddresses() {
    return recipientToMailAddresses;
  }

  public Set getRecipientCCMailAdresses() {
    return recipientCCMailAdresses;
  }

}
Run Code Online (Sandbox Code Playgroud)

以及相关的验证器:AddressValidator

public class AddressValidator {

  private static final String EMAIL_PATTERN
    = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";

  public void validate(String address) {
    validate(Collections.singleton(address));
  }

  public void validate(Set addresses) {
    Validate.notNull(addresses, "List of mail addresses must not be null");
    for (Iterator it = addresses.iterator(); it.hasNext(); ) {
      String address = (String) it.next();
      Validate.isTrue(address != null && isAddressWellFormed(address), "Invalid Mail address " + address);
    }
  }

  private boolean isAddressWellFormed(String address) {
    Pattern emailPattern = Pattern.compile(EMAIL_PATTERN);
    Matcher matcher = emailPattern.matcher(address);
    return matcher.matches();
  }
}
Run Code Online (Sandbox Code Playgroud)

因此,我有两个问题:

1)如果出于某种原因,稍后我们想要以不同的方式验证地址邮件(例如包含/排除与现有邮件列表匹配的一些别名),我是否应该公开一种 IValidator 作为构造函数参数?像下面这样而不是带来具体的依赖(就像我所做的那样):

public AddressValidator(IValidator myValidator) {
   this.validator = myValidator;
}
Run Code Online (Sandbox Code Playgroud)

事实上,这将尊重 SOLID 原则的 D 原则:依赖注入。

然而,如果我们遵循这个逻辑,大多数值对象是否都会拥有一个抽象验证器,或者它在大多数情况下只是一种矫枉过正(想想 YAGNI ?)?

2)我读过一些关于 DDD 的文章,所有验证都必须存在并且仅存在于聚合根中,在这种情况下意味着:MailConfiguration。

如果我认为不可变对象永远不应该处于非内聚状态,我是对的吗?因此,在相关实体中,我所做的构造函数中的验证是否会成为首选(因此避免聚合担心其“子项”的验证?

gui*_*e31 5

DDD 中有一种基本模式可以完美地完成检查和组装对象以创建新对象的工作:工厂

我读过一些关于 DDD 的文章,所有验证都必须存在并且仅存在于聚合根中

我强烈不同意这一点。DDD 中的很多地方都可以有验证逻辑:

  • 创建时的验证,由工厂执行
  • 强制执行聚合的不变量,通常在聚合根中完成
  • 跨多个对象的验证可以在域服务中找到。
  • ETC。

另外,我觉得很有趣的是,您费心创建一个 AddressesPart 值对象 - 这是一件好事,而没有考虑首先将 EMailAddress 设为值对象。我认为这使您的代码变得相当复杂,因为没有关于电子邮件地址是什么的封装概念,因此 AddressesPart (以及任何将处理该问题的地址的对象)被迫处理 AddressValidator 以执行其地址验证。我认为这不应该是它的责任,而应该是AddressFactory的责任。