Луковая архитектура - вызов внешних API

Луковая архитектура - вызов внешних API
Луковая архитектура - вызов внешних API

Зонтичный вопрос

В луковичной архитектуре, где находятся все компоненты, необходимые для использования внешнего API?

Ниже приведена архитектура, которая у меня есть на данный момент, основанная на примере, найденном здесь: https://github.com/CodeMazeBlog/onion-architecture-aspnetcore/

Требования

Сейчас я пытаюсь протестировать эту архитектуру, написав код для выполнения этих требований:

  • Вызвать Microsoft Graph. Получить текущий список команд в определенном арендаторе.
  • Для каждой команды, которую я получаю, сохранить некоторые поля в локальной базе данных. У нас также есть другие пользовательские поля, которые мы хотим сохранить для каждой команды.
  • Сделать последние данные, которые мы сохранили в базе данных, доступными в виде "отчета".

Например, вот что получается из Graph:

  "value": [
    {
      "reportRefreshDate": "2021-09-01", 
      "teamName": "sampleTeam",
      "teamId": "a063d832-ae9a-467d-8cb4-17c073260890",
      "teamType": "Private",
      "lastActivityDate": "2021-09-01",
      "details": [
        {
          "reportPeriod":7,
          "activeUsers": 26, 
          "activeChannels": 17, 
          "guests": 4, 
          "reactions": 36, 
          "meetingsOrganized": 0,
          "postMessages": 0,
          "replyMessages": 0,
          "channelMessages": 0,
          "urgentMessages": 0,
          "mentions": 0,
          "activeSharedChannels": "6",
          "activeExternalUsers": "8"
        }
      ]
    }
  ]

