Avinash Kharche
Avinash Kharche

Reputation: 441

How to remove circular dependency between two classes in a same package?

We are doing some refactoring and we have hit the wall here. There are two service classes AService and BService doing different works have a circular dependency. Earlier they were in a different libraries so there was no circular dependency existed. But now after refactoring we have moved them into a single library into a single package. AService job is to persist some data into a NoSQL database for a separate use case. BService job is completely different than AService job. AService needs BService to GET some data. BService needs AService to write some data to NoSQL Database and reads back.

How to fix the circular dependency issue with making each of them depends on each other. Any design pattern for such issues?

class AService {
@Autowired
private BService bService;

}

class BService {
@Autowired
private AService aService;

}

Upvotes: 3

Views: 10523

Answers (5)

Shanta Kumar das
Shanta Kumar das

Reputation: 31

Instead of interacting directly between Service A and Service B, encapsulate the logic for those both of them dependent to each other. Create a third component like a mediator (updateService, responsible for reading and writing to db through A, get information from B) Though there might be several techniques to resolve circular dependency like lazy initialization or implementing event driven pattern, the mediator strategy is decoupled and easier to trace dependencies.

Upvotes: 1

BionicCode
BionicCode

Reputation: 29008

Solution 1 (recommended): the redesign of class responsibilities. Adhering to the Single Responsibility Principle and according to the class details you revealed, we can fix your class design by extracting at least one new class: the DatabaseConnector. This class encapsulates all database related operations (CRUD) and therefore lifts the circular dependency of the service classes (without changing the original conceptions of the classes).

// This is just a raw template to express the intention
class DatabaseConnector {
  void createInDatabase(Object data) {
  }

  Object readFromDatabase(Object args) {
  }

  void updateInDatabase(Object data) {
  }

  void deleteFromDatabase(Object data) {
  }
}

class AService {
  @Autowired
  private DatabaseConnector dbConnector;    
}

class BService {
  @Autowired
  private DatabaseConnector dbConnector;    
}

You can add more specialized methods to the DatabaseConnector to meet special requirements (e.g. readName(), readId(), etc).

Since it is likely that more classes will need to access the database in future, you already solved or prevented new circular dependencies today. Encapsulation solved potentially upcoming problems.

Solution 2: dependency inversion

interface IAService {    
}

interface BService {
}

class AService implements IAService {
  @Autowired
  private IBService bService;    
}

class BService implements IBService {
  @Autowired
  private IAService aService;    
}

A circular dependency is always an indicator for bad class design. In most cases the origin is the violation of the Single Responsibility Principle (the S in SOLID). A wrong composition concept can also lead to this design error. What will always help, but not fix the conceptional flaws of class responsibilities, is the introduction of interfaces to invert all dependencies (the D in SOLID). Taking the SOLID Principles serious can safe a lot of time and work and will always lead to better code (although you introduced higher code complexity).

The Mediator Pattern can also help to lift circular dependencies by encapsulating the bidirectional interaction of two or more objects.

The downside of your current code is (besides the circular dependency), that whenever class A changes and also data persistence changes, you have to touch and modify class B. This changes can break class B which is using the same persistence operations. This is true for all cases where one class has shared responsibilities with another class. If there wasn't the shared code, both classes wouldn't know each other at all. In your special case, where the dependency is cyclic, you add this flaw to the other dependency direction as well: whenever B needs to adjust or extend how to read data then you have to modify class A, which might break A. If you are using unit tests, then you would have to refactor the tests of both classes too. This tight (and cyclic) coupling of A and B will lead to errors or bugs. Extending code has become dangerous. But the good news is that circular dependencies never compile (since the dependency resolution leads to infinite recursion).

Upvotes: 2

Chris Toh
Chris Toh

Reputation: 88

The easiest way is to move the method that Service B uses out of Service A and into Service B OR into a completely different class.

The design indicates that you probably have too many methods in Service A which Service A does not actually use, but which are public methods that should really be static and in another Class entirely.

Upvotes: 0

pdem
pdem

Reputation: 4077

The real question is why your conception making A and B dependent of each over?

  • It could be that A and B represent the same concept, and may be merged.
  • It could also be that a part of your service A or B should be extracted in a new service C.

In that last case you need to know which method depends on which service, and which one could be extracted in a new service. Without having the context and the list of methods it's difficult to propose a solution. In the case of indirect recursive methods, you may have to cut also some methods.

Side note: don't try to find a workaround to make it work with circular dependencies, it shows a conception problem, you have to do the refactoring.

Upvotes: 6

Guillaume
Guillaume

Reputation: 1

Try to put the autowired on setter instead:

@Autowired
public void setServiceB(ServiceB service){
 this.serviceB = service;
 }

Upvotes: -3

Related Questions