A microservices journey — breaking a monolith into pieces

Enabling autonomy through a decoupled architecture.

Ricardo Alves
KPN Developers Blog
6 min readApr 18, 2019

--

Photo by Scott Webb from pexels.com

The topic of microservices became quite popular during the past few years. It has been increasingly adopted by large organizations as a reference architecture for scalable application development, specially in modern cloud-based systems.

In this article we will not dive into the concept’s details — it has been described in-depth by others already. Nevertheless, we’ll try to expose the most relevant topics that shaped our transition from a monolithic to a decoupled architecture.

Brief history

A few years ago KPN Technium (previously KPN Digital) embarked on a radical journey to setup an IT department based on the agile principles and organized in decentralized autonomous development teams. This transformation required a change in the way IT solutions were delivered. Teams should be able to work more independently of each other and the IT architecture had to support more independent software development life-cycles for all of its components.

As part of this mission to radically simplify the IT landscape, we developed an API engine. Its original goal was to bridge customer-facing applications, built using trendy, modern web frameworks like Angular, React, etc, with other systems within the larger organization, like supply chain applications, billing systems and other interfaces which were not suited for the challenges posed by the new digital channels.
We called it the Digital Engine, an application providing RESTful JSON APIs, specialized in calling upstream dependencies, using the available interfaces — usually SOAP webservices — to retrieve data from such systems and exposing it to the consumer applications, while addressing concerns of performance, security and development speed.

Although keeping in mind the goal of having applications with independent life-cycles, we approached it pragmatically and started with a monolithic application. This would eventually lead to the need of splitting into multiple applications, but such strategy — delay the split until it’s really needed — allowed us to learn more about the domain boundaries and start the microservice journey only when necessary.

The Digital Engine was initially used as a backend for one application, but rapidly grew to become a large monolith, used by a set of applications, developed by multiple Scrum teams. This resulted in several problems:

  • Teams were not independent from each other, as all new releases required cross-team alignment;
  • The risk posed by each new release was increasingly high;
  • The amount of conflicts for every code change was rising;
  • In direct relationship with the amount of code, the amount of unit tests exploded, increasing the build time;

It became then clear, by practice, that a decoupled architecture was necessary to unleash the teams’ potential, and assert the ability of the Digital Engine to grow beyond the monolith and become a Digital Platform supporting the organizational change to autonomous teams. We started then moving to a microservice architecture.

Driving forces and core beliefs

The topics mentioned above were the main triggers for this transition, but for all the architectural decisions there is a set of driving forces, factors of change that shouldn’t be ignored. Some of these factors are:

“organizations … are constrained to produce designs which are copies of the communication structures of these organizations”

It reminds us that the design of systems within a product/service delivery chain will mirror the organizational structure. As it usually happens with other laws of nature, we’re better off embracing Conway’s Law than fighting it. And that means not only adapting our design to the organization, but also the more challenging task of changing the organizational structure to be compatible with a more suitable software architecture.

  • Timelines and value delivery: As usual, any architecture or software solution will only be successful if it delivers value within a reasonable time-frame. KPN is no exception here, so value delivery, for KPN and it’s customers, has been always a driving motivation behind architectural decisions. It is key to have such pragmatism in place.

It is also important to mention some of the core beliefs of KPN Technium, since these have an impact on our technology and architectural choices:

  • Autonomy of teams: We believe that real value comes from self-organizing teams.
  • Every team works DevOps: We won’t dive deep into the concept of DevOps here, but keep in mind we mean that every team is responsible for developing, deploying and operating its own applications (you build it; you ship it; you own it).
  • High-availability by design: We see downtime as a design failure.
  • Security and privacy by design: Security and privacy are a central concern for our products and services. It must be taken into account in the early stages of software development.

Some of these might even seem contradictory to each other. When such forces and beliefs push into different directions, the ability to find balance and compromise is crucial, but that should always be part of a transparent and conscious decision process.

Partitioning principles

The transition to a micro-service architecture is a bumpy road, full of technical setbacks, unexpected dependencies and organizational challenges. Regardless of the particular strategies adopted and albeit potential compromises along the road, it is paramount to have a clear vision of the desired situation. Such vision provides guidance for the next steps to be taken, making it clear when certain actions or decisions might be sending us in the opposite direction.

As mentioned before, one of our driving forces is the organizational structure. Hence, our partitioning strategy followed the basic rule of splitting application based on ownership, i.e. each team started it’s own service.

Having a monolithic application as a starting point, our approach was inevitably shaped by the arduous process of splitting the existing features into multiple, smaller applications. We called this process partitioning, as the Digital Engine was being split into a set of partitions. By splitting we don’t mean necessarily copy and pasting code out of the monolith. The process is itself an opportunity for refactoring and replacing outdated implementations.

Multiple stages of a partitioning strategy.

Another driving force is timelines and value delivery, so the process was done in parallel with the development of new features. It’s then no surprise that the decision to either re-use, split or create a new microservice is always on the table during our daily work. These decisions are then guided by a set of partitioning principles:

  • A service is owned by a single DevOps team.
  • Each service is single purposed and has clear value.
  • Services do not share any form of state.
  • Each service has its own life-cycle.
  • Services interact via well-defined interfaces.
  • Services are operated using standard processes over the ecosystem.
  • Security and access control are enforced consistently.
  • There is no duplication of responsibilities across multiple services.

These principles materialized our conception of a microservice, regardless of its size, complexity, technology used or other properties.

Challenges

Although this approach allowed us to overcome some of the main difficulties mentioned above — more team autonomy, fewer conflicts, faster release cycles — , implementing a micro-service architecture brings additional complexity in other fronts. Some topics are particularly relevant, since they require non-trivial engineering solutions:

  • Monitoring customer journeys across multiple applications.
  • Authentication and authorization in a decoupled architecture.
  • Added latency due to overhead in communications service-to-service.
  • Overhead in software development, with boilerplate code being added per new service.
  • Added complexity to system integration testing.
  • Governance of shared components.
  • Promoting efficiency and best-practices to a growing number of autonomous teams.
  • Scaling shared components regarding resources but also security wise.

For the past few years, we managed to overcome some of the challenges posed by the above topics. In other fronts, we are still making progress and learning along the way.

In the next articles we will share more about our architecture and provide some details regarding the engineering and technology choices we made in order to overcome these challenges.

Final considerations

In our transition to a micro-service architecture, we embraced the main driving forces of organizational structure and timelines for value delivery. Such a pragmatic approach was key to the success of this transition, not only because we kept delivering value while re-engineering our applications, but also teams were rewarded with more autonomy, which enabled them to make their own decisions during the development journey.

--

--