Karma Refactors to Microservices
There's lately been much talk about microservices as a way of building applications using concepts from service oriented architecture. In parallel there has been much consternation about whether the industry needs yet another buzzword and whether microservices are significantly different from SOA. Regardless of the controversy over nomenclature, the approach appears to be gaining traction.
It's one thing to build a microservices application from scratch. It's quite another to refactor the architecture of a running application. But that's exactly what Karma is doing and Stefan Borsje, CTO and co-founder of Karma recently shared their experience in moving to microservices. While gaining in terms of separation of concerns, isolation and resilience, the migration to microservices has brought some challenges around testing and troubleshooting.
Karma provides prepaid wifi devices that utilize 4G spectrum for internet connectivity. The type of business functions that Karma's systems support include online ordering, device management, order fulfillment and customer self service. These capabilities are exposed to the outside world via APIs that power web user interfaces and smartphone apps.
The original Karma architecture was a monolithic application built in Rails. The fundamental challenge was that different parts of the system had to scale at different rates. The application handles devices, users and a store. Due to different usage and growth characteristics, the device and user parts of the application have to scale faster than the store.
Other advantages of microservices include separation of concerns, providing more isolation of their code base and more flexibility in the implementation of the different parts of their systems. Karma can use different versions of Ruby/Rails in different parts of the application without impacting the whole system. And in future they could even use other languages and frameworks when required. Microservices also helps developers conceptualize the individual parts of the application thus improving developer productivity.
Karma took an incremental approach in migrating to microservices. Starting with a monolith, they split out functional pieces as and when it made sense. Experience with building and using the application helped them understand the best way to decompose the system. Decomposition has continued with some of the new services themselves being split into smaller pieces. For example the first decomposition resulted in a "store" microservice which was further decomposed into "orders", "fulfilment" and "tracking." This evolution has been going on for about a year and half and is still in progress. Stefan explains that each individual part takes from a day to a week to go from nothing to production, depending on difficulty.
Decomposition goes all the way through the application stack even to the database layer. Each service which requires a database has its own database. Each microservice chooses what to expose.
Splitting a monolithic application into smaller pieces means there must be coordination between those pieces. So Microservices may be either outward facing or inward facing. In the back-end, Karma utilises two main patterns for microservice interaction. Request/reply interaction patterns are carried over HTTP. But there are also publish/subscribe interactions which are mediated by Amazon SNS and SQS. SNS operates as a publish topic which bridges to individual microservice queues. As Stefan explains:
It comes down to whether a service is asking or telling. If it's asking, it probably needs an immediate response, if it's telling it probably won't need a response—just fire and forget.
Any good case study includes "challenges." Karma found that end to end testing in the new architecture becomes a lot more difficult. It is difficult to find the root cause of any problems in the cause and effect chain. Commenters on the article speculate this may be more to do with asynchronous messaging than the microservices architecture.
One way to manage this problem is to ensure the microservices adhere to a contract and they then test against that contract. But Stafen explained that the contracts are currently implicit so testing is hard to automate. One commenter, Gil Peeters, recommended a consumer-driven contract testing framework called Pact.
It's a right of passage for a business to refactor it's applications to support growth—to support horizontal and differential scaling—and microservices are becoming a popular way to do that. Let us know if you have any experience to share, both positive and negative.