I got several questions from customer and from users here on re:Post about deploying React web apps in Greengrass and with this article series I wanted to demystify the process and provide some best practices
In the previous article in this series I showed how to deploy a simple React app to a Greengrass device. I also showed how to use component parameters to configure the component behaviour, for example to select the port on which the web server is running.
In this article I will show to use component dependencies to better structure the application.
Component dependencies
Say I want to deploy a new version of the web app. I need to re-build the app, zip the build folder, upload the zip archive to S3, increment the component version and redeploy it. This process involves some steps, and I normally use a tool like gdk
to streamline the component creation process.
One issue with the current component definition, is that every time I deploy a new version, Greengrass is going to execute the Install
lifecycle script, including trying to reinstall live-server
. This is not so efficient and in components requiring more or larger dependencies it can also take a significant time. Moreover it introduces a dependency on the availability of npmjs.com at every deployment.
I could write some fancy bash script that does some checks and skips the installation if the package is already there but I then need also to add additional logic to cope for cases where the package needs to be updated and so on. This can become pretty complex quickly, and complexity is also source of bugs.
Component dependencies provide a flexible mechanism to deal with this issue. Let’s see how to use them.
First, I am going to create a new component that is only responsible to install live-server.
There are no artifacts and I only need to use this recipe:
RecipeFormatVersion: 2020-01-25
ComponentName: com.example.live-server
ComponentVersion: 1.2.2
ComponentDescription: "live-server"
ComponentPublisher: Amazon
ComponentDependencies: {}
Manifests:
- Platform:
os: /linux/
Lifecycle:
Install: |-
npm init -y
npm install live-server@1.2.2
Notice that I chose to pin the live-server
version and I made sure the component version matches it. If in the future I want to use a newer version of live-server, I can create a new version of the component with an updated install script.
Next, I revise the com.example.ReactApp component recipe to add the com.example.live-server component as dependency:
RecipeFormatVersion: 2020-01-25
ComponentName: com.example.ReactApp
ComponentVersion: 0.0.3
ComponentDescription: "A simple React app running with live-server"
ComponentPublisher: Amazon
ComponentDependencies:
com.example.live-server:
VersionRequirement: ">0.0.0"
DependencyType: "SOFT"
ComponentConfiguration:
DefaultConfiguration:
PORT: 3000
Manifests:
- Platform:
os: /linux/
Lifecycle:
Run:|-
node {com.example.live-server:work:path}/node_modules/live-server/live-server.js \
{artifacts:decompressedPath}/app --port={configuration:/PORT}
Artifacts:
- Uri: <REPLACE_WITH_S3_URI>
Unarchive: ZIP
Permission:
Read: OWNER
Execute: NONE
I have added a dependency to the com.example.live-server component specifying a version requirement using semantic versioning and a dependency type. SOFT means that even if we deploy a new version of the dependency, this component will not restart. HARD means that any time the dependency component changes, this component restarts.
We can now deploy this component in the same way we did before. Greengrass core software determines that it need another component to be able to run this one, and takes care of fetching the latest version matching the version requirements. Once that is done, Greengrass executes the run script of the com.example.ReactApp component.
To run live-server I need to know where it has been installed and I use the {<component>:work:path}
recipe variable which points to the work folder where npm
installed live-server
.
With this approach Greengrass doesn’t re-install the live-server module every time I deploy a new version of the com.example.ReactApp component.
Another advantage of using dependencies is that I can use the com.example.live-server component as dependency for serving other web app components. This makes my web app components simpler and live-server gets installed only once on a given device even if multiple web apps are being deployed.
What if two web app components specify different version of the live-server component as dependency? Greengrass only deploys a single version of a given component, hence Greengrass needs to resolve the dependency's versions to find the one to use. If there is a conflict, for example because I specified version 1.2.2 in one component and version 1.2.3 in the other, the deployment fails leaving what is currently running unaffected. I can then fix the dependencies and deploy it again.
That’s it for this time. In the next article I am going to show how to create a component that requires different binary artifacts for different architectures (amd64 and arm64).
Stay tuned.