Search code examples
c#dependency-injectioninversion-of-controlcircular-dependency

Dependency Injection Architectural Design - Service classes circular references


I have the following service classes:

public class JobService {
  private UserService us;

  public JobService (UserService us) {
    this.us = us;
  }

  public void addJob(Job job) {
    // needs to make a call to user service to update some user info
    // similar dependency to the deleteUser method
  }
}

public class UserService {
  private JobService js;
  public UserService(JobService js) {
    this.js = js;
  }

  public void deleteUser(User u) {
    using (TransactionScope scope = new TransactionScope()) {
      List<IJob> jobs = jobService.findAllByUser(u.Id);
      foreach (IJob job in jobs) {
        js.deleteJob(job);
      }
      userDao.delete(user);
      scope.Complete();
    }
  }
}        

Each of these service classes is getting instantiated by IoC container, and there is not a functional problem, but this to me feels like there is a potential design flaw in this approach and I'm wondering if there's an alternative approach that makes more sense.


Solution

  • As someone already pointed out, the problem is not with limitations to the DI container but with your design.

    I see the reason that you have a separate UserService and a JobService which contain a reference to each other. This is because both UserService and JobService contain some logic that needs the other service as a reference (adding a job requires adding a user, etc.). However, I think that you should NOT reference one service from the other. Rather, you should have another layer of abstraction behind the services which the services will use for the common logic. So, the services will contain the logic which can't(shouldn't) be reused and the helpers will contain the shared logic.

    For example:

        public class UserHelper{
          //add all your common methods here
        }
        public class JobService {
          private UserHelper us;
    
          public JobService (UserHelper us) {
            this.us = us;
          }
    
          public void addJob(Job job) {
     // calls helper class
          }
        }
    
        public class UserService {
    
          public UserService(UserHelper js) {
            this.js = js;
          }
    
          public void deleteUser(User u) {
            // calls helper class
          }
        }   
    

    In this way, you won't have any issues with circular references and you will have one place which contains the logic which needs to be reused by different services.

    Also, I prefer having services which are completely isolated from one another.