Progressing through Environments with Uniform
16th Dec 2021
When deploying anything other than a simple POC or personal website, a reliable application will need Environments. What is the right pattern for implementing environments for local developers as well as shared ones like staging and production?
Let's look at how Uniform separates your configuration and content. Then look at how we can migrate both to new higher environments as a feature progresses through it's software development workflow.
Uniform Project
Within Uniform the "Project" is the recommended way to separate configuration and content between environments. The screenshot below shows three projects used for my demo website.
When I am developing a new feature I use the "hiddenbeaches" project. I will use the Uniform website to add new components or compositions. I would use it to make changes using the Parallel Change / Expand and Contract workflow.
Once new features are ready the CI process can push new changes into the higher level projects "hiddenbeaches-staging" and "hiddenbeaches-prod" using the Uniform CLI.
Workflow
Let's look at the workflow and what scripts are needed to be able to implement it.
Step 1. Make Changes within the dev project within the UI
My first step is to make the changes I need within the development project (hiddenbeaches).
Step 2. Pull down the changes into local yaml files
I will then run a couple of Uniform CLI scripts which will update a local set of yaml files. These yaml files will be the single source of truth for each component's configuration. They should be committed to the repository and be versioned along with the code that uses those components.
You can pull down components with:
uniform canvas component pull uniform/components
I also pull down my local development compositions to a local set of yaml files. I keep a curated set of composition yaml files in a uniform/preview
and uniform/prod
folder. These are useful for the following reasons:
- I push up the preview compositions for any Pull Request or QA environment. This enables end-to-end tests to be run and I can have a higher level of confidence that merging these changes will not break higher environments.
- I push up the prod compositions in a create only mode when deploying into production.
The pull command should download the yaml files to a location which is not committed to version control but I do commit the curated preview and prod yaml files to my git repository.
You can pull down compositions with:
uniform canvas composition pull uniform/compositions
Wrap both into a single npm script with npm-run-all
"uniform:pull": "run-p uniform:pull:components uniform:pull:compositions","uniform:pull:components": "uniform canvas component pull uniform/components","uniform:pull:compositions": "uniform canvas composition pull uniform/compositions",
(Optional) TypeScript and Code Generators
I use TypeScript in my project and have got the Contentful Code Generator installed. I have also written a script which will convert these local yaml files into TypeScript Types. Once I have updated my component yaml files, I also run my code generators.
Step 3. Update Codebase to support new features
In my example I created a new "Banner" component. I used the new generated TypeScript type BannerComponent
and added it to the resolveRenderer
.
import { VFC } from 'react'import { BannerComponent } from '<>/generated/uniform'export const Banner: VFC<BannerComponent> = ({ entry }) => {return <section>{entry.fields.name}</section>}
Step 4. Push New Configuration in CI Build
When these new changes are committed to the repository and pushed. My CI build uses the Uniform CLI to push them to the appropriate project based on what environment I am building for.
I have already explained some of the thinking behind what I do during this step, but let's look into it with a little more detail.
Component configuration
The new component configuration should have been implemented using the Parallel Change / Expand and Contract workflow. This means that I can mirror all component configuration within the yaml files to the new environment.
⚠️ Mirror mode will change, update and delete component configuration to reflect the yaml files. Mirror is the default mode when using the
push
command.
The Uniform CLI command to push local yaml configuration in the uniform/components
folder looks like this:
uniform canvas component push uniform/components
Pull Request or QA Branches
If the build is running for a Pull Request or some other QA branch, I want the curated set of content to be on the website. I will push composition data in the "mirror" mode, this will create, update or delete any content within this Uniform Project to match the local yaml files.
The Uniform CLI command to push local yaml configuration in the uniform/preview
folder looks like this:
uniform canvas composition push uniform/preview
Production (Like) Branches
If the build is running for a Production branch then there are a few things to consider:
- I don't want to change and existing content which is maintained by someone else
- I do want to create new content which is curated for production and might be needed by the build to complete
To achieve this I use the following Uniform CLI command:
uniform canvas composition push uniform/prod --mode create --state published
All the Push Scripts Together
To bring this all together, here are the scripts which I use during the build.
Here are my commands using npm-run-all
:
"uniform:push": "run-s uniform:push:components uniform:push:compositions","uniform:push:components": "uniform canvas component push uniform/components","uniform:push:compositions": "if [ $VERCEL_ENV = 'production' ]; then run-s uniform:push:compositions:prod; else run-s uniform:push:compositions:preview; fi","uniform:push:compositions:prod": "uniform canvas composition push uniform/prod -m create","uniform:push:compositions:preview": "uniform canvas composition push uniform/preview && uniform canvas composition push uniform/preview --state published",
Roundup and Further Thoughts
This workflow provides the reliability of having gates between environments. It gives me the chance to run checks before allowing code to progress and ensures that my customers receive high quality software delivered to their fingertips!
Looking to the future, I will be considering a further level of encapsulation for Pull Requests. It would be nice to create ephemeral projects which a created and destroyed as needed. This will give me further confidence when merging Pull Requests.