Skip to content

Conversation

@viceroypenguin
Copy link
Member

@viceroypenguin viceroypenguin commented Jan 5, 2024

This PR covers the initial implementation of Immediate.Handlers, up to v0.1.

Goal

User code:

public class GetUsersEndpoint(GetUsersQuery.Handler handler)
{
    public async IEnumerable<User> GetUsers() =>
        handler.HandleAsync(new GetUsersQuery.Query());
}

[Handler]
public static class GetUsersQuery
{
    public record Query;

    private static Task<IEnumerable<User>> Handle(
        Query _,
        UsersService usersService,
        CancellationToken token)
    {
        return usersService.GetUsers();
    }
}

public class LoggingBehavior<TRequest, TResponse>(ILogger<LoggingBehavior<TRequest, TResponse>> logger) 
    : Behavior<TRequest, TResponse>
{
    public async Task<TResponse> HandleAsync(TRequest request, CancellationToken token)
    {
        var response = await InnerHandler.HandleAsync(request, token);
    }
}

[assembly: Behaviors(
    typeof(LoggingBehavior<,>)
)]

Immediate.Handlers should generate:

public static partial class GetUsersQuery
{
    public sealed class Handler(
        LoggingBehavior<Query, IEnumerable<User>> loggingBehavior,
        HandleBehavior<Query, IEnumerable<User>> handleBehavior)
    {
        public async Task<IEnumerable<User>> HandleAsync(Query query)
        {
            loggingBehavior.InnerHandler = handleBehavior;
            return await loggingBehavior.HandleAsync(query);
        }
    }

    [EditorBrowsable(false)]
    private sealed class HandleBehavior : Behavior<Query, IEnumerable<User>>(
        UsersService usersService
    )
    {
        public async Task<IEnumerable<User>> HandleAsync(Query query, CancellationToken token)
        {
            return await GetUsersQuery.Handle(
                query,
                usersService,
                token);
        }
    }

    [EditorBrowsable(false)]
    public static IServiceCollection AddHandlers(
        IServiceCollection services)
    {
        services.AddScoped<GetUsersQuery.Handler>();
        services.AddScoped<GetUsersQuery.HandleBehavior>();
    }
}

public static class Registration
{
    public static IServiceCollection AddHandlers(
        this IServiceCollection services)
    {
        services.AddScoped(typeof(LoggingBehavior<,>));
        GetUsersQuery.AddHandlers(services);
    }
}

Tasks:

@dukesteen @Sossenbinder @sid-6581 If you would like to help out with any of these, please feel free to work directly on the draft1 branch. Update the tasks below with your name if you decide to take it, so that we reduce work duplication.

NB: It is expected that CI will fail on this branch for the time being.

  • Update public api documentation for attributes in the Utility project @viceroypenguin
  • Update FAWMN predicate for RenderModeAttribute to only look at [assembly: ] attributes @viceroypenguin
    • Class attributes for [RenderMode] will be overrides, and will be reviewed as part of the HandlerAttribute processing
  • Implement TransformRenderMode @viceroypenguin
  • Implement TransformBehaviors (simple) @viceroypenguin
  • Implement more complex parsing for TransformBehaviors @viceroypenguin
  • Implement TransformHandler @viceroypenguin
  • Implement RenderHandler @Sossenbinder
  • Add Sample for Normal render mode @sid-6581
    • Note: Existing Class1.cs is only for debugging purposes and should be removed in favor of more complete and practical sample
  • Add unit tests
    • Basic functionality
    • Services
    • Behaviors
    • Behavior constraints
    • Behavior Filtering
  • Add functional tests
  • Add Analyzers for:
    • Behavior attribute references non Behavior<,> type @Head0nF1re
    • Behavior attribute references type without two generic parameters @Head0nF1re
    • Behavior attribute references generically-bound type (aka Behavior<int, int> is invalid) @Head0nF1re
      • NB: the reason for the above rules is because even if TRequest implements a type, if behavior1 is above behavior2, and b1 explicitly relies on a baser class than b2, then b1 will not be able to call InnerHandler.HandleAsync(request), due to request not being of the right type. However, if both are generics, but are constrained, then the bound type will satisfy for both b1 and b2, and it will compile.
    • RenderMode attribute is not a valid render mode [IHR0004] @viceroypenguin
      • Add documentation
    • Handler Method exists [IHR0001]
    • Handler Method returns Task<...> [IHR0002] @dukesteen
    • Handler Method has at least two parameters (request type and cancellation token) [IHR0003] @dukesteen
    • Handler class defines a Query or Command record [IHR0009] @dukesteen
      • Add documentation
      • Add codefix
    • Handler class is not nested [IHR0005] @Sossenbinder
      • Add documentation
  • Update readme.md @viceroypenguin
  • Rearrange projects for better nuget packaging @viceroypenguin

Render Mode

Currently, only Normal will be implemented. A future goal will be to add support for Controllers, FastEndpoints, and MinimalApi render modes; these will allow reducing the amount of code needed to be written for these situations by an additional layer.
 

Other notes

Repo structure has been designed based on my preferences. I am open to suggestions for improvements to the repo structure.

@Sossenbinder
Copy link
Contributor

Thanks for the effort, I'll take a closer look later and check where I could participate!

@dukesteen
Copy link
Collaborator

@viceroypenguin what's the analyzer for invalid behaviors for? You have the base class handling that no?

@viceroypenguin
Copy link
Member Author

@viceroypenguin what's the analyzer for invalid behaviors for? You have the base class handling that no?

"invalid behaviors" in this context would be: doesn't inherit from Behavior<,>, etc.

@dukesteen
Copy link
Collaborator

etc

Ahh so in the assembly attribute

@Sossenbinder
Copy link
Contributor

@viceroypenguin I just spent some time on the repo and read up on the incremental generator doc, I think I'm up to speed. I might be interested in checking out the Handler rendering tomorrow afternoon, if you think that would work for you? Not sure, since it already contains some content

@viceroypenguin
Copy link
Member Author

@viceroypenguin I just spent some time on the repo and read up on the incremental generator doc, I think I'm up to speed. I might be interested in checking out the Handler rendering tomorrow afternoon, if you think that would work for you? Not sure, since it already contains some content

@Sossenbinder Sure, that would work. Ping me on discord and we can work together - we're going to have to figure out together what's needed for rendering and how to pass that from the transformation in a good way.

@dukesteen
Copy link
Collaborator

@viceroypenguin I suggest using ThisAssembly.Resources for all the templates, thoughts?

@viceroypenguin
Copy link
Member Author

@viceroypenguin I suggest using ThisAssembly.Resources for all the templates, thoughts?

I love ThisAssembly.Resources. Unfortunately, until this PR is merged and released, there is an unmaskable warning that shows up under latest-all. Given the length of the helper function, I'm gonna leave as is fro now.

@viceroypenguin viceroypenguin merged commit 38a5934 into master Jan 10, 2024
@viceroypenguin viceroypenguin deleted the draft1 branch January 10, 2024 14:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants