I am working in a Web Api project that has the following project structure:
Root Controllers Orchestrators Managers Services etcTheControllers depend onOrchestrators.
TheOrchestrators depend onManagers.
TheManagers depend onServices. TheServices are making calls to external dependencies e.g., third party APIs or data stores.
So for every flow, an incoming HTTP request follows this path:
Controller <--> Orchestrator <--> Manager <--> ServiceSample code:
AccountsController.cs
public class AccountsController{ private readonly AccountsOrchestrator accountsOrchestrator; public AccountsController(AccountsOrchestrator accountsOrchestrator) { this.accountsOrchestrator = accountsOrchestrator; } [HttpGet("GetAllAccounts")] public async Task<ActionResult<IEnumerable<string>>> GetAllAccounts(string input) { var accounts = accountsOrchestrator.GetAllAccounts(input); return new OkObjectResult(accounts); } [HttpGet("GetAccountById")] public async Task<ActionResult<IEnumerable<string>>> GetAccountById(string input) { var account = accountsOrchestrator.GetAccountById(input); return new OkObjectResult(account); }}AccountsOrchestrator.cs
public class AccountsOrchestrator{ private readonly AccountsManager accountsManager; public AccountsOrchestrator(AccountsManager accountsManager) { this.accountsManager = accountsManager; } public async Task<IEnumerable<string>> GetAllAccounts(string input) { // Validate input if (string.IsNullOrEmpty(input)) { return Enumerable.Empty<string>(); } var accounts = await accountsManager.GetAllAccountsFromExternal(input); return accounts; }}AccountsManager.cs
public class AccountsManager{ private readonly ExternalDependency externalDependency; public async Task<IEnumerable<string>> GetAllAccountsFromExternal(string input) { // Validate input if (string.IsNullOrEmpty(input)) { return Enumerable.Empty<string>(); } var accounts = await externalDependency.CallExternalService(); return accounts; }}ExternalDependency.cs
public class ExternalDependency{ public async Task<IEnumerable<string>> CallExternalService() { await Task.Delay(500); }}There are more than a dozen Controllers which leads to more than a dozen Orchestrators and so on and so forth.
- Is it good enough?
- Is there a better approach for that?
- Is there a pattern that specifically targets these scenarios?
1 Answer1
Honestly, I'd advise against this.
Your approach seems to me a naïve view of how things work. Are there methods that just get data from a DB and return it? Sure. But a lot of the time business logic can be complicated and involve numerous database calls plus calls to external APIs etc. In such cases your structure quickly falls apart and will become an unmaintainable mess.
I see this far too often: people who have convinced them that "this kind of logic needs to be in a class named X to indicate that it is external" etc. That isn't maintainable. Nobody cares about that. (Instead, consider that an API which is used regularly is probably best encapsulated in a service which is then injected into wherever it is needed, and then the right method get called to retrieve the data you need or to post the data you need to send etc.)
My go-to approach isMediatR, where a method in a controller passes the incoming data to a specific Handler, which contains the business logic and often plenty of that logic is in specific classes in order not to have the Handler become unwieldy.
But YMMV, and there are other approaches equally valid (e.g.CQRS).
You mustlog in to answer this question.
Explore related questions
See similar questions with these tags.
