Берем на вооружение паттерн проектирования «request-endpoint-response» (REPR), чтобы упростить разработку API и сделать свой код чище, эффективнее и проще в обслуживании.

Паттерны проектирования были сформированы как решения проблем, которые часто встречаются в программных приложениях. Они представляют собой типовые решения повторяющихся проблем и сложностей при проектировании программного обеспечения. В рамках этого цикла статей мы уже обсудили довольно много паттернов проектирования, среди которых можно выделить паттерн «Спецификация», паттерн «Единица работы», паттерн «Null object», паттерн «Опции», паттерн «Приспособленец» (flyweight), паттерн «Команда», паттерн «Интерпретатор» и паттерн «Синглтон».

В этой статье мы рассмотрим паттерн проектирования REPR (request‑endpoint‑response — «запрос‑конечная_точка‑ответ»), как он упрощает разработку API и как его можно реализовать на C#.

Чтобы вы могли работать с примерами кода, приведенными в этой статье, у вас должна быть установлена Visual Studio 2022. Если у вас еще нет ее, вы можете скачать Visual Studio 2022 здесь.

Что из себя представляет паттерн REPR?

Паттерн проектирования REPR представляет из себя подход, который помогает разработчикам повысить удобство сопровождения, реюзабельность и расширяемость кода за счет разделения ответственности. Он позволяет разработчикам создавать хорошо структурированные и легко расширяемые API, концентрируясь на запросе, конечной точке и ответе.

Паттерн REPR повышает модульность вашего приложения и создает четкое разделение между входным запросом, логикой в конечной точке и выходным ответом. По аналогии с архитектурой вертикальных слоев, паттерн проектирования REPR упрощает разработку API, выстраивая API вокруг конечных точек, а не контроллеров. Важно отметить, что шаблон REPR не является ни REST-ориентированным, ни ресурсо-ориентированным. Он используется для определения конечных точек API.

Зачем нужен паттерн проектирования REPR?

Для создания конечных точек API традиционно используется паттерн MVC (model‑view‑controller). Хотя паттерн MVC имеет ряд преимуществ, одним из основных недостатков этого подхода являются раздутые контроллеры (так называемая «проблема раздувания контроллеров»). Это происходит потому, что контроллеры часто содержат хаотичный набор методов, которые никак не связаны друг с другом (они никогда не вызывают друг друга) и на самом деле вообще не должны быть собраны в одном месте. В результате приложение отходит от REST‑методов, и в итоге получает рыхлую коллекцию методов, доступных через конечные точки HTTP.

Паттерн REPR решает проблему раздувания контроллеров, устраняя необходимость иметь несколько методов действий в одном контроллере. Вместо этого паттерн REPR придерживается принципа единой ответственности, позволяя вам иметь один контроллер для каждого действия, поддерживаемого в стандартном случае использования. Другими ключевыми преимуществами являются разделение ответственности, повышение реюзабельности кода, улучшение читаемости и сопровождаемости, улучшение тестируемости и упрощение отладки, а также повышение безопасности и масштабируемости.

Однако у шаблона REPR есть и определенные недостатки. К ним относятся повышенная сложность и дублирование кода.

Создаем проект ASP.NET Core Web API в Visual Studio 2022

Чтобы создать проект ASP.NET Core 8 Web API в Visual Studio 2022, вам нужно выполнить следующие шаги:

  1. Запустите среду разработки Visual Studio 2022.

  2. Нажмите «Create new project».

  3. В окне «Create new project» из списка отображаемых шаблонов выберите «ASP.NET Core Web API».

  4. Нажмите «Next».

  5. В окне «Configure your new project» укажите имя и местоположение нового проекта. В зависимости от ваших предпочтений установите флажок «Place solution and project in the same directory».

  6. Нажмите «Next».

  7. В окне «Additional Information», которое отобразится далее, выберите в качестве версии фреймворка «.NET 8.0 (Long Term Support)» и убедитесь, что у вас отмечен флажок «Use controllers». В этом проекте мы будем использовать контроллеры.

  8. Также в окне «Additional Information» установите для параметра «Authentication Type» значение «None» (по умолчанию) и убедитесь, что флажки «Enable Open API Support», «Configure for HTTPS» и «Enable Docker» не отмечены. Мы не будем использовать ни одну из этих функций.

  9. Нажмите «Create».

Этот проект ASP.NET Core Web API мы и будем использовать для работы с паттерном проектирования REPR в последующих разделах.

Компоненты шаблона проектирования REPR

Как следует из названия, шаблон проектирования REPR включает в себя три компонента:

  1. Запрос (Request): Представляет собой входные данные, которые ожидает конечная точка. Объекты запроса должны использоваться для проверки ввода и передачи данных между слоями приложения.

  2. Конечная точка (Endpoint): Представляет собой логику, выполняемую конечной точкой для данного запроса.

  3. Ответ (Response): Представляет собой выходные данные, которые возвращает конечная точка.

