It is already 2020 and many companies are still debating what cloud development strategy they should adopt. I am conscious of how many of them still opt for building multi-purpose applications that can run on multiple clouds, while only a few really adopt a native approach for running integrated applications on the cloud platform. And while I can understand that cloud-native development seems like a sky-level risk (am I putting all my eggs in one basket?) I'll try to explain why this is a good (and inevitable) choice.
Writing multi-purpose applications that share code between different platforms is not new. The idea of write once, run everywhere was the mantra during the late 90s but applying it to today's technology seems to be more harmful than beneficial. And I am not talking only about cloud technology. If you don't believe me, look at what happened to Dropbox engineering team with their mobile development strategy
The transition
While many companies start implementing their vision of cloud-integrated applications and services, it is an accepted (yet temporary) pattern to deploy these services outside the platform on any on-prem infrastructure. Why? Because they need to keep the business running while they implement this transition. They can't just wait 3 years to have all their systems migrated to the cloud.
By adopting this bimodal strategy, these applications and services are cloud-native at their default implementation, but still portable by applying isolated and well-documented changes to their source code.
Wait, why do we still require changes to our source code for portability and not implement those services with Kubernetes?
Well, because we want our default service implementations to maximize the benefits of the underlying cloud platform. Requirements are usually cost control, managed services, and live deployments (no maintenance windows) and that indirectly pushes you to serverless and cloud-nativeness.
However, it is important that during this transformational period these companies mitigate but not eliminate cloud-nativeness to enable easy service portability outside the cloud platform.
Why go cloud-native?
By writing code in a standard way using the platform's defaults, we ensure we benefit from the full capabilities of the chosen cloud. Closer proximity to cloud features makes for simpler implementation. Conversely, biting the bullet to rewrite native code to address a different platform, while on the face of it a pain, is usually fairly straight forward and less costly
Common abstraction approaches invariably address the lowest common denominator, put a brake of innovation, and result in more costly and more complex solutions. That overhead will end up being more expensive than re-writing isolated pieces of code multiple times for multiple platforms.
Wait, re-writing all the apps and services code? Yes. Well, not all of it, only some very isolated parts
How to mitigate?
During this transitional period, cloud-nativeness can be mitigated but not eliminated. And this is handled at component engineering level
1. Don't couple
First of all, don't couple your business logic to the platform. Although this seems pretty obvious, it is way more important than it sounds.
2. Write interfaces
Code your business logic to an interface. Again, nothing new, just old school interface-oriented programming. This is, separate functionality into modules that communicate via well known functional interfaces (e.g. interfaces that mean something to the consumer function, rather than being technical abstractions)
3. Isolate the interfaces
Isolate code that accesses cloud services directly via SDK behind such interfaces and use dependency injection to instantiate the appropriate interface implementation at runtime. This is commonly known as The Provider Pattern
Example:
- AWS has a service to provide hierarchical configuration information - SSM Parameters
- Design an interface that presents configuration data in a manner that makes sense to the application (a key/value map, perhaps with additional application-specific semantics)
- All consumers of configuration address this interface
- Write a module that implements the interface using the SSM APIs and translates the data into the format demanded by the interface, plus maybe has caching capabilities
- At application start-up, accept an environment variable naming the configuration data implementation to use.
4. Reimplement the interface
OK, so you want your cloud-native code to run on another cloud? No worries, just reimplement the interfaces for that new cloud platform using the new platform's standards and direct access to services via new SDKs. Well isolated and documented changes, while the core business logic remains untouched.
More importantly, you need to have your application or service running on-prem? Even easier, re-write those interfaces using supported industry APIs (i.e. JMS, MQTT) and de-facto standard providers (i.e. Hibernate for JDBC).
Composing Cloud-Native Software
It is clear and recognized that access to platform services via industry APIs or de-facto standards are more portable by nature. However, we can't just wrap every direct access to cloud services with an HTTP interface (API).
Why not?
Again, by writing code that does not leverage the underlying cloud platformβs defaults, we would take an overhead that we would not have to worry if we stay with these widely used cloud technologies. This overhead will end up being more expensive than re-writing isolated pieces of code multiple times for multiple platforms
However, it is important that every component is exposed through and standard HTTP interface. This is how we will compose cloud-native software.
- Modules are composed by combining different functions
- Components are composed by combining different modules
- Systems are composed by combining different modules
- Solutions are composed by combining different systems
This principle rolls up and down, and it is one of the most important guidelines to build modern software.
Back to our main topic, I think that cloud-nativeness risks are mitigated above the component level.
Summary
Cloud-native development is faster, cheaper, simpler, and more productive. In the unlikely event of a change of cloud or platform provider, portability is obtained by identifying very isolated places where a code change is necessary:
- a new interface provider here,
- a new event format transformer there,
- a slightly different container configuration elsewhere
Even easing any necessary on-prem deployments on-prem. Most services provided by clouds are available off-cloud, though usually at a greater overall cost. Just use industry APIs and de-facto standard frameworks.
In short, exploit modularization, interface-oriented programming, dependency injection and you are good to go. Just (age-old) good programming practices!
(Credits to Billy Huy for the cover picture)
Top comments (3)
An interesting article Pablo!
Interestingly, I think some of the thought processes that you highlight feels (i.e building interfaces) almost feels like you're advocating for a cloud-agnostic approach, rather than cloud-native. That is, by trying heavily to mitigate the downsides to cloud and building in a way that means software can be potentially moved later. Whilst I do agree that this makes a lot of sense, and I do this myself where practical sometimes these optimisations fall into the trap of premature optimisation, where you find yourself building large, potentially complicated interfaces where there really need not be one.
There are also, in my opinion, different aspects of cloud programming that cannot be coded to an interface. For instance: mitigations for infrastructure nuances. i.e, I monitor lambda applications much differently to how I would monitor the exact same service running on different infrastructure. Due to underlying differences, the code itself is optimised differently for different platforms.
I think the downsides of cloud native are essentially one of cost. There is a cost associated with vendor lock-in, but there is also a cost in spending time making components cloud-agnostic. I guess it's just a case of which balance of risk / cost is appropriate for the scenario. But it's indeed hard to quantify.
And ... Just my two cents. A corollary of this PoV is that the role of Cloud-Native developer does not exist. We have just programmers using patterns and libraries. Ages-old good programming practices! Nothing new
Agree Lou, I sort of lean towards a defensive approach which seems to be positioning towards cloud-agnostic, but the main message is something like "get off the backpack you've been dragging all your programming life, forget about over-complicated frameworks and just go for pure programming, taking the control back". It's a song against Inversion of Control in the Cloud :)