0

So I've got some complex business logic that is backing an ASP.NET Core API, that is starting to get unwieldly due to the size and I was hoping I could get some advice on better approaches. An example request might have a route of /accounts/{accountId:guid}/approve, and when that endpoint is called we need to do the following operations:

  • Get the account from a Cosmos DB
    • Example account entity: {"id": "<SOME_GUID>", "accountNumber": "C12345", "status": "Pending"}
  • Perform checks on the account to ensure it is valid to be approved
  • Perform checks against other entities in the Cosmos DB
    • E.g. No other account is approved with an accountNumber matching this account
  • Update the account in the Cosmos DB
  • Send events to the Azure Event Grid informing of approval
  • Send email notifications
    • Done via Azure Event Grid, but that's more of an implementation detail
  • Update the audit records for the account in the Cosmos DB
    • I know Cosmos DB has some auditing functionality, but it doesn't suit our purposes and thus we manage a second set of entities for auditing.

Now obviously some of those steps need to be done before others (e.g. you can't run checks on the account before getting it from the Cosmos DB), and we want to exit out early indicating the error if one of the steps fails (e.g. if updating the account in the Cosmos DB fails we don't want to update the audit records).

Currently we have a system I architectured that uses a series of operations to perform the steps sequentially exiting out early if there are any errors. There is good use of generics to allow very common steps such as getting an entity by it's ID to be placed into the sequence of operations without any other implementation. This system works alright, but even with the work I've done implementing even similar endpoints (think /accounts/foo/{accountId:guid}/approve and /accounts/bar/{account:Id:guid}/approve isn't as fast as we'd like, and every change to business rules requires changes to code.

I've looked online and struggled to find a system that fits with our requirements. Essentially we need a system for implementing the following requirements for our business logic:

  • Run rules sequentially
    • A bonus would be the ability to run some rules in parallel where it makes sense
  • Run rules for which the state changes throughout the rules
  • Exit out of rules early if an error state occurs
  • I'm sorry if this is too basic, but do you use something like a `Service` for managing accounts? Not sure what particular business logic changes you are struggling with, but it appears that all requests are centered around user accounts. So having a common library isolated from the API is the most important step, I think. Then you can create routines for common repeating tasks and reuse them frequently. In .NET Core *dependencies injection* should be your friend for creating such services. Everything else is just software engineering. – Isolin May 22 '21 at 23:13
  • So we have backing services, which use the operations architecture I mentioned above for all endpoints (all handled through DI). The accounts endpoint here is just one example of the endpoints our API managers (and actually isn't an exact example from our code to hide the specifics of what we're doing). The issue is that if we have approval for `foo` accounts and want to add in approval for `bar` accounts, there are slight variations on the business logic for each which makes reusing a single service for both tricky and in some cases infeasible, and also requires a significant code change. – Jake Conkerton-Darby May 22 '21 at 23:56
  • Our architecture currently works alright, but it's all stuff I've had to implement from scratch, and I'm not certain it scales well, so was hoping for some recommendations for other systems/solutions. – Jake Conkerton-Darby May 22 '21 at 23:58
  • Sounds like you're looking for a rules engine pattern. But in any case, you should have more generic endpoints because nobody wants to have to add unlimited amounts of /approve routes and methods when the business logic expands. And business rules are policy, and policy is metadata. It's best to have a single approve controller that the caller acts on, rather than having an approve method in every controller. - At least in my experience the first is definitely a lot better manageable. – Dennis VW May 23 '21 at 00:33
  • Your questions sounds too broad. If you can focus on one specific (maybe the most critical) aspect of your flow, you'd be able to show some code, and we would be able to suggest something – Rodrigo Rodrigues May 23 '21 at 02:50

0 Answers0