In this article, we are going to explore the REPR (Request-Endpoint-Response) design pattern in .NET.
We’ll start our discussion by examining what the REPR pattern entails. Then, we’ll go on to see how we can use it in our .NET projects. Finally, we’ll look at the advantages of implementing this pattern and some potential drawbacks we might encounter when utilizing it.
Let’s begin.
VIDEO: Implement REPR Pattern in .NET Web API.
What Is the REPR Design Pattern?
The REPR (pronounced “Reaper”) pattern is a novel design pattern that emphasizes the design of APIs around endpoints instead of controllers. Created by Steve “ardalis” Smith, this pattern helps us improve the organization of the endpoints in our APIs, making them easier to locate, navigate and modify.
The REPR pattern suggests that we define each endpoint in our application as an individual class, with each class having a single method to handle incoming requests. With this pattern, endpoints serve as the fundamental building blocks of our projects.
To achieve such an endpoint-centric application, the REPR pattern prescribes that our applications should have three main components:
- Request
- Endpoint
- Response
Here, a request object comes in from our user. Then, we handle it at the endpoint stage and finally return a response object to our user. With this approach, we simplify the process of receiving requests, processing them, and sending results to our users.
Why Do We Need the REPR Pattern?
Typically, when we develop an API using MVC architecture, we utilize controllers. During the early stages of our API development, these controllers start small, with just the necessary action methods to handle basic operations.
However, at some point, there may be reasons to add more action methods to our controller. This modification can cause our controller to grow significantly and accumulate numerous responsibilities. This often results in controllers that have many dependencies that are sparsely used. In the long run, these large and bloated controllers become a nightmare for us to maintain. For teams with multiple developers working on a particular controller, this may lead to merge conflicts, as changes may not be synchronized.
Moreover, such large and bloated controllers often violate the Single Responsibility Principle (SRP). This happens because these controllers contain numerous methods that handle various services and functionalities, giving them multiple reasons to change.
So, to address these issues, Steve Smith developed the REPR (Request-Endpoint-Response) pattern. With this pattern, our endpoints (previously action methods) are placed in their classes. This makes navigating through our endpoints, handling, and modifying them a safer and cleaner task. Additionally, with this separation of endpoints into individual classes, we now have an application that strictly adheres to the SRP.
Therefore, by implementing the REPR pattern in our .NET applications, our APIs become more modular, maintainable, and scalable. Thus addressing many of the challenges associated with traditional MVC controllers.
How to Implement the REPR Pattern in Our Project
We can manually implement the REPR pattern in our .NET project by first eliminating the controllers in our app. Next, we move all endpoints into separate files. Finally, in each endpoint file, we use MediatR to handle all incoming requests to that endpoint. However, for an app that contains many endpoints, this process can become tedious, and the required command-handling instructions are difficult to implement.
Thankfully, we do not have to do this manually.
There are two popular libraries that we can use to implement the REPR pattern in our apps: the ApiEndpoints library, created by Steve himself, and the FastEndpoints library. In this article, we will utilize the FastEndpoints library since it has more features and is the go-to option recommended by Steve.
To demonstrate how to use the FastEndpoints library to implement the REPR pattern, let’s create a simple API with a single endpoint. This endpoint allows users to enter the details of a book and receive the book’s description as a response.
To create this app using the FastEndpoints library, first, we have to create an empty ASP.NET Core web app:
dotnet new web -n REPRPattern
After that, we need to install the FastEndpoints package to our project:
dotnet add package FastEndpoints
With that, we can now add some FastEndpoints startup configurations to our Program.cs
class:
using FastEndpoints; var builder = WebApplication.CreateBuilder(); builder.Services.AddFastEndpoints(); var app = builder.Build(); app.UseFastEndpoints(); app.Run();
This configuration sets up and runs our app using FastEndpoints.
With this configuration, whenever we execute our app, we first create a new WebApplicationBuilder. After that, we integrate the FastEndpoints library into the ASP.NET Core middleware pipeline. Lastly, we build the application and launch it.
With that, let’s now define the main components of our application: the request, endpoint, and response classes.
Create the Request, Response, and Endpoint Classes
For the request, let’s define a BookEndpointRequest
record:
public record BookEndpointRequest(string? Title, string? AuthorName);
Next, let’s add a BookEndpointResponse
record for the request:
public record BookEndpointResponse(string? Description);
With these request and response models, we can now define our endpoint:
public class BookEndpoint : Endpoint<BookEndpointRequest, BookEndpointResponse> { public override void Configure() { Post("/api/book/create"); AllowAnonymous(); } public override async Task HandleAsync(BookEndpointRequest request, CancellationToken cancellationToken) => await SendAsync(new($"{request.Title} was written by {request.AuthorName}"), cancellation: cancellationToken); }
Here, our endpoint class inherits from the generic Endpoint
base class, which requires both a request and a response type. We provide our custom BookEndpointRequest
and BookEndpointResponse
classes as the required request and response types.
Then, in the Configure()
method, we specify that this endpoint handles HTTP POST requests, defining its route path. After that, we invoke the AllowAnonymous()
method to indicate that we want this endpoint to allow requests without authentication.
Finally, in the HandleAsync()
method, we process the incoming request by using its properties (Title
and AuthorName
) to create a description. Then, we return a BookEndpointResponse
instance with this description to the caller.
With this, our endpoint, or more specifically, our application is now ready to handle requests. Calling this endpoint with the required request parameter from any client will yield a JSON response with the BookEndpointResponse
record structure.
That’s it!
That’s how fast and easy it is to set up a REPR Pattern-based API using the FastEndpoints library.
Kindly note that the FastEndpoints library is a full-fledged library with support for Swagger, unit and integration testing, rate limiting, and other tools required to build a standard API. To learn more about this library, kindly visit their documentation.
Benefits of Using the REPR Pattern
In this section, let’s highlight some of the obvious and not-so-obvious benefits of implementing the REPR pattern in our .NET projects.
Firstly, the REPR pattern improves the organization and maintainability of our code, making it easier for us to find and modify endpoint-related functionalities.
Additionally, by implementing this pattern, we eliminate the possibility of controllers becoming bloated and difficult to maintain.
Furthermore, splitting our endpoints into different files, allows us to adhere strictly to the Single Responsibility Principle (SRP).
Another advantage of using the REPR pattern is that it makes it easier for us to test and debug individual endpoints.
Finally, implementing this pattern improves the performance of our applications. According to the FastEndpoints documentation, FastEndpoints APIs are faster than traditional controller-based APIs and only slightly slower than minimal APIs.
Drawbacks of Using the REPR Pattern
Now, although there are a lot of benefits we stand to achieve from utilizing the REPR pattern, there are still some disadvantages associated with implementing this pattern.
Firstly, using this pattern increases the number of files we need to manage in our application. This can make our project structure more complex and require us to put in more effort to keep things organized.
Additionally, since this pattern is still relatively new, it has a small community of developers and adopters, resulting in limited support and resources for its users. This can make it challenging to find solutions to some specific problems.
Conclusion
In this article, we explored the REPR design pattern and how it can be used to improve the structure of our APIs.
We also examined how to use the FastEndpoints library to implement this pattern in our project. Finally, we considered the potential benefits and drawbacks of using this design pattern.
Consequently, as developers, it is up to us to weigh these advantages and disadvantages before we choose to use this pattern in our API projects.