Skip to content

Align SharedKernel with CleanArchitecture's Result Pattern #12

@axtox

Description

@axtox

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions