0

I am facing an issue with a circular dependency reference error in a .net core web app. Whilst I think I understand the issue I can’t think of a work around to the problem I face, I’ve searched around and also don’t seem to find anyone else with the same issue.

This is the logic flow I’m trying to achieve: 1. Generic repository handling db CRUD operations. 2. Within this repository is a service that performs certain task based on the kind of db action taking place.

Example 1 A new record is inserted to the user table, there is a task that triggers a welcome email to the user.

Example 2 A lead is inserted to the database, there is a task that creates follow up task for a system admin.

The reason this is all handled in such a generic way is that I want to provide an interface for app admins to create/update these trigger tasks, hence in example 1 why I don’t just hard code an email to the user.

I’m using DI to resolve various services and the issue I have is as follows:

EntityFrameworkRepository implements IRepository enter image description here

TriggerService constructor enter image description here

ITriggerService is injected into the EntityFrameworkRepository, the error is triggered because I then try to inject and resolve IRepository in the trigger service, even without this injection templateService also attempts to resolve IRepository in its own constructor as well. At the minute I just have the ITempalteService and IEmailerService coded but there will be lots of other ‘trigger actions services’ these will also be used throughout the code and other services so I don’t really want to change the design of these.

I recognise that this is bad design based on all the other people that have asked similar questions and the responses they received, what I can’t figure out is how to resolve/architect the correct solution to achieve the desired goal.

All suggestions welcome!

James
  • 926
  • 1
  • 8
  • 24
  • Why you need IRepository property in TriggerService? – Krishna Mohan Jan 25 '18 at 12:23
  • It reads the trigger actions from the db, the template service also reads templates from the db to use in the email service – James Jan 25 '18 at 12:28
  • I think it is better to invoke the triggerService from Controller/BusinessLayer (as per your solution structure). To use EntityFrameworkRepository in TriggerService, you can DI it in Startup.cs – Krishna Mohan Jan 25 '18 at 12:43
  • Regardless of the circular dependency, which you could solve for example using events (as explained in the linked question): It does not seem right that the *database repository* is responsible for triggering welcome emails or other things. A repository’s only concern should be the database access. If there is an action “create user and trigger welcome email”, then that should rather go into a separate service, introducing another layer. So you would use that “user service” which then created the entity using your repository, and then triggered the welcome email using the trigger service. – poke Jan 25 '18 at 13:20

1 Answers1

0

Generally speaking, a repository should only perform dumb CRUD operations, without any business logic. Of course, sending a mail and creating a follow up task upon insertion of a record are business logic.

Imagine that you needed to allow insertion of a record without sending the welcome mail, because a new use case is introduced, and such use case is unrelated with the original intention. A simple example would be a kind of user that need not receive the notification. In other words, if you hardwire some business logic with a repository operation, you won't be able to detach it when necessary.

The right way to think of your problem is to rephrase it, converting mixed repo + business logic into pure business logic:

  1. A new record is inserted to the user table, there is a task that triggers a welcome email to the user. => A new user is registered, there is a task that triggers a welcome email to the user.
  2. A lead is inserted to the database, there is a task that creates follow up task for a system admin. => A lead is created, there is a task that creates follow up task for a system admin.

Talking about code, my suggestion would be to add an intermediate service that calls the repository to perform the bare CRUD and calls the services providing the desired side-effects.

Considering your 1st example (the 2nd in similar), instead of calling directly the IRepository to insert the user record, inject in the caller a new IUserService, which in turn receives IRepository and IEmailerService in its constructor and calls them. This allows you to inject IRepository into IEmailerService without trouble.

The only thing I can't tell you is whether my suggestion can be applied according to your need to provide a general interface. Anyway, the refactorings needed to apply such change should result minimal, and of course the circular dependency issue (caused by the circularity in the original formulation) is resolved.

FstTesla
  • 844
  • 1
  • 7
  • 10