We were given the task of delivering an MVP within a short timeframe. We chose a microservice based architecture, allowing concurrent development of functionality. We adopted the popular MERN stack, unifying the language across the system. Working closely with the customer and stakeholders was key to delivering what was really wanted. High engineering standards underpinned the work that we did, providing us with fast feedback when the system broke and high confidence in deployments, and leaving us with a robust and extensible system.
Several months ago we were given the task of delivering an MVP within only a handful of months, for a product that at its core is an over the phone variation of one of our existing offerings. It initially appeared that we could try to crowbar it into a cloned version of our existing offering, but we realized during our initial design sessions that this approach would be drastically hindered by the limitations of our existing system (which was not designed with this in mind). It would also not live up to the high engineering standards we have grown used to within our department.
We chose to adopt the strangler pattern, writing parts of our system from scratch and interfacing with our legacy system where appropriate, to make a deliverable feasible in only a handful of months, and to also build out a platform that is extensible.
In a nutshell, the plan was to quickly spin up a plain but functional UI, a new set of RESTful APIs (to overcome some technical debt we have been carrying with our legacy APIs) and a new set of data stores to keep our new products’ data separate from our existing offerings’. All this within a resilient microservice based architecture. We planned to marshall data between this new system and our legacy system where required, being careful to limit the number of microservices that would facilitate this communication with the legacy system to as few as possible. In fact, we managed to limit this to only one!
The MERN stack
The MERN stack consists of the Mongo database, the Express Node.js framework, the React front end library (often coupled with Redux), and the Node.js server environment.
Benefits of the approach
For starters, working with a microservice based architecture lends itself well to working on different parts of the system concurrently, due to the system’s decoupled nature. Assuming contracts are agreed up front, stub servers can be spun up easily using technologies such as Dyson, allowing us to isolate development and testing of components within our system without upstream components even existing yet. That’s pretty awesome.
Other great benefits include a highly decoupled back end system with a high level of cohesion, alongside a front end that separates concerns well and avoids the tie in of a large framework such as AngularJS. With React, it’s as much a philosophy as it is a library – declare the view and manage state separately, in our case with the popular Redux library. Redux enforces a unidirectional data flow and encourages a functional style, making code easier to reason about.
Throughout development, we have prided ourselves on maintaining a high level of engineering standards. This goes much further than simply baking code linting into our pipeline.
For one, it has involved writing meaningful unit tests with a high level of test coverage. This allows us to validate that new functions work as desired and provide fast feedback about any regressions that have been introduced.
It has also involved writing tests at the component or integration level, providing us with confidence that the building blocks of our system fit together as components in the way we expect.
In addition, we have included several system tests, serving as a useful smoke test within our pipeline to verify that the system is up and running and not obviously broken.
Monitoring and logging are another key area for us and should be for anyone adopting a similar approach. We’ve used a “ping” dashboard to show the health of each microservice at any given time, providing us with another useful mechanism to spot failures fast.
It goes without saying that manual exploratory testing has remained a tool within our arsenal.
Working with the customer
It’s not surprising that customers will often ask for a fixed scope with fixed time but you can’t have both. We have tried to manage this by communicating well and prioritizing effectively. It’s been a key challenge for us working with the customer to establish which features are essential to delivering the key business value and which are not.
As the project continues to move forward its key that we continue to gain fast and frequent feedback and involvement from the customer to help guide the future direction of the product. It’s also key that we continue to adhere to and even improve upon our high engineering standards.
In addition to incrementally adding features, I envisage us looking to add contract testing between services within our system. We know that tests at the unit and component/integration level are great to verify that those parts of the system work as expected, but it’s a great improvement to know that these parts of the system satisfy the contracts expected of them by the other services within the system. This will enable us to individually deploy microservices with truly high confidence.