Ardalis.SharedKernel icon indicating copy to clipboard operation
Ardalis.SharedKernel copied to clipboard

Align SharedKernel with CleanArchitecture's Result Pattern

Open axtox opened this issue 8 months ago • 2 comments

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?

  1. Since Ardalis.SharedKernel is 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.
  2. As seen in the CleanArchitecture repo, handlers often return Result<T>. By baking this into the ICommand/IQuery interfaces, we:
    • Eliminate repetitive Result<YourType> declarations.
    • Make the intent immediately clear in derived handlers.
    • Encourage consistent error handling.

Drawbacks

  1. Had to remove the out keyword from ICommand which was originally added for covariant purposes. I attempted to modify Ardalis.Result to include IResult<out T> interface, but that didn't work well - if implemented, it would require IResult<T> to be used consistently across all extension methods in additional packages.
    public interface IResult<out T> : IResult 
    {
        T Value { get; }
    }
    
    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.
  2. Had to introduce the ICommand with no return type (only the Result), and ICommand<T> which allow you to return value wrapped with Result. 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

  1. Does this align with the library's direction?
  2. 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?
  3. Do you think the approach with the separate ICreatedCommand<TId> and ICommand can be discussed further and added as an example of more "clear" CQRS pattern?

axtox avatar May 02 '25 19:05 axtox

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!

ardalis avatar Oct 15 '25 09:10 ardalis

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.

ardalis avatar Oct 15 '25 11:10 ardalis