Functions Packaging and Versioning in Adobe I/O Runtime

“Serverless” is a hot topic when it comes to deploying and executing code on FaaS (Function as a Service) platforms. But having a flexible and affordable execution environment alone is not enough to attract a large community of developers — that is only one side of the coin. What really makes a platform (in a broad sense) attractive to developers in the long-term is the ability to share and reuse code libraries and tools developed by the community. Javawouldn't have become so popular without Maven, and Node.js wouldn't be so rich without npm.

This applies to serverless platforms as well: Without a rich ecosystem of tools and reusable code components and libraries, each developer would have to re-include all its dependencies in each function deployed on the platform.

Function versioning

In the Adobe AEM Commerce team, we are currently writing commerce integration actions on the Adobe I/O Runtime platform based on Apache Openwhisk. In order to share actions with other users of the platform, Openwhisk introduces the concept of shared packages, where one can deploy a shared collection of actions that can be used by others “out of the box,” or that can be combined with other actions in order to compose more complex sequences of actions without having to write a single line of code.

Shared packages and package bindings in Apache Openwhisk basically solve the problem of sharing and reusing functions, but unfortunately Openwhisk doesn't provide any out-of-the-box versioning capability when it comes to sharing actions and packages. In comparison, the AWS Lambda platform supports functions versioning and lambda aliases, two fundamental concepts that can be used to deploy multiple versions of a function and let developers either stick to a dedicated version or always use the latest version.

So to share different package versions on Adobe I/O Runtime, our team came up with a versioning and code sharing/releasing strategy somewhat similar to what npm does in the Node.js ecosystem. Because Openwhisk doesn't provide any versioning feature at the package level, we have borrowed the semantic versioning syntax of npm in order to release and concurrently deploy different versions of shared packages inside the Adobe I/O Runtime platform. Concretely, we release and deploy packages by appending the package version to the name of the package where, for example, we would deploy a package shared-actions@1.0.0 when releasing the first major version of the shared-actions package. While customers and users might use the actions of that package in their own action sequences, we can happily release a new major version shared-actions@2.0.0 without any danger that we might break the code of customers using the previous version of that package.

So instead of just releasing and redeploying one single, shared package with the latest changes and features, we just redeploy new versions and let all the versions co-exist inside the Adobe I/O Runtime platform so that customers can continue to use older versions while we deploy new versions with potentially backwards-incompatible changes. For example, at some point the platform might provide shared packages like:

shared-actions@0.9.9-beta
shared-actions@1.0.0
shared-actions@1.0.1
shared-actions@1.1.0
shared-actions@2.0.0
shared-actions@latest (pointer to latest version)
shared-actions@1.0 (pointer to latest 1.0.x version)
shared-actions@1 (pointer to latest 1.x.y version)

… Looks familiar? This is exactly what code artifact repositories like maven and npm are doing, but we are now applying that concept to a serverless platform. While this is nothing revolutionary, we believe that it is a very safe strategy to regularly deploy shared and reusable code on the Adobe I/O Runtime platform without the risk of breaking existing customer code and production deployments.

It is then up to each user to decide which packages she wants to use and deploy them in her own namespace. For example, the following code snippet shows how it is possible to bind and configure action packages with the serverless tool, making it possible for the customer to deploy different versions of an action.

resources:
packages:
production:
development:
my-actions@1.0.0:
binding: /shared-namespace/shared-actions@1.0.0
my-actions@2.0.0-beta:
binding: /shared-namespace/shared-actions@2.0.0-beta
functions:
# Makes "hello-world" version 1.0.0 available in the production package
hello-world-prod:
name: production/hello-world
sequence:
- my-actions@1.0.0/hello-world
annotations:
web-export: true
  # Makes "hello-world" version 2.0.0-beta available in the development package
hello-world-dev:
name: development/hello-world
sequence:
- my-actions@2.0.0-beta/hello-world
annotations:
web-export: true

We believe shared packages and package versioning will become a very important facet of the Adobe I/O Runtime platform, transforming what some may perceive as a simple runtime environment into a rich ecosystem of reuseable components and dependencies. This also opens up interesting future challenges, for example, permission handling (answering “Who is allowed to use what?”) and dependency management (to ensure that one cannot remove a dependency used by others). This is a big step toward empowering the Adobe I/O Runtime platform on its path to Cloud Native Applications development.