Expansion of the Coupling Surface
Application architectures have evolved from single-unit monolithic deployments coupled to compute and storage, into distributed architectures consisting of multiple polyglot services coupled to diverse integration APIs and operational tools. As a consequence, application portability is an order of magnitude more complex than a simple infrastructure migration for a monolith. Now, applications have a larger surface area with complex topologies and dependencies which tends to gradually evolve over time. Compared with the monolith’s “one and done”, distributed applications can be migrated progressively, incrementally migrating infrastructure elements, swapping dependencies, porting and reusing patterns and knowledge across projects.
To realise such portability, it is important to analyze an application from multiple perspectives, choosing design principles that encapsulate change, complemented with APIs that enable infrastructure and tooling flexibility. Let’s explore the primary application surfaces that can limit portability and how to address these with the tools and practices that offer the broadest implementation flexibility.
Structuring Application Boundaries for Business Alignment
The foundation of portability requires an understanding of Domain-Driven Design (DDD), Hexagonal architecture, and Microservices principles as effective methodologies for encapsulating change within a business domain. DDD helps identify application boundaries that closely mirror the business domain and enhance modularity by limiting the impact of change. The Hexagonal architecture amplifies portability by ensuring that the application remains independent of its environment. Microservices further strengthen this decoupling, enabling each service to operate independently, enhancing application portability. Incorporating these techniques, help create natural application boundaries and limit the impact of change, enhancing the modularity and stability of software systems.
Combining these techniques with an anti-corruption layer and Inverse Conway Maneuver will further encapsulate and isolate changes. An anti-corruption layer represents a façade between systems that don't share the same semantics and helps translate requests that one subsystem makes to the other subsystem. This pattern ensures that an application's design is not limited by dependencies on other systems. The inverse Conway Maneuver, on the other hand, aims to align business structures with IT systems in order to create fewer communication overheads and shorter distances to the decisions. It helps to indirectly promote the desired architecture and socio-technical cohesion by aligning the team and organizational structure.
Irrespective of the technology used or business domain, these techniques help define architectures that align business and IT to work as a single team, encapsulate change within application boundaries, and set a foundation for portability.
Next, let’s see what are the technical coupling surfaces and how to allow migration of business logic across a wide range of tools and services.
Packaging for Infrastructure Agnostic Compute
Once application boundaries have been established - preventing changes from causing unintended ripples across the system - the next area of concern is around compute portability. This involves circumventing a specific compute provider, leveraging container technologies such as the Open Container Initiative (OCI) specifications and Kubernetes for container orchestration.
Containers abstract the programming language, runtime, and application architecture details into a single portable format along with their dependencies, thereby enabling these applications to run seamlessly in diverse environments. Containers extend to on-premise, cloud container services, and even Functions as a Service (FaaS) platforms like AWS Lambda or GCP Cloud Run. Kubernetes extends containers by popularizing APIs such as health probes, declarative deployments and placements are de facto standard in container orchestration. Using these technologies is the cornerstone of application portability from compute point of view.
Architecture aligned with the team structure, application boundaries aligned with business domain, combined with open source container technologies, and monitoring standards will form a solid portable foundation, but might not be enough. We also need a degree of flexibility also from operational and integration surfaces of the applications.
Exposing Standardized Operational APIs
Observability is a key operational attribute in distributed systems, facilitating real-time insight into system performance and overall health. An effective monitoring system is an integral part of any modern distributed application runtime. Tools like OpenTelemetry, Zipkin, Grafana, and Prometheus are excellent open source choices for this purpose. Zipkin focuses on distributed tracing to help developers understand latency issues in distributed systems. Prometheus stands out for its powerful real-time metrics and querying capabilities. OpenTelemetry offers a single set of APIs to capture metrics, traces, and logs, facilitating comprehensive system monitoring. Using these open source APIs and standards will ensure the highest degree of flexibility from an observability point of view. To that end, embedding open observability APIs within applications can streamline issue detection and resolution, allowing operations teams to respond to and rectify system irregularities promptly and efficiently. One project that can bring observability into applications regardless of the programming language and observability stack is Dapr from CNCF. For example, Dapr's Observability APIs deliver a way for applications to integrate with various monitoring platforms without having to worry about specificities. They allow applications to push telemetry data effortlessly, with Dapr taking care of injecting traces, metrics, and logs that can be sent to any monitoring system supporting the mentioned open APIs.
Furthermore, tools such as Envoy, Istio, and Dapr, among others, allow the delegation of application networking responsibilities to operations teams, thereby minimizing the burden on developers. These tools ensure network resilience through built-in functionalities such as circuit breakers, retries, and timeouts. They also offer comprehensive security through approaches such as mutual TLS (mTLS) and OAuth. This transition of networking responsibilities not only fortifies system robustness but also paves the way for application portability agnostic of monitoring and networking concerns. An additional strength of Dapr is its unique approach to applying resilience policies uniformly across all applications and component interactions, including those with external systems, significantly enhances the portability of these policies. This homogenization of resiliency, security, and observability configurations across different tools under a single format promotes the understanding of these distributed systems patterns and enables reuse. Consequently, this allows operations teams to apply the same principles and practices across a range of applications and platforms, streamlining operational workflows and reducing cognitive load.
Developing with Polyglot Integration APIs
The next area of software portability concerns is focused on reducing integration coupling between services and their dependencies through the use of de facto open source integration standards, and the adoption of event-driven architecture. For example, event-driven architecture (EDA) helps build flexible, loosely coupled systems by breaking down application functions into distinct services that react to events. In such a setup, services can be updated, replaced, or ported with little disrupting the entire application, thereby enhancing portability. But the platform of choice for implementing EDA can leak into applications and reduce its portability. Using de facto open source standards such as Apache Kafka, AMQP and others will ensure flexibility to swap different implementations of these protocols provided by different infrastructure providers. The pub/sub API from Dapr further accentuates this principle by providing a layer of abstraction over underlying infrastructure.
EDA is only one form of application interaction style and there are others that are used at the same time, such as synchronous service interactions, stateful workflow orchestration, distributed lock sharing, and others. There are interactions between the application and the supporting infrastructure such as key/value access, configuration and secret distribution and others. All of these interaction surfaces represent an opportunity for coupling. Using widely adopted APIs or abstractions such as Dapr API offers portability and code reuse without concerning underlying specifics. Dapr's State Management API offers an efficient way for applications to interact with a plethora of state stores. Whether it's Redis, Azure Cosmos DB, or AWS DynamoDB, this API ensures applications aren't tied down by the specifics of state storage, enabling smoother transitions. Dapr's Pub/Sub API works similarly, abstracting away the details of various message brokers like RabbitMQ, Azure Service Bus, or NATS, and thereby allowing applications to publish events and subscribe to topics freely. This level of abstraction extends to the Service Invocation API, which allows services to call each other directly while taking care of underlying complexities such as service discovery, load balancing, and network resilience. The result is code that remains resilient and portable across different networking environments. Similarly, the Bindings API provides a consistent method to interact with a wide range of external resources, irrespective of their nature, allowing the application to seamlessly communicate with these systems without worrying about the specifics of their SDKs or APIs. For secure application operations, the Secrets API comes into play, providing an abstracted way to retrieve sensitive data, like API keys or connection strings, from various secret stores such as Azure Key Vault, HashiCorp Vault, or Kubernetes secrets. Finally, Dapr's Observability feature delivers a way for applications to integrate with various monitoring platforms without having to worry about specificities. They allow applications to push telemetry data effortlessly, with Dapr taking care of injecting traces, metrics, and logs that can be sent to any monitoring system. Overall, the suite of APIs provided by Dapr ensures that application code remains untethered from the specifics of underlying technologies, thereby enhancing portability and reducing the risk of vendor lock-in.
Ensuring Data Interoperability
To maintain data integrity and compatibility in dynamic environments, the use of schemas and a schema registry is crucial. Schemas define the structure of the data exchanged between services, while a schema registry such as Apicurio Registry, Confluent Schema Registry serves as a central repository for storing these schemas. This approach ensures consistency in data exchange irrespective of participating application differences, mitigating the risk of integration errors as services evolve, thereby enhancing portability. In addition to the schema for the business data, using a schema envelope such as CloudEvents specification helps defining the format of event data that is being exchanged between systems. CloudEvents is a specification for describing event data in a common way by standardizing the information that must be included with each message and defining a common format for that information. This uniformity enables developers to create systems that can work together, regardless of the technology stack or cloud platform each component uses. The producer of an event and the consumer of an event do not need to know about each other, they only need to understand the same CloudEvents format. This decoupling allows changes to be made to one service without affecting others, enabling a more flexible, resilient system.
OpenAPI and its sister project AsyncAPI are other tools that promote portability too. They are specifications used for defining and documenting your entire APIs, including its endpoints, input/output parameters, and authentication methods, in a single, standardized format. This reduces ambiguity and increases the ease of integration, promoting better interoperability between different services. Moreover, they facilitate code generation for both server stubs and client SDKs across various programming languages, which significantly speeds up the development process. This accelerates deployment and boosts portability, enabling the API to be more readily consumed across diverse systems and technology stacks.
The Evolution of the Portability Attributes
The rise of the cloud has redefined the infrastructure granularity and coupling surface, underscoring the importance of optionality in multiple dimensions. It is no longer sufficient to run an application on a cloud environment, the application also needs to consume the local cloud services whether these are for synchronous traffic routing, asynchronous event processing, or configuration and secret management. At the same time, the application needs to blend with the observability stack, operational and automation tools too, while minimizing coupling.
Another significant influence on portability is the emergence and adoption of a growing number of programming languages. In contrast to the past, where diverse frameworks were used for different purposes, today's developers can use the best suited language for their domain, whether that is Go or Rust for infrastructure management, Java or .Net for business applications, Python for data science, and NodeJS for web development. In the modern development landscape, using polyglot protocols, patterns, and libraries is another critical portability factor. The sidecar approach such as Dapr, combining well established patterns with API based interactions is a good example of such a polyglot library.
And lastly, it is the influence of the open source movement as a source of rapid innovation and broad adoption. The emergence of open source has shifted longevity from formal software standards like SOAP to open-source projects that have grown into de facto standards such as Kafka, Redis, MongoDB, and proven by wide adoption. Using such open APIs ensure broades integration interoperability across clouds, supporting tools, and libraries.
These influences have redefined the value multiplier factor in organizations from access to more powerful hardware into access to more talented people. Expressing your designs in patterns can make it easier to explain, understand, and take your application and move it to another platform. In the current times, the reduction of cognitive load, the portability of skills as crucial as that of hardware migration, which is why the portability of tools and knowledge, across people and projects is paramount too. The strategic principles in this article provide guidance on modern application development supporting practical portability in space and time. It encourages developers to embrace change, leverage open source and de facto standards, and prioritize reusable and broadly applicable skills and tools. In this ever-evolving landscape, tools and platforms like Dapr will undoubtedly be instrumental in promoting and advancing application and skills portability.