А это поля, которые я хочу сохранить:

  "LastSynchronizedDate": "2021-09-01", 
  "teamName": "sampleTeam",
  "teamId": "a063d832-ae9a-467d-8cb4-17c073260890",
  "teamType": "Private",
  "lastActivityDate": "2021-09-01",
  "details": [
    {
      "activeUsers": 26, 
      "activeChannels": 17, 
      "guests": 4,      
      "activeSharedChannels": "6",
      "activeExternalUsers": "8",
      "mycustomfield1": "8",
      "mycustomfield2": "somestringvalue"
    }

Компоненты

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

Я думаю создать следующие файлы

  • модель базы данных и методы, содержащие мое определение того, что такое команда. Здесь будут содержаться методы с бизнес-логикой для манипулирования моей версией команды. "Domain->Entities->MyTeam.cs"

  • модель того, что MS Graph возвращает в отчет. Насколько я понимаю, DTO - это в основном модели db. Так что в моем случае это будет представлять то, как будут выглядеть данные из Graph. Может ли эта "модель" также включать методы для манипулирования json данными из Graph? Или только модель самой записи? "Contracts->GraphTeamModelDTO.cs"

  • модель моей местной команды.
    "Contracts -> MyTeamDto.cs"

  • Интерфейс к таблице команды моей локальной базы данных "Domain->Repositories-> IMyTeamRepository.cs"

    public interface IO365TeamRepository { Task<IEnumerable> GetAllAsync(CancellationToken cancellationToken = default);

  • новый внутренний герметичный класс, который позволит чему-то вызывать "synchronize teams data" для запуска вызова graph. Этот класс будет использовать DTO.

    "Службы >TeamsManagementService.cs**".

     public Task<IEnumerable<TeamFromGraphDto>> SynchronizeTeamsForTenant(int tenantId, CancellationToken cancellationToken = default)
      {
    
    
      }
    
  • соответствующий интерфейс публичной службы, на который будет опираться презентационный уровень для запуска всего процесса /report "Services.Abstractions ->ITeamsManagementService.cs**

  • Конкретный класс, реализующий методы базы данных для получения таблицы моих локальных команд "Infrastructure -> Persistence -> Repository -> MyTeamRepository.cs"

  • контроллер, который раскрывает API, чтобы клиенты могли вызывать мой сервис. Презентация -> Контроллер -> TeamManagement.cs

    [ApiController] [Route("api/beta/reporting/teams/{tenantId:int}")] public class TeamsManagement: ControllerBase { private readonly IServiceManager _serviceManager;

      public TeamsManagement(IServiceManager serviceManager) => _serviceManager = serviceManager;
    
      [HttpGet]
      public async Task<IActionResult> SynchronizeTeams(int tenantId, CancellationToken cancellationToken)
      {
          var teams = await _serviceManager.TeamManagementService.SynchronizeTeamsForTenant(tenantId, cancellationToken);
    
          return Ok(teams);
      }
    

Чего не хватает

Я не знаю, куда именно поместить следующие объекты:

  • конкретный класс, который создает клиент ms graph, используя их библиотеку. Я думаю, это должно быть что-то вроде: "Infrastructure -> ExternalAPIs -> Graph ->MyGraphServiceClient.cs"

  • связанный интерфейс, который, как я думаю, будет потребляться моим сервисом TeamsManagement: "Service.Abstractions -> IMyGraphServiceClient.cs"

Звучит ли это разумно / правильно? Я нахожусь в середине написания фактической логики, чтобы проверить это... Но я подумал, что мне стоит спросить мнение сообщества, пока я не продвинулся слишком далеко - на случай, если я сбился с пути.

Зонтичный вопрос
Где в луковой архитектуре помещаются все компоненты, необходимые для использования внешнего API?

Если он звонит вам, это часть уровня API.
Если вы это называете, это часть уровня инфраструктуры.

Мне не нравится ваша текущая структура проекта, так как я гораздо более склонен разделять слои на отдельные проекты. Тем не менее, это не делает вопрос недействительным, поэтому я оставлю его в покое.

конкретный класс, который создает клиент графа ms, используя свою библиотеку. Я думаю, что это должно быть что-то вроде: «Инфраструктура -> Внешние API -> График -> MyGraphServiceClient.cs»

Я согласен с битом Infrastructure ->. Все остальное очень контекстуально и не может разумно ответить незнакомцу из Интернета.

Это зависит от размера и сложности вашей кодовой базы. Возможно, это единственный элемент инфраструктуры и подразделение не имеет значения. Может быть, это можно отсортировать в папку проекта. Возможно, это гарантирует наличие отдельного проекта, например. Infrastructure.Graphs. Все это возможно (и даже больше).

связанный интерфейс, который, я думаю, будет использоваться моей службой TeamsManagement: «Service.Abstractions -> IMyGraphServiceClient.cs»

Я предполагаю, что Service.Abstractions — это ваш проект интерфейсов. Если да, то я согласен.

Однако будьте очень осторожны с самим интерфейсом, убедитесь, что он не привязан к конкретной технологии (например, Microsoft Graphs). Убедитесь, что интерфейс не зависит от технологии как по имени, так и по поведению (и связанным структурам данных), если, например, ваше приложение неразрывно связано с этой технологией (например, если вы создаете расширение Microsoft Graphs). Приведенный ниже совет предполагает, что вы по своей сути не привязаны к этой технологии. Если да, совет не обязательно применим.

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

Если вы новичок в этом, я бы предложил следующий порядок операций:

  • Сначала напишите конкретный класс. Создайте свой код, чтобы он работал так, как вы хотите. Пока не беспокойтесь об интерфейсах. Если вы хотите запустить свой код, либо используйте тестовый проект, либо подключите свою инфраструктуру к какому-либо потребляющему приложению (например, консольному приложению).
  • Как только код заработает, отрефакторьте его, чтобы он был настолько чистым, насколько вам нужно/хотите.
  • Создайте интерфейс на основе того, с чем вашему потребляющему приложению/тестовому проекту необходимо взаимодействовать, чтобы выполнить необходимую вам логику.
  • Оцените этот интерфейс. Показывает ли это, что вы используете Microsoft Graphs? Это могут быть имена методов, используемые классы DTO, имена свойств этих классов DTO,...
    • Единственным исключением являются параметры конфигурации, необходимые для настройки клиента, например. строка подключения или определенные конфигурации Graphs.
    • Для каждой обнаруженной вами вещи, которая раскрывает технологию, напишите зеркальный элемент, который не раскрывает технологию. Думайте об этом так, будто вы пытаетесь скрыть эту информацию от посторонних глаз.
    • При необходимости реорганизуйте логику своей инфраструктуры и добавьте к ней сопоставления, чтобы она преобразовывала элементы, зависящие от технологии, в только что созданные элементы, не зависящие от технологии.

И это должно быть так.

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


LetsCodeIt, 18 января 2023 г., 15:32