Dependencies amongst the modules are one of the biggest challenges of distributed architectures. Microservices-based applications are distributed in nature – you might often have to tackle dependency challenges, i.e., dependency issues and concerns across different services.
Since monolithic applications follow a layer model, you would come across fewer cyclic dependencies. In a monolithic application dependency is created between the modules of the application when you import dependency from one part of the application to another; for example, you provide the necessary dependency to a module so that it can execute successfully.
On the flip-side, microservices-based applications follow a graph model due to which chances of cyclic dependencies are more frequent. A typical microservices architecture would have dependencies among components and services.
These services can be modeled as independent, isolated units but they still need to communicate with each other for data and information exchange. Ideally, there should not be any cyclic dependency in a typical microservices-based application since a microservice should not call another microservice directly – a microservice should be called through an event.
However, you might need collaboration, cooperation, and integration of the parts in your microservices-based application and hence the dependency problem would surface.
Read: Difficulties of Monolithic Architecture and Microservices
Problems with Modularity in Software Development
Modularity refers to the ability of an application to be broken down into separate, replaceable components that operate together to create an integrated whole, each of which can perform a particular business function. Modularity helps increase reusability and maintenance and facilitates low coupling and high cohesion.
Despite the benefits it offers, one of the major downsides of modular design is dependency problems. Dependencies are a consequence of modularity – dependencies exist between modules because of the modular design of the applications.
What is Cyclic Dependency and Why Is It Bad?
Circular dependency is described as a connection between two or more modules of an application in which the modules are dependent on one another directly or indirectly and are therefore mutually recursive in nature.
Modules that exhibit circular dependency are said to be mutually exclusive. When you have cyclic dependencies in a microservices-based application your services wouldn’t be scalable, and you wouldn’t be able to deploy the services independently. When you’ve cyclic dependencies between the modules of an application they are said to violate the Acyclic Dependencies Principle as well as the Ordering Principle.
When the abstractions exhibit cyclic dependencies between one another, you’d have to change, test, and reuse them together. Cyclic dependencies make the code difficult to understand, change and maintain over time due to which your application’s code becomes error prone as well.
When there are circular dependencies between the components of an application, the components are difficult to test as well. A change in one module of an application that exhibits cyclic dependencies between the modules might cause a ripple effect on other modules. Reusing individual modules that are tightly coupled is extremely difficult. Excessive inter-module dependencies in an application are an indicator of poor software design primarily because such systems are difficult to understand, work and maintain over time.
Read: Serverless Functions versus Microservices
Breaking the dependency cycles
To reduce or eliminate such dependencies your architecture must take advantage of loose coupling and problem locality. While the former is an approach to designing systems such that the components depend on one another to the least extent possible, the latter states that related problems should be grouped together. There are several ways to break dependency cycles – given below is a list of strategies that can be followed to break the dependency chains.
- Strategy 1: You can take advantage of an interface to break the dependency chain. To do this you can introduce the interface for one of the abstractions that have been used in the dependency cycle.
- Strategy 2: You can remove the unnecessary dependencies by refactoring the source code of the application.
- Strategy 3: This is yet another approach in which you can move the source code that introduces cyclic dependency to a different abstraction.
- Strategy 4: If the abstractions that are involved in the cycle are a semantically single object, you can merge the abstractions into a single abstraction.
Let’s understand strategy #1 in more detail. Consider two classes X and Y that are dependent on one another – hence forming a dependency cycle. So, class X depends on class Y and class Y also depends on class X. We know the problem – how do you break this dependency?
To solve this dependency problem, extract the functionality of class X into an interface I. Now let class Y depend on interface I and class X extend class Y and implement interface I. This would break the circular dependency. You can also have class Y extending class X and implementing interface I to break the circular dependency.
Dependency Square Matrix
A Dependency Square Matrix (DSM) provides a simple and concise approach to depict the dependencies between the modules of an application. It is an adjacency matrix that is essentially a visual representation of the dependencies in an application. Dependency Matrix can help you in decision making in test cases and detecting data related failures quickly. In the DSM, the rows and columns are used to represent tasks.
The tasks have dependencies on one another since data or information or objects must flow from one task to another. A cell is either left blank or contains a mark. If the cell doesn’t have a mark, it implies that the task doesn’t have any dependency. On the contrary, of a cell contains a mark it implies that there exists a dependency between a task in the column and a task in the row. You can apply the dependency matrix at the module level as well. In this case the dependency matrix would depict the dependencies between various modules pf an application.
Tools to Detect Cyclic Dependencies
There are several tools to detect cyclic dependencies in an application and Lattix Architect is one of them. Lattix Architect is a nice tool to detect architecture issues quickly. You can take advantage of this tool to get a visual representation of the application’s architecture together with its dependencies – the dependencies are represented using the Dependency Structure Matrix. These dependencies would help you to gain an insight into how the different layers and modules of the application interact with one another and any unwanted relationships and dependencies that might exist in the source code.
Here’s a quick glance at what you can do with Lattix Architect:
- Understand the architecture of the application
- Check the software quality
- Refactor the architecture of the application
Stan4J is a popular tool available both in Windows and Mac OS used for development and quality assurance for applications built in Java. It integrates into the development process, can detect code smells such as cyclic dependencies, and provides visual dependency analysis.
Structure 101 is yet another tool for C, C++ and Java with support for visualization and dependency features. You can use this tool for structural analysis, dependency analysis and impact analysis.
Summary of Managing Application Dependencies
Although loose coupling, high cohesion and modularity are good practices in software engineering, they aren’t devoid of the downsides. One such downside is cyclic dependency. The good news is that you can eliminate circular dependency problems in several ways.