In the Wealth Wizards software team, we’ve recently embarked upon a journey to break down, or strangle the monolith, so to speak, and adopt a microservice-based architecture. In a nutshell this means moving away from having one large server side application with lots of often highly coupled classes, to a system where the functionality is divided up into small, single purpose services, that communicate with each other via some type of API.
One of the beauties of adopting this architecture is that not only can different microservices be written in different languages, but they can even communicate with different types of databases. For the time being however, we decided to roll with Node.js, starting out framework-less but quickly coming to the conclusion that using a framework such as Express was going to make our lives that little bit easier!
Whilst there’s naturally a learning curve that comes with all of this, I found it fascinating how your thought processes begin to shift when it comes to technology in general, across the board. You find yourself beginning to intrinsically look to avoid monolithic code repos or applications across the stack, and think more carefully about your technology/framework choices, trying to make single purpose, cohesive applications, and thinking ahead to try and avoid becoming tied into frameworks further down the line where migrating away may become increasingly costly as your ties to them grow!
One thing I’ve enjoyed and noticed as a particular benefit is how testable the code is, and the governance of ensuring that services are fully tested has now been built into our software delivery pipeline, which is super nice! By the time our new platform is deployed, we will have fully unit tested, component tested, and system tested code, making software releases far more automated, far less costly, and with a high level of confidence, something which is really powerful.
The ops side of micro services is, in a way, almost the key part/challenge, and whilst it’s not something I’m heavily involved in, at Wealth Wizards we’re trying to promote a culture of teams of engineers, as opposed to separate ops and dev teams, and as such I’ve been lucky enough to learn about and play with a whole bunch of new and exciting technology within this space, including but not limited to, Docker, Docker Compose, Jenkins, and Kubernetes. One of the side effects of our new stack that I’ve really liked is the concept of configuration and infrastructure as code, and this is something I would definitely recommend to others as a best practice.
In general, some of my key takeaways/learnings when it comes to implementing microservices with Node.js are:
- spec out APIs up front using YAML files
- plug the API YAML files into an API generation package such as Swagger UI
- write unit tests and component tests as you go, integrating them into your pipeline, and failing builds when the tests fail, or if the coverage is not deemed sufficient
- extract code which is reused commonly across services into npm packages to DRY up code, although accept that there will be a maintenance cost associated with this
- lock down versions within npm to ensure that different dev machines and server
- environments are using same exact version number for dependencies to avoid headaches!
As a final point, whilst there is a whole bunch of great knowledge and information out there on the topic, one thing I’ve found is that some level of planning and design up front is essential but to some degree you should accept that some of the design decisions such as the code structure, library selections, DRYing up code with packages, and other such governance tasks will naturally evolve over the initial weeks, and so at some point you need to just take the plunge into the microservice world and learn as you go!
- Mark Salvin, Senior Developer