Design patterns have evolved to address problems that are often encountered in software applications. They are solutions to recurring problems and complexities in software design. We’ve discussed many design patterns here including the specification pattern, the unit of work pattern, the null object pattern, the options pattern, the flyweight pattern, the command pattern, the interpreter pattern, and the singleton pattern.
In this post we will delve into the REPR (request-endpoint-response) design pattern, how it simplifies the development of APIs, and how it can be implemented in C#.
To use the code examples provided in this article, you should have Visual Studio 2022 installed in your system. If you don’t already have a copy, you can download Visual Studio 2022 here.
What is the REPR design pattern?
The REPR design pattern is an approach that helps developers enhance code maintainability, reusability, and extensibility by isolating concerns. Developers may create well-structured and easily expandable APIs by concentrating on the request, endpoint, and response.
The REPR design pattern promotes modularization and guarantees a distinct separation between the input request, the logic at the endpoint, and the output response. Analogous to the vertical slice architecture, the REPR design pattern simplifies API development by organizing your APIs around endpoints instead of controllers. Remember that the REPR design pattern is neither REST-based nor resource-based. Instead, it is a pattern used for defining your API endpoints.
Why use the REPR design pattern?
The MVC (model-view-controller) pattern has traditionally been used to build API endpoints. While the MVC pattern does have several benefits, one of the major constraints of this approach is bloated controllers, aka the “swollen controller problem.” This is because you often end up with controllers having disparate methods that are not cohesive (they never call each other) and don’t really belong together. As a result, the application drifts away from REST-based methods and ends up with a loose collection of methods exposed over HTTP endpoints.
The REPR pattern solves the problem of controller bloat by eliminating the need to have multiple action methods in one controller. Instead, the REPR pattern adheres to the single responsibility principle, allowing you to have one controller per action supported in a typical use case. Other key benefits include the separation of concerns, improved code reusability, improved readability and maintainability, better testability and simpler debugging, and improved security and scalability.
However, the REPR pattern also has certain downsides. These include increased complexity and duplication of code.
Create an ASP.NET Core Web API project in Visual Studio 2022
To create an ASP.NET Core 8 Web API project in Visual Studio 2022, follow the steps outlined below.
- Launch the Visual Studio 2022 IDE.
- Click on “Create new project.”
- In the “Create new project” window, select “ASP.NET Core Web API” from the list of templates displayed.
- Click Next.
- In the “Configure your new project” window, specify the name and location for the new project. Optionally check the “Place solution and project in the same directory” check box, depending on your preferences.
- Click Next.
- In the “Additional Information” window shown next, select “.NET 8.0 (Long Term Support)” as the framework version and ensure that the “Use controllers” box is checked. We will be using controllers in this project.
- Elsewhere in the “Additional Information” window, leave the “Authentication Type” set to “None” (the default) and make sure the check boxes “Enable Open API Support,” “Configure for HTTPS,” and “Enable Docker” remain unchecked. We won’t be using any of those features here.
- Click Create.
We’ll use this ASP.NET Core Web API project to work with the REPR design pattern in the sections below.
Components of the REPR design pattern
As the name suggests, the REPR design pattern includes three components:
- Request: This represents the input data that the endpoint expects. These request objects should be used for input validation and passing data between the layers of the application.
- Endpoint: This represents the logic executed by the endpoint for a given request.
- Response: This represents the output data that the endpoint returns.
Create a request object
The following code snippet illustrates a typical request class.
public class CreateUserRequest { public int UserId { get; set; } public string Username { get; set; } public string Password { get; set; } public string Email { get; set; } }
Similarly, a request class for creating a new product would look like this:
public class CreateProductRequest { public int Id { get; set; } public string ProductName { get; set; } public string Category { get; set; } public string Description { get; set; } public decimal Quantity { get; set; } public decimal Price { get; set; } }
And a request class for retrieving product data would look like this:
public class GetProductRequest { public int Id { get; set; } public string ProductName { get; set; } public string Description { get; set; } public decimal Price { get; set; } }
Implement the logic at the endpoint
Refer to the Controllers folder in the project you created earlier. Right-click on the Controllers folder and create a new API controller named CreateProductController. Replace the default generated code with the following code.
using Microsoft.AspNetCore.Mvc; namespace REPR_Example.ProductAPI.Endpoints.Product.CreateProduct { [Route("api/[controller]")] [ApiController] public class CreateProductController : ControllerBase { [HttpPost(Name = "GetProducts")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult GetProductsAsync( GetProductRequest getProductRequest) { //Write your code here to retrieve products. return NoContent(); } [HttpPost(Name = "CreateProduct")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult CreateProductAsync (CreateProductRequest createProductRequest) { //Write your code here to implement logic such as, //validation, mapping etc. return NoContent(); } } }
Now, consider the following HTTP GET endpoint.
https://localhost:4586/api/getproducts
When this endpoint is hit, the typical response in JSON will look like this:
{ "products": [ { "id": 1, "name": "HP ZBook Laptop", "description": " i9 Laptop with 32 GB RAM and 1 TB SSD", "price": 2500 }, { "id": 2, "name": "Lenovo Thinkpad Laptop", "description": "i9 Laptop with 16 GB RAM and 1 TB SSD", "price": 1500 } ] }
Create the response object
Last but not least, the following code snippet illustrates a typical response class.
public class CreateProductResponse { public int StatusCode { get; set; } public string ErrorMessage { get; set; } }
It should be noted here that not all endpoints will need input data for the request or response classes. In many cases, you may need only to send the HTTP status code in the response.
REPR pattern use cases
A typical use case of the REPR pattern is CQRS (command and query responsibility segregation), where you need to have a separate endpoint for each command and query. The vertical slice architecture is yet another use case of the REPR pattern, where the application is split into vertical layers depending on their responsibilities.
That said, you are not constrained to use REPR to build a particular style of architecture. You could define RESTful resources and even RPC-style endpoints using the REPR design pattern. You can decide to opt for this pattern based on your application’s requirements.
The REPR design pattern helps improve the readability, maintainability, and scalability of your code base by isolating the various layers of your application, such as the user interface, the business logic, and the data access layers. You may modify and expand the REPR pattern according to your requirements by incorporating additional layers or components.
Copyright © 2024 IDG Communications, Inc.