7

I am relatively new to microservice architecture and I was never before involved in a project where the architect insists on having a circular dependency between services.

I am in a position without a choice to design two microservices with circular dependency. Is my natural reaction against to do so - a real one, or I am simply transferring incorrectly from other areas of the software development?

One obvious issue that I can see is with the bootstrapping, forcing the services to keep retrying to connect to each other, being impossible to create an order that all dependencies are already up and running. This though does not seem so bad since I have to have this anyway for fault tolerance.

It also creates some issues with testing, but it seems I will be able to resolve them with doubles.

What real risks and dangers are there in such architecture (if any) that I need to consider?

gsf
  • 274

2 Answers2

7

In another answer, I recommend against this.

The main issue I can see, besides the bootstrapping issue which is related, is if one of the services in the cycle fails, then they all fail. It's bad enough debugging/restoring service when one service fails because one of its upstream services failed. In that case, you can backtrack the dependencies until you find which the closest upstream dependency that's still working. You can fix that dependency, and then repeat the process if the original service is still not working. In this case, you've isolated the second failing service to be downstream of the one you just fixed. Keep repeating until the original service is working.

If you have cyclic dependencies though, there's no way to tell which service in the cycle is causing the problem. If two or more services are having problems, then you are in a Christmas/series lights situation: if two services have failed but you don't know which, then you get no information whether "fixing" one actually helped. Only when you fix both the failing services will you know which they were. If the issue is an operational one that can be resolved by restarting the service, say, then the quickest thing to do to restore service is likely restarting the whole cycle. This avoids needing to isolate the problem and effectively treats the cycle as a single service, at least from a failure management perspective. If the issue is a software defect, then you are in a much worse position because to fix the issue requires isolating the defect, and that's what cyclic dependencies make difficult. Obviously, good monitoring/logging helps significantly with this. Just as obviously, the smaller the cycle, the less severe this problem is.

0

I'm not exactly sure what you mean by a circular dependency, but at a general level two micro-services should be able to be deployed, managed, and versioned interdependently of each other. If you're in a position where a change in micro-service A results in a change in micro-service B, then you're kind of doing it wrong. But I can see where two services may have inter-dependencies.

But it's possible to have "circular" dependencies and still be a reasonable micro-service. Say, for example, one service manages users and credentials and the other manages login sessions. In order to change a user's credentials you may need a valid session token that proves you are that same user. To get a session token, you need to be able to authenticate a user with their credentials. The two services both need to be up for security to work, but they may have clearly defined interfaces. For testing you could create a stub version of the other service.

The API for the credentials service might allow a user to validate credentials, register users, and obtain details for a user. The session service has an API that returns a session token, given a set of credentials, and returns the principal for a given session token. If the API definitions are well thought-out and carefully adhered to, I think it would be fine to have these inter-dependencies. But if you can't establish clear contracts, and changes to one service implies changing the other, then you probably should not have split the services.

As part of the micro-service on something as critical as user authentication, you would put a load-balancer in front of a cluster of containers or VMs running the service. That way you could run two or three copies of each service and if one went down, the load balancer would hide that. (In addition to reasonable retry policies for transient failures, etc.). But you wouldn't be able to authenticate unless the user credentials service was available and the session service was also available.


Adding some clarification:

Generally speaking (micro-services, class dependencies, etc.) you want your coupling to be a directed acyclic graph. B depends on A then A should have no knowledge of B. This isn't a micro-service specific, but a general design principle. One way to fix my design above is to split the credential validation into a separate service and get back the DAG. The specific risk you are trying to avoid is that you can't change or evolve A and B independently of each other. You will always wind up making changes to both.

Never is a very strong word and so I wouldn't say you should never do something, but you should avoid circular dependencies. I think coupling is the real danger.

ipaul
  • 484