Dependency Injection Architectural Design - Service classes circular references

后端 未结 4 1898
悲&欢浪女
悲&欢浪女 2021-01-15 18:05

I have the following service classes:

public class JobService {
  private UserService us;

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

  pu         


        
相关标签:
4条回答
  • 2021-01-15 18:33

    This wont work in Autofac. See circular dependencies section of the documentation.

    Constructor/Constructor Dependencies Two types with circular constructor dependencies are not supported. You will get an exception when you try to resolve types registered in this manner.

    You could potentially use relationship types (Func<>, Lazy<>) to break the cycle.

    Your code is a bit too generic to come up with a proper solution but you should consider changing the direction of dependencies regardless of what IoC container you use.

    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
      }
    }
    
    public class UserService {
      private JobService js;
      public UserService(Func<JobService> jsFactory) {
        this.js = jsFactory(this);
      }
    
      public void deleteUser(User u) {
        // needs to call the job service to delete all the user's jobs
      }
    }        
    

    Alternatively, In the case of your example you could move deleteUser and create a method, delete all jobs on the job service and instead of refering to the user use an id. this breaks the dependency by using the id.

    Another alternative is to pass the job service as a parameter to deleteUser.

    0 讨论(0)
  • 2021-01-15 18:38

    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.

    0 讨论(0)
  • 2021-01-15 18:39

    The problem you are having has in fact nothing to do with the limitations of your DI container, but it is a general problem. Even without any container, it will be impossible to create those types:

    var job = new JobService([what goes here???]);
    var user = new UserService(job);
    

    The general answer is therefore to promote one of the dependencies to a property. This will break the dependency cycle:

    var job = new JobService();
    var user = new UserService(job);
    
    // Use property injection
    job.User = user;
    

    Prevent however from using more properties than strictly needed. These dependency cycles should be pretty rare and makes it much harder to either wire your types together, or to validate the DI configuration for correctness. Constructor injection makes this much more easy.

    0 讨论(0)
  • 2021-01-15 18:47

    You can decouple the services by using events. Instead of calling a dependent method of another service when an action has been performed, an event is raised. An integrator can then wire up the services through the events. A service does not even know the existence of the other service.

    public class JobService
    {
        public event Action<User, Job> JobAdded;
    
        public void AddJob(User user, Job job)
        {
            //TODO: Add job.
            // Fire event
            if (JobAdded != null) JobAdded(user, job);
        }
    
        internal void DeleteJobs(int userID)
        {
            //TODO: Delete jobs
        }
    }
    
    public class UserService
    {
        public event Action<User> UserDeleted;
    
        public void DeleteUser(User u)
        {
            //TODO: Delete User.
            // Fire event
            if (UserDeleted != null) UserDeleted(u);
        }
    
        public void UpdateUser(User user, Job job)
        {
            //TODO: Update user
        }
    }
    

    The integrator wires up the services

    public static class Services
    {
        public static JobService JobService { get; private set; }
        public static UserService UserService { get; private set; }
    
        static Services( )
        {
            JobService = new JobService();
            UserService = new UserService();
    
            JobService.JobAdded += JobService_JobAdded;
            UserService.UserDeleted += UserService_UserDeleted;
        }
    
        private static void UserService_UserDeleted(User user)
        {
            JobService.DeleteJobs(user.ID);
        }
    
        private static void JobService_JobAdded(User user, Job job)
        {
            UserService.UpdateUser(user, job);
        }
    }
    

    (Note: I simplified event raising a bit. It's not thread safe like this. But you can assume that the events are subscribed in advance and will not be changed later.)

    0 讨论(0)
提交回复
热议问题