Align SharedKernel with CleanArchitecture's Result Pattern
Hi @ardalis,
I've created a PR #13 that introduces the Ardalis.Result into the ICommand and IQuery interfaces directly in Ardalis.SharedKernel. The goal is to provide a standardized way to handle operation using Result Pattern in CQRS-based workflows, improving consistency and readability. I have already implemented this approach in several SharedKernel projects.
Why This Change?
- Since
Ardalis.SharedKernelis meant to serve as a reference implementation (copied/modified rather than used directly), it should demonstrate best practices. The Result pattern is widely adopted for clear error handling and richer return types. - As seen in the CleanArchitecture repo, handlers often return
Result<T>. By baking this into theICommand/IQueryinterfaces, we:- Eliminate repetitive
Result<YourType>declarations. - Make the intent immediately clear in derived handlers.
- Encourage consistent error handling.
- Eliminate repetitive
Drawbacks
- Had to remove the
outkeyword fromICommandwhich was originally added for covariant purposes. I attempted to modifyArdalis.Resultto includeIResult<out T>interface, but that didn't work well - if implemented, it would requireIResult<T>to be used consistently across all extension methods in additional packages.
However, this change would allow automatic casting to base types, providing a potential benefit. - Currently, developers are forced to use .Map() or .Bind() extension methods, if I'm not mistaken.public interface IResult<out T> : IResult { T Value { get; } } - Had to introduce the
ICommandwith no return type (only theResult), andICommand<T>which allow you to return value wrapped withResult. Personally, I don't like the idea to return more than created entity Id or domain concept struct, so I have another suggestion to consider. The idea is to have 2 different types of commands: those that return created entity identification information and empty one, returning only the result information:public interface ICommand : IRequest<Result>; // TId should be the strongly typed domain concepts // representing the ID of the entity, so it needs to have constraints public interface ICreatedCommand<TId> : IRequest<Result<TId>> where TId : struct, IComparable, IComparable<TId>, IConvertible, IEquatable<TId>, IFormattable;
Things To Discuss
- Does this align with the library's direction?
- Do you think if is worth to consider alternative approaches for covariance using
IResult<out T>and what are the main drawback in this approach? - Do you think the approach with the separate
ICreatedCommand<TId>andICommandcan be discussed further and added as an example of more "clear" CQRS pattern?
Sorry for the late reply, just getting back to this repo now in preparation for net10 updates. I like what you have in the PR but I've just updated everything to switch from MediatR to Mediator and in so doing I actually removed the ICommand and IQuery types because I plan to just leverage the ones built into Mediator Abstractions. But having some direct support for result could be interesting. I'm going to close your PR but once I finish rolling this out I may return to this issue and see if it makes sense to add this back in (or see if you want to do a revised PR). Thanks!
I'd want to try out these approaches in an experimental way before putting them here since the template depends on these by default. What I've been doing lately is using the sample project inside the clean architecture repo for such experiments, and then when I'm pretty happy with them pulling them into the main template. That sample actually uses a separate (but very similar) sharedkernel package hosted over at github.com/nimblepros/sharedkernelsample. Maybe we can play around with these more opinionated result-focused ideas there. I'll add an issue there now and reference this one.