public class Customer
{
public int Id { get; set ; }
public int SocialInsuranceNumber { get; set ; }
public bool IsValid()
{
// my logic to validate social insurance number
}
}
First, avoid creating invalid objects. It only makes the code harder to maintain. If Customer is an object in your domain model, then it shouldn't be doing the data validation at all -- that's a job for the anti corruption component to deal with.
The usual answer is to create a value type for your state.
public class Customer
{
public Id<Customer> id
public SocialInsuranceNumber sin
...
}
The constructor for these types should throw an exception if the data isn't valid; therefore, the Customer entity doesn't need to worry about that at all.
The business has the requirement of one single application - for majority of the situations when user enters a social insurance number we have to make sure the value cannot start with 0 and the value must be a valid SIN, but in some unique situation we must allow a valid SIN starting with 0.
"Some unique situation" is a a bit vague; in DDD you would be expected to sit down with your domain experts and get a much better understanding of that.
First problem: do you need to support customers with SINs that use the leading 0? The only acceptable answers are "No, never", and "Yes". No, never is straight forward, so let's look at yes.
First, we create a value type for the looser rules of the SIN.
public class ValidSocialInsuranceNumber {
public final String id;
ValidSocialInsuranceNumber(String id) {
// throw illegal argument exception of the sin isn't valid
validate(id);
this.id = id;
}
}
public class SocialInsuranceNumber {
public final ValidSocialInsuranceNumber validSin;
SocialInsuranceNumber(ValidSocialInsuranceNumber validSin) {
// throw an illegal argument exception if the sin
// has been issued for Fictitious purposes
forbidFictitious(validSin.id);
this.validSin = validSin;
}
}
Since we sometimes need to allow a valid but fictitious SIN, the customer needs
to be modeled with the looser constraint.
public class Customer
{
public Id<Customer> id
public ValidSocialInsuranceNumber sin
...
}
But your anti corruption layer doesn't need to; instead, most paths can use the strict version to validate the user inputs, and then down grade to the other when
the check is no longer needed.
addCustomer(String id, String sin) {
customerId = new ID<Customer>(id);
customerSIN = new SocialInsuranceNumber(sin);
customer = new Customer(customerId, customerSIN.validSin)
}
If the sin doesn't validate under the strict rules, report the error back to the human operator, perhaps with a note to let them know that support does have a "unique" path available that they can execute on the operator's behalf.
Then, in your "unique" code path, use the more forgiving logic
addUniqueCustomer(String id, String sin) {
customerId = new ID<Customer>(id);
validSin = new ValidSocialInsuranceNumber(sin);
customer = new Customer(customerId, validSin)
}
That's how I would handle it in the case as described, where real exceptions are less likely than typos.
When that's not the case, then I start looking to using a process; allow the user to be created (possibly in a disabled state), but also raise an event that an unusual SIN has been added, surfacing it for the business to act upon. Using process managers to mitigate contingencies is a common approach in ddd
bool IsFiticiousSIN
property to theCustomer
class? Alternatively, you can