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:
- MessagePack Support for when you want that RPC feeling over REST
- TypeScript Client Generator for when you don’t want to use Swagger
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:
Framework | Startup Time | Memory Usage (MiB) - Startup | Memory Usage (MiB) - Load Test | Requests/sec |
---|---|---|---|---|
reaper-aot | 21 | 20 | 88 | 121,284 |
minimal-aot | 21 | 18 | 85 | 119,071 |
reaper | 108 | 20 | 312 | 110,220 |
carter | 118 | 20 | 313 | 106,719 |
minimal | 98 | 20 | 313 | 105,830 |
fastendpoints | 137 | 23 | 317 | 99,591 |
controllers | 145 | 23 | 316 | 98,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:
Reaper.SourceGeneration
tells something that theSTJ Generator
should run after it- Generate out the context
- Finish doing other stuff
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:
Reaper.SourceGeneration
does everything it needs to including generating out the context- Generate a new (completely fake) context that contains the code
- 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.