Hi Amichai! Greetings from Brazil! I am so grateful for your series! I've been following this far and everything just gets better!! Congratulations! Content like this is very rare!
Great content 👌 especially the ending part - Yes!. The pipeline behaviours are like class decorators which obeys the "O" in the SOLID principles - the class should be closed for modifications and open for extensions.
Thanks for this video. I was waiting for a good explanation of MediatR behaviors and validating. Can't wait for domain and application validaiton breakthrough :D
Hello great content. Thank you very much. I have read and seen a lot of content about command validation but I still can't find a reason that justifies so much over-engineering and so much complexity. I can achieve the same thing by injecting a class that performs all the validations with less than half the effort and with the same result. what am i missing? Thanks in advance 👍
Well Done Amichai, I am excited to fo find your course. it is truly helpful. Actually, I did not use your Error0r library so I had some challenges in returning the exception errors and I used my approach. Thanks for sharing.
i don't know if it's just me but that final part with pipeline and Aggregate was so much easier for me when I learned this concept a while ago in js world working with rxjs .pipe(). YOu should probably rename 'pipeline' to 'behavior' when defining Aggregate reducer. Nevertheless, content is terrific as always, thank you Sir!
Instead of invoking a Dynamic Call Site by using the 'dynamic' keyword in '(dynamic)errors' (which is slow) the same effect is accomplished by a simple cast from an object reference: '(TResponse)(object)errors' I try to prevent to use the keyword 'dynamic' at all times when possible.
But actually, why another approach is with using reflection but not that simple as in your comment, may be there are some reasons that it will not work?..
So let's go back to when I asked you why your Commands/Queries weren't your API contracts. You stated that you wanted to be able to change the commands/queries implementations without changing the contracts. Ok, fair enough. However, now, you are validating the Command and not the request. So, when you return a validation problem it is possible you are defining a validation problem on a property that doesn't actually exist on the contract. So, your RegisterRequest could have FullName which you map to First Name, Last Name in your command. Your validator finds the Last Name is empty and returns a problem that states, LastName is required or some such error. How is the client supposed to fix this, because he has no LastName property in his contract? Am, I seeing this correctly, or did I miss something?
Great question. You are correct; since we are validating the executing command/query and not the actual request, there can be a mismatch between the two. One of the cons of Application-layer validation (instead of presentation-layer validation) is that we might have already moved from the API request model to the use-case model, and there might be a mismatch. One of the benefits of application layer validation is that you can take into account application logic and include it in your "Bad Request" response. Is it a big deal that the property name in the response's error list doesn't match the request property? I would argue that it usually isn't, but as always, it depends. What do you think?
@@amantinband I think as a client interacting with an API if I got an error message about a property that wasn't in the request I wouldn't know what to do to fix it or create client-side validations that match the contract. So, in my opinion the Commands and Queries should BE the API contracts. If you need to modify command isn't it a different command, and you should add a new one and maybe deprecate the old one.
On a specific project, maybe. But as a rule of thumb, I would disagree. The API should be known and well documented, and the error response should be descriptive. These together should cover the client’s onboarding to the service. The commands/queries aren't always a 1:1 mapping, and having this abstraction is what the presentation layer is all about. Finished modeling your service and want to switch to gRPC? No problem, you would only update the presentation layer. At Microsoft, there are some services where you have the same flows available for some clients via a REST API and other clients via another technology style bond or gRPC. What do you think? Was that convincing?
@@amantinband No. I think we are agree about 1 thing, the contracts shouldn't change, but disagree that they should be, or need to be different than the commands and queries. Adding another layer and seem that I don't feel is needed. I guess if you document your error responses it's not too much of an issue, but it just doesn't sit right with me.
I have just started to move my API to clean architecture. This helps a lot. Thank you. ❤ Can we have some details about how to work with a SQL database following your approach and how to run CRUD operation in Database?
Great video as usual. Just one thing, instead dynamic I've implemented in this way: First, I split Command and Query into 2 interfaces public interface ICommand : IRequest { } public class ValidationBehaviour : IPipelineBehavior where TRequest : ICommand { private readonly IEnumerable _validators; public ValidationBehaviour(IEnumerable validators) { _validators = validators; } public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) { if (!_validators.Any()) { return await next(); } var context = new ValidationContext(request); var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken))); var errors = validationResults.SelectMany(result => result.ToErrors()).ToList(); if (!errors.Any()) { return await next(); } return errors; } } Because, if I need ErrorOr in e.g. some behavior (logging or whatever) after next() to log something with this approach I have an ErrorOr object. What do you think? Also, I dont like dynamic and I dont like exceptions 😉 Edit: result.ToErrors() line ToErrors is an extension😀
Hey Amichai, great content! I've got a question. Doesn't this go against DDD principle of keeping logic(validation in this case) inside entity classes and ValueObjects in the domain? Right now the domain has no knowledge of these validations
I understand that some validations are going to be repeated in both places, but they are more like insurance. I see it as a way to avoid executing logic that will be invalid due to input parameters. Not all requests will reach the domain layer if you validate the parameters before performing an operation with that layer. Other cases will not even have interaction with the domain, but you will still have to validate input parameters, such as queries. After that, if you somehow make your parameters invalid after the command validators, domain validation will be your insurance. That's how I understand it. Although there are cases where you can ignore the command validators and just use the domain validation, it is up to you.
Will you cover unit testing of the handlers? I’m curious how do you test it with so implicit validation that is sctually done in runtime behind the scenes. Should we not test invalid inputs then?
I noticed someting strange, while the validation handler is called, validation errors are returned before it calls the handler. You can even comment out the pipeline definition in the dependency Injection file and it still return validation erroros.
Thanks for the video. It's a little bit hard to read without word wrapping and big font. if you could maybe close the files list or decrease font a little I think it would be much easier to undrestand.
What status code would you assign to Error.Failure? 500? I guess it depends on what I understand by a failure in my system. I think that NotFound, Conflict and Validation solve the 99% of the usecases. So the Failure could mean some unrecoverable error caused by the server, but the one that you expect to happen eventually. But then what about Unexpected? If you ever return Error.Unexpected in your code, it means that you expect something may happen, so it isn’t unexpected. What is your methodology? And big thanks for the amazing erroror library!
I spend a lot of time searching within Microsoft's internal code bases. These are the best models of how services (and microservices) scale. Some technologies and architectures also have great examples and templates available on GitHub
Can use it in wpf pip behaviors mediatr i thing is distinated for web only asp because i try it many time i given error the services logger cant injected with wpf there are no request
I am very confused. Why do we need pipeline behaviors? A pipeline behaviour basically does something before the the request is handled and after the request is handled. Why can't we make it just part of the request handler? If it is to implement single responsibility, then why can't we just just call one handler after another in order which does the same thing as the pipeline.
Thats because you want to separate use case handlers from validation. If I understand you correctly, if you create multiple handlers in mediator for every validation you will end up having long chain of mediator invokes in your controller and this chain gonna increase every time you need to add new validation rule. Or you can add your own validator somewhere and invoke it from command handler and this will be single responsibility principle violation. So you want your validation separated both: from command handler and from controller and this what pipeline behavior allows you to do.
I love your content but it's really strange for me that you use vs code for this. To be honest without VS + resharper or Rider I feel really unconfortable.
Just some feedback, and this may just be applicable to me. But you go very fast, it's sometimes hard to keep up, and Co-Pilot is not helping with this either. I'm constantly pausing, rewinding, pausing, just to try and keep up. Maybe I'm just slow.
Whoo thats great feedback. I tried going a bit slower in this vodeo, so I'm very curios to hear if others feel the same specifically regarding todays video. Thanks for letting me know!
I viewed the vid at 1.5 speed 😅 But then again I’m not particular interested in the little details but more in the overall structure which is good to grasp at 1.5 speed.
@@amantinband For me the main problem was the Copilot. A lot of times i couldnt read or associate what the method was doing. A feedback I want to give (Love this series btw) is to spend some time in the beginning explaining what is the structure you are sugesting (Maybe a drawing or something). Implementation details don't matter as much imo, but i really want to absorb HOW it was implemented -structure wise- and WHY, and that was why this video in particular got confusing. But anyways thanks for the informative content.
@@amantinband please keep the same speed, people who struggle following you can pause. I love how you making the video short with extensive useful content. Copilot is great 👍
Hi Amichai! Greetings from Brazil!
I am so grateful for your series! I've been following this far and everything just gets better!! Congratulations! Content like this is very rare!
Brazillian here too! Amichai is helping me so much at my first dev job
Great content 👌 especially the ending part - Yes!. The pipeline behaviours are like class decorators which obeys the "O" in the SOLID principles - the class should be closed for modifications and open for extensions.
What?! Zero comments about the most beautiful part of this video?! 😍🐶 28:15
Same!
Thanks for this video. I was waiting for a good explanation of MediatR behaviors and validating. Can't wait for domain and application validaiton breakthrough :D
Thanks!
🫶🏼
Thanks for creating awesome stuff. Please keep making videos on c#.
Thanks, Aslam!
Hello great content. Thank you very much. I have read and seen a lot of content about command validation but I still can't find a reason that justifies so much over-engineering and so much complexity.
I can achieve the same thing by injecting a class that performs all the validations with less than half the effort and with the same result. what am i missing? Thanks in advance 👍
Teşekkürler.
Thanks! 🫶🏼
Well Done Amichai,
I am excited to fo find your course. it is truly helpful.
Actually, I did not use your Error0r library so I had some challenges in returning the exception errors and I used my approach.
Thanks for sharing.
i wish i could be as good as you guys in software development world.
Finally, I'm very happy and great video
I really enjoy the series. Have learned a lot from these series. Anyway, alt+z should toggle the words wrap automatically.
i don't know if it's just me but that final part with pipeline and Aggregate was so much easier for me when I learned this concept a while ago in js world working with rxjs .pipe(). YOu should probably rename 'pipeline' to 'behavior' when defining Aggregate reducer. Nevertheless, content is terrific as always, thank you Sir!
the tutorials are really great, thanks a lot
Thanks, Ugur!
Instead of invoking a Dynamic Call Site by using the 'dynamic' keyword in '(dynamic)errors' (which is slow) the same effect is accomplished by a simple cast from an object reference:
'(TResponse)(object)errors'
I try to prevent to use the keyword 'dynamic' at all times when possible.
Hi, I'm thinking the same thing and was just about to post a new comment asking if I can do this, but I see I'm not the only one who thinks so 🙂
But actually, why another approach is with using reflection but not that simple as in your comment, may be there are some reasons that it will not work?..
All my sympathies to your enter key
So let's go back to when I asked you why your Commands/Queries weren't your API contracts. You stated that you wanted to be able to change the commands/queries implementations without changing the contracts. Ok, fair enough.
However, now, you are validating the Command and not the request. So, when you return a validation problem it is possible you are defining a validation problem on a property that doesn't actually exist on the contract.
So, your RegisterRequest could have FullName which you map to First Name, Last Name in your command. Your validator finds the Last Name is empty and returns a problem that states, LastName is required or some such error. How is the client supposed to fix this, because he has no LastName property in his contract?
Am, I seeing this correctly, or did I miss something?
Great question. You are correct; since we are validating the executing command/query and not the actual request, there can be a mismatch between the two.
One of the cons of Application-layer validation (instead of presentation-layer validation) is that we might have already moved from the API request model to the use-case model, and there might be a mismatch.
One of the benefits of application layer validation is that you can take into account application logic and include it in your "Bad Request" response.
Is it a big deal that the property name in the response's error list doesn't match the request property? I would argue that it usually isn't, but as always, it depends. What do you think?
@@amantinband I think as a client interacting with an API if I got an error message about a property that wasn't in the request I wouldn't know what to do to fix it or create client-side validations that match the contract.
So, in my opinion the Commands and Queries should BE the API contracts. If you need to modify command isn't it a different command, and you should add a new one and maybe deprecate the old one.
On a specific project, maybe. But as a rule of thumb, I would disagree. The API should be known and well documented, and the error response should be descriptive. These together should cover the client’s onboarding to the service.
The commands/queries aren't always a 1:1 mapping, and having this abstraction is what the presentation layer is all about. Finished modeling your service and want to switch to gRPC? No problem, you would only update the presentation layer.
At Microsoft, there are some services where you have the same flows available for some clients via a REST API and other clients via another technology style bond or gRPC.
What do you think? Was that convincing?
@@amantinband No. I think we are agree about 1 thing, the contracts shouldn't change, but disagree that they should be, or need to be different than the commands and queries. Adding another layer and seem that I don't feel is needed.
I guess if you document your error responses it's not too much of an issue, but it just doesn't sit right with me.
I have just started to move my API to clean architecture. This helps a lot. Thank you. ❤
Can we have some details about how to work with a SQL database following your approach and how to run CRUD operation in Database?
Thank you very much about this tutorial :3
Great video as usual. Just one thing, instead dynamic I've implemented in this way: First, I split Command and Query into 2 interfaces
public interface ICommand : IRequest
{
}
public class ValidationBehaviour : IPipelineBehavior
where TRequest : ICommand
{
private readonly IEnumerable _validators;
public ValidationBehaviour(IEnumerable validators)
{
_validators = validators;
}
public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next)
{
if (!_validators.Any())
{
return await next();
}
var context = new ValidationContext(request);
var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var errors = validationResults.SelectMany(result => result.ToErrors()).ToList();
if (!errors.Any())
{
return await next();
}
return errors;
}
}
Because, if I need ErrorOr in e.g. some behavior (logging or whatever) after next() to log something with this approach I have an ErrorOr object. What do you think?
Also, I dont like dynamic and I dont like exceptions 😉
Edit: result.ToErrors() line ToErrors is an extension😀
And the behavior is invoked? I'll have to check this out if yes
@@amantinband
Yes, behavior is invoked. Here is my request example
public class RegisterCommand : ICommand
....
So ApiIdentity is TResponse
@NovaLogic Can you hit a breakpoint in the behavior? If yes, will you be able to share a gist? AFAIK this isn't supported yet.
@@amantinband
drive.google.com/drive/folders/1K3-OQBcpEZdufXjukj14xEXJ_2LWvcW-?usp=sharing
let me know if you have an access issue
I love your content!
Added now, thanks for noticing!
Hey dude love your content. Are you going to do a video on testing the api? Love to see your strategy etc.
Yup 👍
Love your content. It's SO CLEAN :D. Will you demonstrate other behaviors like logging etc?
I might be. Unsure if it would be the simplest way to demonstrate logging, though.
Hey Amichai, great content! I've got a question. Doesn't this go against DDD principle of keeping logic(validation in this case) inside entity classes and ValueObjects in the domain? Right now the domain has no knowledge of these validations
I understand that some validations are going to be repeated in both places, but they are more like insurance.
I see it as a way to avoid executing logic that will be invalid due to input parameters. Not all requests will reach the domain layer if you validate the parameters before performing an operation with that layer.
Other cases will not even have interaction with the domain, but you will still have to validate input parameters, such as queries.
After that, if you somehow make your parameters invalid after the command validators, domain validation will be your insurance.
That's how I understand it. Although there are cases where you can ignore the command validators and just use the domain validation, it is up to you.
Will you cover unit testing of the handlers? I’m curious how do you test it with so implicit validation that is sctually done in runtime behind the scenes. Should we not test invalid inputs then?
Hey Sekresio. Yes, we'll have a couple of videos on testing 👍🏼
I noticed someting strange, while the validation handler is called, validation errors are returned before it calls the handler. You can even comment out the pipeline definition in the dependency Injection file and it still return validation erroros.
Thanks for the video. It's a little bit hard to read without word wrapping and big font.
if you could maybe close the files list or decrease font a little I think it would be much easier to undrestand.
Ooh, interesting feedback. It would be easier for me to code with a smaller font. Do you watch the videos on your phone or computer?
@@amantinband I watch them on my laptop which is 15'.
What status code would you assign to Error.Failure? 500? I guess it depends on what I understand by a failure in my system. I think that NotFound, Conflict and Validation solve the 99% of the usecases. So the Failure could mean some unrecoverable error caused by the server, but the one that you expect to happen eventually. But then what about Unexpected? If you ever return Error.Unexpected in your code, it means that you expect something may happen, so it isn’t unexpected. What is your methodology? And big thanks for the amazing erroror library!
Great content! What kind of programming books do you recommend?
I rarely read books. Mostly learn from documentation, blogs, and my favorite - existing code bases.
@@amantinband Pls give examples of existing code bases :D
I spend a lot of time searching within Microsoft's internal code bases. These are the best models of how services (and microservices) scale. Some technologies and architectures also have great examples and templates available on GitHub
Is it more sensible to configure the life cycle of the fluent validation objets singleton rather than scoped?
Great video! Do you use some type of VS Code extension that proposes the code to complete the statement?
GitHub Copilot
github copilot
Do you have an issue with the 'J' button on your keyboard Ami? 😊
Very very very very good videos by the way.
Shouldn't you assign null to IValidator? validator at 12.36 when injecting optional validator?
Do these commands act like a DTO? Is it appropriate?
Should I pass DTO's into them and validate them like that?
Doesn't look right for me :c
Love your videos!
I changed the validateBehavior from register command to generic type, now validation is not working, API gives no error response.
Thank you.
Can use it in wpf pip behaviors mediatr i thing is distinated for web only asp because i try it many time i given error the services logger cant injected with wpf there are no request
Thanks for the course.
Why u didnt validation on api layer on request models?
That's another common approach. I like putting the validation in the application layer rather than the presentation layer.
@@amantinband this is for performance. In this case what do u prefered? Performance or clean code
I am very confused. Why do we need pipeline behaviors? A pipeline behaviour basically does something before the the request is handled and after the request is handled. Why can't we make it just part of the request handler? If it is to implement single responsibility, then why can't we just just call one handler after another in order which does the same thing as the pipeline.
Thats because you want to separate use case handlers from validation. If I understand you correctly, if you create multiple handlers in mediator for every validation you will end up having long chain of mediator invokes in your controller and this chain gonna increase every time you need to add new validation rule. Or you can add your own validator somewhere and invoke it from command handler and this will be single responsibility principle violation. So you want your validation separated both: from command handler and from controller and this what pipeline behavior allows you to do.
what is the name extention suggest code in visual code you use ?
anyone know how we are connecting the debugger?
Can you share the source code of this project ?
Why the "is 0" thing works
Hello,
I dont understand the need of this behaviour ?
I dont have it and my code still returning the errors as your
Nevermind, it's because you validate the command when i validate the request .. :)
small FYI, this part isn't in the play list. :)
Thanks, added now!
Finally 😃
I love your content but it's really strange for me that you use vs code for this. To be honest without VS + resharper or Rider I feel really unconfortable.
Each one with its pros and cons 🙂
Fix the thumbnail! 😁
Whoops pipline 😂 Thanks 🫶
Oh my God
Just some feedback, and this may just be applicable to me. But you go very fast, it's sometimes hard to keep up, and Co-Pilot is not helping with this either. I'm constantly pausing, rewinding, pausing, just to try and keep up. Maybe I'm just slow.
Whoo thats great feedback. I tried going a bit slower in this vodeo, so I'm very curios to hear if others feel the same specifically regarding todays video. Thanks for letting me know!
I viewed the vid at 1.5 speed 😅
But then again I’m not particular interested in the little details but more in the overall structure which is good to grasp at 1.5 speed.
Haha I'm a x2 speeder. I sometimes wish I could change real life to x2, but that's an indication I need to work on my patience 😂
@@amantinband For me the main problem was the Copilot. A lot of times i couldnt read or associate what the method was doing.
A feedback I want to give (Love this series btw) is to spend some time in the beginning explaining what is the structure you are sugesting (Maybe a drawing or something). Implementation details don't matter as much imo, but i really want to absorb HOW it was implemented -structure wise- and WHY, and that was why this video in particular got confusing. But anyways thanks for the informative content.
@@amantinband please keep the same speed, people who struggle following you can pause. I love how you making the video short with extensive useful content. Copilot is great 👍