Monoliths to Microservices: The Journey to Cloud Native
by Jason Bloomberg
Programs in PASCAL, a procedural programming language popular in the 1970s and 1980s, required a single period at the end of each program.
Computer programs, after all, were discrete. Self-contained. And above all, monolithic.
Smalltalk, and later C++ and finally Java, brought a new programming paradigm: object orientation. Instead of a block of procedural code, these languages supported classes, objects, interfaces, and eventually more advanced constructs like JavaBeans.
Compared to procedural programs written in languages like PASCAL and early COBOL, object-oriented applications were refreshingly modular. Collaborative development became more straightforward, and software became more flexible.
Object orientation, however, had one severe limitation: it favored a single execution context. Getting objects on one computer to call methods of objects on a different computer proved overly complex and difficult to implement.
Distributed computing had arrived – and object orientation alone wasn’t up to the task.
The rise of the Extensible Markup Language (XML) in the early 2000s gave rise to a new approach to distributed computing: Web Services. Web Services provided XML-based endpoints to various software objects and other programs, giving them a language independent way of interacting with each other.
Web Services, in turn, led to the development of Service Oriented Architecture (SOA), an approach to abstracting Web Services in order to provide greater reuse, interoperability, and orchestration of software endpoints.
This vision for SOA treated the underlying software as monolithic, despite its often object-oriented nature.
As a result, the only practical way to provide the flexibility Web Services promised was to implement Enterprise Service Buses (ESBs) – on-premises middleware that connected such monoliths while exposing Web Services endpoints.
Meanwhile, Web Services proved to be unwieldy in practice, giving way to the lighter weight, Web- centric RESTful approach to implementing software endpoints. Soon, companies began moving to REST- based SOA – a lighter weight implementation of Service endpoints, but SOA nonetheless.
And then the cloud came along, and everything changed.
Cloud computing fundamentally changed the distributed computing equation, both for enterprises as well as the web-scale companies that now found a new home in the cloud.
The cloud abstracted compute, storage, and networking, as well as shifting the center of gravity for enterprise IT away from on-premises physical infrastructure.
Middleware itself changed as well, as a new class of integration platform-as-a-service (iPaaS) soon came to replace ESBs – not entirely, but certainly as the favored approach to enterprise integration.
Both XML and REST-based Web Services, however, remained nothing but software endpoints – endpoints to monolithic, on-premises applications. These now-legacy apps were ill-suited for the cloud: compute, storage, and networking were typically intertwined, and in many cases tied to specific hardware.
Such limitations gave rise to a new approach for building modular software components – components that were self contained rather than simply being interfaces to monolithic software as Web Services had always been.
In other words, the time for microservices had come.
There are many different definitions of microservice, but the one we favor is a parsimonious, cohesive unit of execution.
Parsimonious means it’s as small as it needs to be but no smaller (hence the ‘micro’ prefix).
Cohesive refers to the fact that each microservice does one thing, and one thing well.
The fact that microservices are units of execution, however, is what fundamentally contrasts them with Web Services. While Web Services are basically interfaces to something else, microservices are self-contained pieces of software that run in containers.
They include their own business logic, interface (typically in the form of an API), and just enough of an execution environment to run in a container (instead of, say, a servlet engine running on an ESB).
Microservices provided a new level of modularity, combined with a freedom from middleware-driven execution environments that proved to be the bane of every enterprise SOA deployment. Furthermore, microservices were inherently cloud-friendly.
To achieve scalability and elasticity, microservices required container orchestration platform that would help organizations manage large numbers of dynamic microservices and the containers they ran in. It’s no coincidence, therefore, that Kubernetes exploded quickly onto the scene.
Kubernetes brought its many containers per pod/many pods per cluster architecture to the table, empowering organizations to build applications out of largely ephemeral components – a capability the cloud promised but wasn’t a practical reality until Kubernetes came on the scene.
Along with containers’ and microservices’ ephemerality, Kubernetes also facilitated the ability to group microservices by business domain. Interactions between microservices across domains called for greater control than for interactions within each domain.
This combination of Kubernetes’ approach to microservices orchestration with the inherent ephemerality of containers and microservices – with the addition of domain-driven design – forms the heart of what we now call Cloud Native Architecture.
What constitutes modular vs. what constitutes monolithic has been shifting with each generation of software architecture.
Procedural was monolithic when object-oriented was modular. Then object-oriented software was the monolith when SOA-based Web Services provided modularity. Enter the cloud, and soon all on-premises software became the monolith as the cloud’s abstracted separation of concerns provided the modularity to the software running on it.
Cloud native computing continues this trend. Ephemeral microservices in containers offer a modern take on ephemerality, while everything else is monolithic in comparison.
There is an important twist to this story, however. Not all software can or should be built on ephemeral components. There remains an important role for monolithic software within today’s fully cloud native architectures.
Fundamentally, getting cloud native computing right means extending the best practices of the cloud and Kubernetes across the entire IT landscape – including software we now consider to be monolithic.
The most important lesson every architect should learn is use the right tool for the job. If that tool is microservices, then fine – but if monolithic software better meets the business need, then so be it.
This article is the first in a four-part series. In the second article, we will be explaining the origin of cloud native observability. The third article will cover scaling up cloud native deployments. Finally, the fourth article will round out the series by explaining how cloud native observability helps resolve the challenges of cloud native at scale.
Copyright © Intellyx LLC. Lightstep is an Intellyx customer. Intellyx retains final editorial control of this article.