Создаем объект запроса

Следующий фрагмент кода иллюстрирует типичный класс запроса.

public class CreateUserRequest
{
    public int UserId { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public string Email { get; set; }
}

Аналогично, класс запроса для создания нового продукта будет выглядеть следующим образом:

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; }
}

А класс запроса для получения данных о продукте будет выглядеть следующим образом:

public class GetProductRequest
{
    public int Id { get; set; }
    public string ProductName { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
}

Реализуем логику в конечной точке

Найдите папку Controllers в проекте, который мы создали ранее. Кликните правой кнопкой мыши по этой папке и создайте новый контроллер API под именем CreateProductController. Замените сгенерированный по умолчанию код на следующий:

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)
        {
            //Здесь должен быть ваш код для получения продуктов.
            return NoContent();
        }
        [HttpPost(Name = "CreateProduct")]
        [ProducesResponseType(StatusCodes.Status204NoContent)]
        public ActionResult CreateProductAsync
        (CreateProductRequest createProductRequest)
        {
            //Здесь должен быть ваш код, реализующий такую логику, как,

        	//валидация, отображение и т.д.
            return NoContent();
        }
    }
}

Теперь рассмотрим следующую конечную точку HTTP GET.

https://localhost:4586/api/getproducts

При обращении к этой конечной точке типичный ответ (в формате JSON) будет выглядеть следующим образом:

{
      "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
            }
      ]
}

Создаем объект ответа

И последнее, но не менее важное: следующий фрагмент кода иллюстрирует типичный класс ответа.

public class CreateProductResponse
{
    public int StatusCode { get; set; }
    public string ErrorMessage { get; set; }
}

Следует отметить, что не всем конечным точкам нужны входные данные для классов запроса или ответа. Во многих случаях вам может быть достаточно отправить в ответе только код состояния HTTP.

Примеры использования шаблона REPR

Типичным примером использования паттерна REPR является CQRS (принцип разделения команд и запросов), когда для каждой команды и запроса требуется отдельная конечная точка. Еще один вариант использования паттерна REPR — архитектура вертикальных слоев, когда приложение разделяется на вертикальные слои в зависимости от их ответственности.

При этом вы не ограничены в использовании REPR для построения архитектуры определенного стиля. Вы вполне можете определить RESTful ресурсы и даже конечные точки в стиле RPC, используя паттерн проектирования REPR. Вы можете выбрать этот паттерн, исходя из требований вашего приложения.

Паттерн проектирования REPR помогает улучшить читаемость, сопровождаемость и масштабируемость кодовой базы за счет изоляции различных слоев приложения, таких как пользовательский интерфейс, бизнес-логика и слои доступа к данным. Вы можете модифицировать и расширять шаблон REPR в соответствии с вашими требованиями путем включения дополнительных слоев или компонентов.


В заключение напоминаем про открытый урок, посвященный Generic коллекциям в C#. Вебинар будет особенно полезен для разработчиков, которые уже ознакомлены с основами C# и хотят глубже понять механизмы работы с коллекциями данных, улучшить производительность своих приложений и написать более типобезопасный код. Записаться на урок можно бесплатно на странице курса «C# Developer».

Комментарии (4)


  1. propell-ant
    17.05.2024 15:42
    +3

    Наверно очень интересно готовить такие вот статьи ни о чём...

    "О, дивный новый мир"


    1. ryanl
      17.05.2024 15:42
      +9

      Индусы в большинстве своем хорошего и не пишут, у них большая конкуренция вот и приходится хоть как-то имитировать компетенции и проявлять активность. И да, нет такого паттерна, это все выдумки автора, статья - шлак, непонятно для кого. Нету качественных глубоких материалов, но зато есть напоминалка на гавнокурсы, уж простите.


      1. propell-ant
        17.05.2024 15:42

        Далее идет крик души:

        Это что перевод?!

        Я что, прочитал и написал коммент на перевод статьи о несуществующем паттерне, в которую изначально налили воды с помощью chatgpt?

        И как теперь мне жить с этим?


  1. raspberry-porridge
    17.05.2024 15:42
    +1

    Возможно я что-то не понимаю, но почему в запросах на создание сущности есть свойство идентификатора этой сущности? Присваивать сущности Id должна система хранения, а не внешний агент.

    public class CreateUserRequest
    {
      public int UserId { get; set; }
      ...
    public class CreateProductRequest
    {
      public int Id { get; set; }
      ...

    И зачем в запросе получения сущности так много полей, если мы знаем id? А комбинировать запрос конкретной сущности с поиском сущностей по критериям - такой себе подход.

    public class GetProductRequest
    {
        public int Id { get; set; }
        public string ProductName { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
    }