This Article

Using the REPR Pattern on .NET Native AOT with Reaper

What I think is a pretty cool logo. You probably don't.
Note: This article's content was expanded out a little to help with code readability.

Internally, we settled on the brilliant FastEndpoints to implement performant APIs in a way that made sense, using the Request-EndPoint-Response (REPR) pattern.

If you’re still using Controllers or fell all the way down to Minimal APIs then this sits right in the middle and I highly recommend you try FE out.

I’ve got a couple of packages that extend it a little further:

Unfortunately, it doesn’t support Native AOT and likely isn’t going to soon for all the right reasons. dj-nitehawk is a smart guy, and the effort required to maintain all the hell of Source Generation required to get FE working on Native AOT isn’t worth the effort for the amount of people that may want to use it (which appears to be very little).

Perhaps I’m weird, I just want to eke out every bit of performance. It’s not even just about the startup time but the memory usage in operation. When you’re paying for resources as you would in a modern cloud platform, you really want to make things as good as they can be.


That’s where Reaper comes in.

Consider it a very much scaled back version of FastEndpoints, designed for and used in some of our internal microservices where we want that excellent startup and runtime performance.

It relies on something new in .NET 8, which is the introduction of a few tiny APIs that support Minimal APIs to generate out some request delegate handlers. A lot of the stuff in Reaper is very similar to what you’d get from Minimal APIs.

The difference is that you can still wire things up in the REPR pattern with separate Handlers, Requests and Responses.

Here’s an example endpoint:

public class TestRequest
{
    public string Test { get; set; }
}

public class TestResponse
{
    public string Input { get; set; }
    public DateTime GeneratedAt { get; set; }
}

[ReaperRoute(HttpVerbs.Post, "reflector")]
public class ReflectorEndpoint : ReaperEndpoint<TestRequest, TestResponse>
{
    public override async Task ExecuteAsync(TestRequest request)
    {
        Result = new TestResponse()
        {
            GeneratedAt = DateTime.UtcNow,
            Input = request.Test
        };
    }
}

Reaper will take all of this stuff and generate out a tonne of source to support the execution of this, all Native AOT compatible.

The Need for Speed (and good memory performance)

Here’s a meaningless benchmark:

FrameworkStartup TimeMemory Usage (MiB) - StartupMemory Usage (MiB) - Load TestRequests/sec
reaper-aot212088121,284
minimal-aot211885119,071
reaper10820312110,220
carter11820313106,719
minimal9820313105,830
fastendpoints1372331799,591
controllers1452331698,128

But here’s something more important: This basically is the minimal-aot generator. What I’m saying is that if you run it yourself, you’ll probably get results where they swap places.

That’s why they’re so similar, the point of Reaper is not to replace the excellent work that the .NET team are doing to bring Native AOT to us. It’s also not meant to replace FastEndpoints.

What it is meant to do though is make the maintenance of your application much easier by allowing you to use the lovely, clean REPR pattern in your codebase and get what are essentially Minimal APIs off the back of it. As well as generate out enough source to allow you to do less work (see below).

JSON Context Support

But wait, I hear you screaming, what about the JSON models?

Got you. It’s actually impossible using existing tooling to source generate contexts due to the way that source generators work. The simple explanation is that they can’t be chained.

If you’re the inquisitive type you might notice that Reaper has another repository which is the .NET Runtime. This fork slightly modifies the internals to allow us to use the Source Generator for STJ from the Reaper Source Generator.

Yeah so definitely don’t do that yourself, but until they either make the surface public themselves or allow the chaining of generators it’s the best option available.

Ideally, what would happen is:

  1. Reaper.SourceGeneration tells something that the STJ Generator should run after it
  2. Generate out the context
  3. Finish doing other stuff
  4. STJ Generator does its work off the newly generated code

That’s not possible because files made from 1 generator aren’t available in another, so what we actually need to do is:

  1. Reaper.SourceGeneration does everything it needs to including generating out the context
  2. Generate a new (completely fake) context that contains the code
  3. Pass it to a fake version of the STJ Generator to generate even more code

Basically the real generator just isn’t involved as it can’t be.

The relevant code to support the context generation is here.


All the above said, Reaper’s an effort to make things slightly nicer if you want to live in the Native AOT space.

If you’re using Controllers please just stop, look at FastEndpoints if you don’t care about Native AOT.

If you’re using Minimal APIs for Native AOT, check out Reaper.

Reaper on GitHub

Written by Rudi Visser

Fancy reading more?

We'll be writing more in the coming weeks. Check back later!