2

Umbrella Question

In an onion architecture, where do all the components needed to consume an external API fit in?

The following is the architecture I have so far, based off an example found here: https://github.com/CodeMazeBlog/onion-architecture-aspnetcore/

enter image description here

Requirements

I'm now trying to test out this architecture by writing code to fulfill these requirements:

  • Call Microsoft Graph. get the current list of teams in a specific tenant.
  • For each team i get back, save some of the fields to a local database. We also have other custom fields that we want to save for each team.
  • Make the latest data we saved in the database available as a "report".

For example, here's what you get from 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"
        }
      ]
    }
  ]

And these are the fields I want to save:

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

Components

Using the structure shown in the screen shot can you help me vet my design as far as where I'm going to put everything? Assuming the folder structure is fixed, I'd like to make sure that I'm populating each folder with the correct type of logic.

I'm thinking of creating the following files

  • database model and methods that contains my definition of what a team is. This will contain methods with business logic to manipulate my version of a team. "Domain->Entities->MyTeam.cs"

  • a model of what MS Graph returns in the report. My understanding is that DTOs are basically db models. So in my case, this would represents what the data will look from Graph. Can this 'model' also include methods to manipulate the json data from Graph? or just the model of record itself? "Contracts->GraphTeamModelDTO.cs"

  • a model of my local team.
    "Contracts -> MyTeamDto.cs"

  • An interface to my local database's team's table "Domain->Repositories-> IMyTeamRepository.cs"

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

  • a new internal sealed class that will allow something to call "synchronize teams data" to trigger the call to graph. This class is what will use the DTO.

    "Services >TeamsManagementService.cs**

     public Task<IEnumerable<TeamFromGraphDto>> SynchronizeTeamsForTenant(int tenantId, CancellationToken cancellationToken = default)
      {
    
    
      }
    
  • a corresponding public service interface that the presentation layer will rely on to trigger this entire process /report "Services.Abstractions ->ITeamsManagementService.cs**

  • Concrete class that implements the database methods to get at my local teams table "Infrastructure -> Persistence -> Repository -> MyTeamRepository.cs"

  • a controller that exposes an API so clients can call my service. Presentation -> Controller -> 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);
      }
    

What's Missing

I don't know exactly where to put the following objects:

  • concrete class that creates a ms graph client using their library. I think it should be something like: "Infrastructure -> ExternalAPIs -> Graph ->MyGraphServiceClient.cs"

  • the related interface that I think will be consumed by my TeamsManagement service: "Service.Abstractions -> IMyGraphServiceClient.cs"

Does this sound reasonable / correct? I'm in the middle of writing the actual logic to test this out... But i thought i'd ask for community input as well before I get too far along - in case I've strayed.

1 Answer 1

4

Umbrella Question
In an onion architecture, where do all the components needed to consume an external API fit in?

If it calls you, it's part of the API layer.
If you call it, it's part of the Infrastructure layer.

I'm not a fan of your current project structure, as I'm much more inclined to separate the layers into projects of their own. That being said, it doesn't invalidate the question at hand so I'll leave it be.

concrete class that creates a ms graph client using their library. I think it should be something like: "Infrastructure -> ExternalAPIs -> Graph ->MyGraphServiceClient.cs"

I agree with the Infrastructure -> bit. Everything else is highly contextual and can't reasonably be answered by an internet stranger.

This depends on the size and complexity of your codebase. Maybe this is the only infrastructure element and subdivision is irrelevant. Maybe this can be sorted into a project folder. Maybe it warrants having a separate project, e.g. Infrastructure.Graphs. All of these are possible (and more).

the related interface that I think will be consumed by my TeamsManagement service: "Service.Abstractions -> IMyGraphServiceClient.cs"

I assume Service.Abstractions is your interfaces project. If so, I agree.

However, be very careful about the interface itself, make sure it's not tied to the specific technology (i.e. Microsoft Graphs). Make sure that the interface is technology-agnostic in both name and behavior (and related data structures), unless e.g. your application is inherently tied to that technology (e.g. if you're building a Microsoft Graphs extension). The below advice assumes that you're not inherently tied to that technology. If you are, the advice doesn't necessarily apply.

At its very core, any code that is related to the technology belongs inside of Infrastructure, and should not be visible to any other part of your codebase.
In order to retrieve the data, your technology-specific concrete code is going to need to implement a technology-agnostic interface. This interface is visible to the other parts of your codebase, because it is the sanitized way to access the logic.

If you're new to this, I would suggest the following order of operations:

  • First, write the concrete class. Build your code so it works the way you want it to. Don't worry about interfaces yet. If you want to run your code, either use a test project or hook up your Infrastructure to some kind of consuming application (e.g. console application).
  • Once the code works, refactor it so it is as clean as you need/want it to be.
  • Create the interface based on what your consuming application/test project needed to interact with in order to execute the logic that you need.
  • Evaluate this interface. Does it reveal that you're using Microsoft Graphs? This could be in the method names, DTO classes used, property names of those DTO classes, ...
    • The only exception to this are configuration settings that are needed to set up the client, e.g. a connection string or certain Graphs configs.
    • For every single thing you find that reveals the technology, write a mirror element that doesn't reveal the technology. Think of it like you're trying to hide this information from prying eyes.
    • If needed, refactor your infrastructure logic and add mappings to it, so that it converts your technology-specific elements into the technology-agnostic ones you just created.

And that should be it.

Some people prefer working the other way around, by first defining the technology-agnostic interface and only then figuring out the concrete logic to implement it. I prefer this approach too, but I think this requires some familiarity with the process to begin with. For a newcomer, I would recommend the steps I bullet pointed above.

1
  • this is awesome. It gives me a place to start. And yes, ultimately, each folder i describe above will be converted to a c# library project.
    – dot
    Commented Dec 4, 2022 at 23:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.