Closed kwentinn closed 4 years ago
Hi @kwentinn we already have something similar. There is a validation service that can called automatically to validate the commands before they are sent to the command handler.
Documentation: https://lucabriguglia.github.io/Kledex/Validation
Regarding the business rules, I add in the aggregate only the rules for the invariants and everything else into a separate validator.
I guess I didn't really understand the point of your validation service ! I'm gonna shift this implementation into a validation service and that should do the trick. Thanks for your feedback @lucabriguglia :)
No problem, give me a shout if you don't want to use Fluent Validation and you want to create a custom validation provider and need help ;-)
Closing as it seems resolved.
I finished working on it and it works fine ! Here's how I did it :
First, I redesigned the IPolicy
interface
public interface IPolicy<ICommand>
where ICommand : Kledex.Commands.ICommand
{
PolicyResult CanExecute(ICommand command);
Task<PolicyResult> CanExecuteAsync(ICommand command);
}
PolicyResult
is just a simple class
public class PolicyResult
{
public bool CanExecute { get; set; }
public string Reason { get; set; }
}
Then I defined a new PolicyValidationProvider
class :
public class PolicyValidationProvider : IValidationProvider
{
private readonly IHandlerResolver _handlerResolver;
public PolicyValidationProvider(IHandlerResolver handlerResolver)
{
_handlerResolver = handlerResolver;
}
public async Task<ValidationResponse> ValidateAsync(ICommand command)
{
var validator = _handlerResolver.ResolveHandler(command, typeof(IPolicy<>));
var validatorType = validator.GetType();
var cmdType = command.GetType();
var validateMethod = validator.GetType().GetMethod("CanExecuteAsync", new[] { command.GetType() });
var policyResult = await (Task<PolicyResult>)validateMethod.Invoke(validator, new object[] { command });
return BuildValidationResponse(policyResult);
}
public ValidationResponse Validate(ICommand command)
{
var validator = _handlerResolver.ResolveHandler(command, typeof(IPolicy<>));
var validateMethod = validator.GetType().GetMethod("Validate", new[] { command.GetType() });
var policyResult = (PolicyResult)validateMethod.Invoke(validator, new object[] { command });
return BuildValidationResponse(policyResult);
}
private static ValidationResponse BuildValidationResponse(PolicyResult policyResult)
{
var errors = new List<ValidationError>();
if (!policyResult.CanExecute)
{
errors.Add(new ValidationError { ErrorMessage = policyResult.Reason });
}
return new ValidationResponse { Errors = errors };
}
}
I create a static class for Kledex extension like so
public static class ServiceCollectionExtensions
{
public static IKledexServiceBuilder AddPolicyValidationProvider(this IKledexServiceBuilder builder)
{
builder.Services.AddScoped<IValidationProvider, PolicyValidationProvider>();
return builder;
}
}
Finally I just need to call the validation provider in the Startup.cs file via service.AddKledex().AddPolicyValidationProvider();
and that's it !
Now, new question ! What if I try to use 2 validation providers at the same time on the same commands ? My guess is that the resolver doesn't know what to do and crashes because I believe you cannot have multiple interfaces having different implementations with aspnetcore DI, am I right ?
@kwentinn excellent! Regarding your new question, it is potentially possible to resolve at runtime multiple implementations of the same interface (like event handlers) but what's the reason for having multiple providers?
Well, let's say I'd like to check the command's parameters first (with the FluentValidationProvider), and then enforce a business rule with a policy like a user e-mail address must be unique (with the PolicyValidationProvider).
I might as well use FluentValidation inside my Policy class. But what I'd like to do is really separate the field or value validation and the business rules which can't be inside the aggregate.
I get your point, but you can do it with FluentValidation as I did here: https://github.com/lucabriguglia/Weapsy/blob/vnext/src/Weapsy.Domain.Validators/Sites/CreateSiteValidator.cs
But, this is an interesting concept. Let's say I don't want my aggregate to be polluted with a lot of business logic, I could have one validator for the command and another one for the aggregate itself to check the invariants. In this case I might have two validators but one provider.
I guess we can close this issue now as it was more a discussion than a real issue. And your solution is quite elegant, so it's fine by me.
Hi guys, I'm currently working on a side-project to check out the possibilities of DDD, CQRS and Event Sourcing. It's a simple scheduling program. I've introduced an
IPolicy
interface that aims at checking some more stuff that the aggregate root cannot (eg. query a read model via some other read model repository). It simply defines a single methodCanExecuteAsync
that the deriving classes must implement.Here's a quick look at the code :
Say we want to register a user, but we want to make sure we don't have 2 users with the same email address. We define a
RegisterUserPolicy
class implementingIPolicy<RegisterUser, User>
to enforce the business rule :Here we handle the
RegisterUser
command, making use of the policy :I was just wondering what you guys think about this IPolicy interface. How would you handle such a business rule ? Would you add it directly inside the aggregate ?
It's working fine, but I'm worried about the lack of synchronisation mechanism between the Read & Write models... If the read model is out of sync, the business rule will fail and may lead to some data inconsistencies.
TL;DR I've created a simple interface and I'm looking for feedback :)