Let’s imagine I have a single web app containing features which are topically related, but have highly differing implementation requirements.
Let’s imagine that this web app is about fruit, and contains the following features:
- A fruit encyclopedia giving details about various kinds of fruit, including full 3D models that can be inspected.
- A game where you can throw fruit at a wall and watch it smush, again using 3D models but also full physics.
- A marketplace for purchasing different kinds of fruit.
This seems like a prime opportunity for a microservices-based architecture. As microservices are meant to be separated into bounded contexts each of which implements a cohesive set of functionality, it makes sense to have one microservice for each of the three above features, providing its backend. A fourth microservice is then added to provide the actual web app UI frontend, to be filled in with data from the other three services. Finally, we want some sort of API gateway / load balancer which handles all client requests and redirects them to the appropriate service.
This will look something like the following:
However, these services aren’t as easy to separate as it first appears. I see two main issues:
Issue 1: code reuse
Both the encyclopedia and the game require 3D models of fruit, although the encyclopedia wants to add wider information, and the game wants to add physics simulation. According to traditional DRY, one would factor out the duplicated functionality as a shared library. However, in microservices this can lead to tight coupling as changing the shared library affects both services.
I can think of three solutions:
- Ignore DRY and just implement the same functionality in both. This keeps them nicely separated, but causes duplication of work if e.g. a bugfix in the common functionality needs to be applied to both.
- Embrace DRY and use a shared library. If a change is needed, upgrade the version used by each of the services separately as necessary. Accept that the services may end up running different versions of the same library, or you’ll often be making changes to both together.
- Embrace microservices and create yet another one to serve 3D models of fruit. If the implementation is hidden behind a generic API, then implementation changes and bugfixes shouldn’t affect either service using it, as the API contract is still fulfilled. However, depending on the technologies in use, generalising the 3D models in this way may not be feasible or performant, leading to tight coupling and effectively devolving into a slower and less flexible option (2).
Under what situations would each of these methods be appropriate? Is there another method I have not thought of?
Issue 2: containers
Containers are an obvious and powerful tool for implementing microservices. However, they are not synonymous with microservices, and as far as I’ve been able to determine, the relationship between the two is hazy at best. From my research, best practices state both that one container should implement one microservice, but also that one container should only house a single process or responsibility.
A single microservice likely still contains several components; for example, the encyclopedia and marketplace likely both want some sort of database as well as their business logic.
If the logic and the database are placed in separate containers, then there is no longer a 1:1 mapping between containers and microservices. Proliferation of containers also leads to lots of inter-container communication, which slows things down. Containers cannot necessarily be guaranteed to be co-located, so requests between must be encrypted in case they pass over the internet (I think, correct me if I’m wrong). The formation of requests, translation between different APIs, encrpytion, and the transmission time itself all add overhead.
If the logic and database are placed in the same container, then there is no longer a 1:1 mapping between containers and processes. This makes it harder to scale them independently, in case the logic is very simple but requires enormous data storage, or vice versa. One must also build and deploy them together.
How should microservices be divided into containers under this scenario? Are there genuinely good alternatives to containers?