Recently, I came across FluentValidation, a validation framework created by Jeremy Skinner. I browsed the code base & It's very interesting & reflects a great effort. I think I'll learn a lot from it in the future. So, I decided to make a change for a hobby project I'm working on.
In my hobby project, Entities use a simple Design by Contracts, DbC, for validating method inputs.
I had a test like this
1: [Test]
2: public void Cannot_create_account_with_null_name()
3: {
4: string name = null;
5: string description = "Account Description";
6: decimal openingBalance = 2500m;
7:
8: var exception = Assert.Throws<PreconditionException>(()
9: => new Account(name, description, openingBalance));
10:
11: Assert.AreEqual(exception.Message, SystemMessages.Errors.ValueIsRequired("name"));
12: }
That tests a constructor like this
1: public Account(string name, string description, decimal openingBalance)
2: {
3: Check.Require(name.IsNotEmpty(),
4: SystemMessages.Errors.ValueIsRequired("name"));
5: Check.Require(openingBalance >= 0,
6: SystemMessages.Errors.OpeningBalanceMustBeGreaterThanZero);
7:
8: Id = Guid.NewGuid();
9: Name = name;
10: Description = description;
11: OpeningBalance = openingBalance;
12: CurrentBalance = openingBalance;
13: }
I thought to change the test to be like this
1: [Test]
2: public void Cannot_create_account_with_null_name()
3: {
4: string name = null;
5: string description = "Account Description";
6: decimal openingBalance = 2500m;
7:
8: var account = new Account(name, description, openingBalance);
9: var result = validator.Validate(account);
10:
11: Assert.IsFalse(result.IsValid);
12: Assert.AreEqual(1, result.Errors.Count);
13: }
And to create a validator for the Account entity like this
1: using FluentValidation;
2:
3: public class AccountValidator : AbstractValidator<Account>
4: {
5: public AccountValidator()
6: {
7: RuleFor(account => account.Id).NotEmpty();
8:
9: RuleFor(account => account.Name).NotEmpty();
10:
11: RuleFor(account => account.OpeningBalance)
12: .GreaterThanOrEqualTo(0)
13: .WithMessage(SystemMessages.Errors.OpeningBalanceMustBeGreaterThanZero);
14:
15: RuleFor(account => account.CurrentBalance)
16: .GreaterThanOrEqualTo(0)
17: .WithMessage(SystemMessages.Errors.CurrentBalanceMustBeGreaterThanZero);
18: }
19: }
But writing this change, I found it doesn't sound logic. What if the user of this entity didn’t run the validation?! the entity still can be created with invalid arguments which shouldn't happen.
That led me to the question about Contracts against Validation?
The problem is not in the validation framework but I think it’s in the way I used it.
So, I checked some of available examples over the Internet but I found them do the same.
Since that I didn't reach an answer yet, I decided to leave this post openly ended & I hope to receive recommendations or comments about what is the best practice here. When and Where to use, or not to use, Validation and/or Contracts. By `Where` I means in which layer of solution should we use Validation and/or Contracts to reflect different business rules.