Thoughts About Dependencies In Software World
Everything in software is all about dependencies! Right this morning, this idea is becoming more and more clear in my head. So I decide to write down how this comes up in my mind and why I think it so. I will talk about how dependencies take effect in interfaces design, software architecture, deployment and productivity.
The definition of dependencies is quite simple: There are two things need to be done, but A has to be finished before B. It’s a little bit different in software since we involved time in dependencies, so the definition can be converted to “version X of A has to be finished before B” we could also saying “B depends on version X of A”. So as you see describing a dependency is still not very complicated but things could go wrong.
Problems In Dependencies Management
Circular dependencies are when connecting all dependencies together it becomes a circle. The most simple example is A has to be finished before B and B also need to be finished before A. As you could see in above phase when this thing happens there is a conflict: A and B’s desire can not be fulfilled at the same time. In software when this thing happens software will most likely crash (yes there are some tricks for some specific languages like Python which can walk around it, but I highly recommend not to do the trick in most cases). In reality, things could be more complex, the circle can be huge when you aware things went wrong, and it could also be very hard to locate the real problem because there are many things involved.
There are two ways I recommend to resolve circular dependencies: one is instead of depending on each other, depend on a third party module. Take above example we could extract code from A and B out to C and let A and B both depends on C to resolve it. Another one is reorganizing the code put the dependencies either in A or B, because when circular dependencies happen usually means the code needs to be put together are separated into different places. About how to chose which method to use I have a very simple trick: if the code caused this problem is for specific business logic chose the second, or else if it also can be used elsewhere chose the first way.
Dynamic loading is also another way to fix the circular dependencies issue, but its use case is very limited, abuse it will lead to an even more complicated problem. For me, I will only use it for factories which could instance the underlying worker based on requires. Fox example in Java, Spring use reflection to instance service bean, in Python, you could use import system to dynamic instance service object and there are a lot many in other languages.
long dependencies chain
Long dependencies chain is that A depends on B, B depends on C and etc. When this chain grows longer and longer the robustness of software will be affected and also upgrading of the software will also be hard since you might encounter interface compatibility problem (remember the butterfly effect right :))
My suggestion for this problem is giving up on those third-party softwares which have long dependencies chain (usually the quality of those libraries are questionable) and write your own code. And if it’s your own code try to compress the chain by moving related code into a single place.
too many dependencies
The thing for too many dependencies doesn’t actually have a way to fix, I think we can only avoid it by not flooding our project with too many dependencies, we have to keep an eye on our project to remove those unnecessary dependencies from time to time.
Dependencies In Interface Design
There is a misunderstanding about dependencies, people easily think dependencies are only those in configuration files (ant.xml, maven.xml in Java, requirements.txt in Python). No, it’s not. Dependencies are all about code splitting at the first beginning. This means when we design interfaces we need to make sure the cohesion of interfaces, interfaces which are similar should be put together instead of spreading everywhere causally. If you are confused about how to do it, exploring libraries which you use heavily, those usually can be treated as best practices. And remember don’t take only one library as the standard, check out more and compare them.
Dependencies In Software Architecture
For software architecture, the most important thing is the major framework your project depends on, for web backend project it’s the web framework(e.g.: Spring in Java, Django in Python, Rails in Ruby and etc), for frontend UI project (e.g.: React.js, Vue.js, bootstrap and etc). If you looked those project they either self-dependent or have very few dependencies. So from my point of view for those are heavily used in your code base best to depends on those which have simple dependencies.
And what’s more, avoiding use two similar things in one single project, predictable is one of the most important things for code readability, unnecessary confusion in code could make things unpredictable and failed eventually. If you have no choice then hide the underlying implementation by extract abstract interfaces suits your requirements.
Dependencies In Deployment
Yes, deployment is also involved with dependencies. Many companies have CI built to ensure code quality: usually, every time after the code is pushed to repository testing is triggered and if all test cases are finished without error code can be deployed to staging or production environment. So this is the simplest dependencies management in deployment and it just work. Think about if you do it manually how could you ensure this can happen every time in a correct way.
And the reality in deployment is more complicated according to the service size. Basically, it involves infrastructure(e.g.: network, hardware, backup, storage), service scaling and deployment, monitoring and log analysis and even more. Even more and more SaaS service and cloud service really simplified a lot of work compare to years ago, but it still quite challenging to integrate them together. Especially when micro-service is becoming popular nowadays dependencies management is also should be treated as the same important as them.
Automation and monitoring are the only things which I can think about to properly do the dependencies management for deployment. And there are actually tons of tools for it (Jenkins, Docker, Ansible, SaltStack and etc). When the complexity is out of the reach for humans we can only rely on those tools to behavior consistent and give us feedback when something goes wrong.
Even I talked about dependencies in above sections, but still there are a lot of aspects I didn’t cover, but what I really want to urge people to do is looking into the things you are working on right now and try to figure out the relationships between them, even more, try to reorganize them if you find out it definitely an obstacle to your productivity and time will be saved for a better life-work balance.