Search code examples
c#design-patternsarchitectureinversion-of-control

Sideways layer calls design issue


I have a layered application where the UI -> Service -> Logic -> DA. I'm using Inversion of Control and Unity as my IoC container.

A common issue I have and am looking to find out if this is acceptable or how to work around it, is the case below.

enter image description here

In the normal flow of things (red arrows), with an example of Login, the UI calls the UserService class, which calls the Userlogic class, which calls the UserDataAccess class which goes to the DB. Data comes back (maybe) back to the logic, where it's processed, and then a reply goes back up to the UI.

But the blue line - is that bad architecture? What's happening there is that I am registering a new user. UI calls the UserService (_userService.RegisterNewUser), which calls the Logic, which registers the user with the database and replies with the new UserID to the Logic.

If all is good, at that point, I need to create a 'Finance' record for the new user (It's a business requirement). So then I make a call to my _FinanceLogic class (_FinanceLogic.CreateUserFinance), which then goes down to the database and responds back up all the way back to the UserLogic, which then finalises the Registration if all is OK.

What I'm trying to find out is - is the cross layer call (_UserLogic calls _FinanceLogic) acceptable? I need to add a reference to my _UserLogic, for the _FinanceLogic class. Which means my constructor is getting larger and larger.

public UserLogic(ILog logger, IEmail email, IUserData user, IFinancialLogic financialLogic)

Or, should _UserLogic be calling _FinanceDataAccess direct? I suspect not, as there would be logic in the _FinanceLogic.CreateUserFinance which you would not want to bypass.


Solution

  • Yes, you can totally make that sideways call. The blue arrow in question is a sideways call on the Logic layer. Generally it is absolutely okay to have sideways calls as long as you remain on the same layer of abstraction - which you do in this case.

    However, from a design perspective I am not sure whether the call is in the right layer. It is difficult to tell the difference between the Service and Logic layer, but it seems to me that the business logic is encoded in the Service layer. Hence, as you correctly identified the Finance record creation as business requirement, you should trigger that form the service layer (as it presumably contains all the business logic).

    Alternative Design: Event-based Architecture

    You might want to leverage an event-based architecture where you encode the flow of your business logic with events. Typically you name events in past-tense to reflect the fact that they already happened. You can now define events among:

    • UserAccountCreatedEvent: to be emitted as soon as the account is stored in the DB
    • FinanceRecordCreatedEvent: to be emitted as soon as the finance record is stored in the DB.
    • UserRegisteredEvent: to be emitted as soon as all required steps of the registration have completed (namely, the above two events).

    All you need is an event library that allows you to define listeners for the different events. With such a setup it is very easy to implement changes in the future. You could easily add an ConfirmationEmailListener to send a confirmation email for each UserAccountCreatedEvent.