Last Updated: April 14, 2017
· jasny

Preventing dependency hell in a micro architecture

When implementing a new feature for an application that uses a micro architecture, you often need to make changes across several (micro) services. Typically these services also enjoy code reuse through libraries, making the case that you need to touch mulitple repositories to implement a single feature even more likely.

This can cause an issue with dependencies. Changes are made on your local system for all libraries and services and everything works. From there you might add several pull requests for the different repositories. But this is where is becomes tricky. If PRs on the libraries aren't merged first and in the correct order, services might break. Also if the services aren't all deployed at the same time, they might no longer work together.

When working with an Agile system like Scrum or Kanban, we can set some rules to solve this.

  • Each task MUST deliver a single pull request.
  • Each pull request SHOULD be able to be merged independently.

Effectively, if a task also requires a change in a library or other app, an additional task should be created for it.

Libraries should remain backward compatible. Rather than flat out removing a feature, deprecate it. Be sure to mark such code as deprecated, both through a @deprecated property in the docblock and giving a warning at runtime. Try to keep methods BC, marking those parts of the code. If a method needs to be changed and can't remain BC, add another method instead and deprecate the old one. Same goes for properties and attributes.

For services this is even more important. The API should also remain backwards compatible. Functionality should be deprecated and trigger warnings. The warnings will give an insight if and how much the deprecated functionality is still being used.

Ideally, releasing a new major version of a library or service, would just remove the deprecated and BC specific code.

In consuming libraries and using other APIs, a service should be forward compatible. It should depend on and work with an older version of the library or API. The dependencies are updated to a newer version of the library, it should use the newer functionality. For using external services you can do the same by checking the API version.

Using both backwards and forwards compatibility is the ideal situation. However sometimes this is simply not feasible. In that case, you can only do complete one task and should put the other task and/or PR on hold until the first PR is merged and released.