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
  • 264
  • 3
  • 8
  • if its a true circular dependency, how can you compile it?. more info plz – Ewan Jun 04 '17 at 22:59
  • 1
    @Ewan If you use restful API the dependency is semantical. But it is possible even if you use other technologies with splitting the interfaces from the implementation. If with Xi we mark the interfaces package for X - when separated the technical dependency for A is Ai and Bi and for B is as well Ai and Bi which resolves the compile time circular dependency. – gsf Jun 04 '17 at 23:10
  • I feel like the circular dependency between service A and B should actually be service C.. – Greg Burghardt Jun 04 '17 at 23:25
  • if you resolve the circular dependency then its resolved. I think you need to spell out the exact situation – Ewan Jun 04 '17 at 23:30
  • 1
    @Ewan, One of the important aspects of using microservices is being able to use different technologies for different services. This by itself suggests no compile time dependency from circular or any other type. This does not mean that I might not have an operational dependency between services and some of them in a circular dependency. I am not sure that specifics will add anything more to the picture. – gsf Jun 04 '17 at 23:44
  • well I cant force you to add any, but it sounds like you don't have a circular dependency. You either have an infinite loop or your services are too big/ have multiple responsibilities – Ewan Jun 05 '17 at 06:33
  • 1
    @Ewan what 'infinite loop' has to do with dependencies?!? Also how having two services could lead to having one 'too big'?!? – gsf Jun 05 '17 at 15:20

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.

Derek Elkins left SE
  • 6,591
  • 2
  • 13
  • 21
  • Thank you, Derek. I upvoted your answer but will give a bit more time before I check it. There might be other issues as well. – gsf Jun 05 '17 at 03:24
  • @gsf, you don't need a whole list of possible errors. Because they may or may not happen in your specific case. What you need is to be aware of the MS autonomy in their respective SDLC and Governance. If one MS compromises others autonomy and viceversa, your decomposition (design) is inadequate and fated to fail in the medium-long run. The details are irrelevant when the problem is at design level. – Laiv Jun 05 '17 at 17:43
  • @Laiv Sure, but I am forced to do something that already breaks it, I need to know what all possible issues are so I to prepare for them. If it was up to me I would of eliminate the circular dependency - first thing. – gsf Jun 05 '17 at 22:20
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
  • 2
  • 6
  • I am not sure that your example constitutes circular dependency. Having something to depend on more than one microservices is commonly called miniservice ... and it does not imply or require circular dependency. Circular dependency occurs when service A depends directly or indirectly on service B to operate and service B as well requires directly or indirectly service A to operate. Having service C, UI, or another controller component that requires A and B to operate is not circular dependency. – gsf Jun 04 '17 at 22:31
  • I think the example meets the criteria. You can't generate a session token from the session service without the session service calling into the users service to validate credentials. You can't make changes to a user through the users service without having a valid token in the sessions service and validating the token represents the user being updated. A needs B to function and B needs A to function. If you go beyond that, A needs B which in turn needs A, that may still be fine. If they have valid, stable contracts and changes to A do not imply changes to B or vice-versa. – ipaul Jun 04 '17 at 22:38
  • Ok, it can be implemented with a circular dependency ... and this is the question, how bad this is (if at all), what are the problems over all the other possible options. Make A depending on B only, B depending on A only, having only one microservice to do both, or having orchestration of sorts for these scenarios where A and B are needed both. I do not see this covered in your answer. – gsf Jun 04 '17 at 22:45
  • If you are not sure about Cyclic dependencies, better to study first buddy – Mustafa Mohammadi Dec 12 '19 at 16:39