Good practice for checking immutable value objects

Suppose the MailConfiguration class, which defines the options for sending emails:

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 ...... } 

So, I use two value objects: AddressesPart and FilesAttachment.

Theses of the two value objects have similar structures, so I'm going to expose AddressesPart here:

 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; } } 

And related validator: 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(); } } 

So I have two questions:

1) If for some reason later we want to check the address mail differently (for example, to include / exclude some aliases that correspond to the existing mail list), should I set the IValidator view as a constructor parameter? like, for example, the following, rather than citing a specific dependency (as I did):

 public AddressValidator(IValidator myValidator) { this.validator = myValidator; } 

In fact, this will respect principle D of the SOLID principle: dependency injection.

However, if we follow this logical, will most value objects have an abstract validator, or is it just outsmarting most of the time (thinking YAGNI?)?

2) I read in some articles than with regard to DDD, all checks should be present and present only in Aggregate Root, this means MailConfiguration in this case.

Am I right when you consider that immutable objects should never be in unchesive state? Thus, will validation in the constructor, as I have done, be preferable in the corresponding object (and thus avoiding the aggregate in order to worry about checking it for β€œchildren”?

+4
source share
4 answers

I’m not quite sure if I will follow you 100%, but one way to deal with providing immutable objects is only allowed if they are valid, use Essence .

In short, the idea is that the parent class contains a static factory that creates immutable instances of itself, based on instances of the inner entity class. The inner essence is changeable and allows you to create objects, so you can add pieces during your work and can also be checked along the way.

SOLID principles and good DDD are respected because the parent immutable class still does only one thing, but allows others to create it through an "entity".

As an example, consider the Ldap extension in the Spring Security Library.

+2
source

There is a basic template in DDD that perfectly performs the task of checking and assembling objects to create a new one: Factory .

I read in some articles than with regard to DDD, all checks should be present and present only in Aggregate Root

I strongly disagree with this. Validation logic can be in a wide range of places in DDD:

  • Factory verification after creation
  • Execution of aggregated invariants, usually performed in a composite root.
  • Validation spanning multiple entities can be found in domain services.
  • and etc.

Also, it’s funny to me that you took the trouble to create an AddressesPart value object, which is fine, without considering that EMailAddress is a value object in the first place. I think this is quite difficult to complicate your code, because there is no encapsulated representation of what an email address is, so AddressesPart (and any object that will manipulate addresses, for that matter) is forced to deal with AddressValidator to perform validation his addresses. I think that this should not be his responsibility, not the AddressFactory address.

+3
source

Some observations first.

Why are there no generics? J2SE5.0 was released in 2004.

The current version of Java SE has the Objects.requiresNonNull standard. A little sip, and capitalization is wrong. Also returns the passed object, so a separate line is not required.

  this.senderAddress = requiresNonNull(senderAddress); 

Your classes are not completely immutable. They are subclasses. They also do not make a safe copy of their mutable arguments ( Set - ashamed that the Java library does not yet have immutable collection types). Note. Copy before checking.

  this.recipientToMailAddresses = validate(new HashSet<String>( recipientToMailAddresses )); 

Using ^ and $ in a regular expression is a little misleading.

If validation changes, then there are two obvious (reasonable) options:

  • Use only the widest variations of this class. Confirm more specifically in the context that it will be used.
  • Go to your validator and use this as a property. To be useful, client code would have to check and do something reasonable with this information, which is unlikely.

It makes no sense to pass the validator to the constructor, and then discard it. This makes the constructor difficult. Put it in a static method if necessary.

The attached instance must verify that its argument is valid for a particular use, but should not overlap with classes, ensuring that they are generally valid. Where will it end?

+1
source

Although an old question, but for those who stumbled on this subject, please keep it simple with POJOs (regular old Java objects). As for validations, there is no one truth, because for pure DDD you need to keep context in mind. For example, a user who does not have credit card information can and should be able to create an account. But credit card information is needed when checking on the shopping cart page. As DDD has perfectly solved this, it is moving bits and pieces of code into Entities and Value Objects, where it naturally belongs.

As a second example, if the address should not be empty in the context of a domain-level task, then the address value object should force this statement inside the object, instead of asking the third-party library to check whether the specific object is null or not.

In addition, an address as an object with an autonomous value does not give much value at its discretion compared to ShippingAddress, HomeAddress or CurrentResidentialAddress ... the ubiquitous language, in other words, the names convey their intentions.

0
source

Source: https://habr.com/ru/post/1402295/


All Articles