Publishing Frontend Features Like a Greek God
Tags: piral, azure, devops, microfrontends
Published: 2020-02-28 13:00:14

*Photo by [Amélie Mourichon](https://unsplash.com/@amayli?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on Unsplash.*

> This story is part of the [Applied Cloud Stories](http://aka.ms/applied-cloud-stories) initiative by Microsoft.

Build and release pipelines - who does not love them? Just push your code and see how a magic dance begins. Various systems that are connected just by some configuration will start processing our code.

There are many ways to properly implement a pipeline that continuously performs builds, essential validations, and tests. We can set up a set of scripts. We can use tools such as Jenkins. We can go to a service provider. In this post I'll go for [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) to set up a pipeline for continuous deployments of frontend modules also known as microfrontends.

For the microfrontends we'll use [Piral](https://github.com/smapiot/piral).

## Azure + Azure DevOps = :heart:

Using Azure DevOps we get a lot of features right away - and most of them actually for free. One of the cool features of Azure DevOps is called "Azure Artifacts". This provides a registry for most of the well-known package managers such as NuGet, Maven or [NPM](https://www.npmjs.com).

While Azure DevOps is actually cloud agnostic it certainly comes with the best integration of Microsoft Azure. This should not be of any surprise - everyone would do the same. So when I say it's as easy as it can get to deploy anything to Azure using Azure DevOps I fully mean it.

Using [YAML](https://yaml.org) we can write build pipelines that are powerful and easy to manage:

```yaml
trigger:
  branches:
    include:
    - release
    - dev

pool:
  vmImage: 'ubuntu-latest'

steps:
- task: Npm@1
  displayName: 'npm install'
  inputs:
    verbose: false

- script: |
    npm run build
  displayName: 'npm build for DSP'
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/release'))

- task: Npm@1
  displayName: 'npm publish (DSP)'
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/release'))
  inputs:
     command: publish
     publishRegistry: useFeed
     publishFeed: NPM-Feed

```

Likewise, secrets can be managed in an [Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/). These secrets can be then used via "libraries" in Azure DevOps pipelines.

The libraries view is shown below.

![Azure Pipelines Libraries](https://dev-to-uploads.s3.amazonaws.com/i/us2yyihxifcrlpiddbw7.png)

The secrets can be connected directly to Azure Key Vault. The latest value will be retrieved when a pipeline using this variable group is run.

![Azure Pipelines Variables Group](https://dev-to-uploads.s3.amazonaws.com/i/1b6h0i5rigr3wk9youft.png)

The configuration file only requires a single addition. Insert this snippet before the `steps` section.

```yaml
variables:
- group: Pilet Config
```

For instance, it is possible to use these variables in another task that only kicks in if we pushed to the `dev` branch.

(Furthermore, we may want to modify the version in the *package.json* when we publish from the `dev` branch, so I've added a small shell script to accomplish that)

```yaml
steps:
- script: |
    sed -i -e "s/\(\"version\":\\s\+\"\([0-9]\+\.\?\)\+\)/\1-pre.$(Build.BuildNumber)/" package.json
  displayName: 'Preview version (CI)'
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/dev'))

- task: Npm@1
  displayName: 'npm publish (CI)'
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/dev'))
  env:
    PILET_PUBLISH_KEY: $(PILET_PUBLISH_KEY)
    PILET_PUBLISH_URL: $(PILET_PUBLISH_URL)
  inputs:
    command: custom
    verbose: false
    customCommand: 'run publish-pilet'
```

We will see later on that such a secret management makes sense to be used for deploying securely.

For now we only use the secret as environment variables to directly deploy from our Azure DevOps build pipeline.

Running the pipeline shows us if everything could be run successfully.

![Azure Pipeline Result](https://dev-to-uploads.s3.amazonaws.com/i/0ihsg3klk3mzrmdzidzr.png)

## Microfrontends + Piral = :rocket:

The features for our frontend application are packaged in little modules called [pilets](https://docs.piral.io/reference/specifications/pilet-specification). A pilet is just an NPM package containing different resources that should be used in our larger frontend application, which follows the [microfrontends](https://dev.to/florianrappl/microfrontends-based-on-react-4oo9) pattern.

By using [Piral](https://github.com/smapiot/piral) we simplify the whole development approach. Essentially, this boils it down to

- decide on a basic set of components ("pattern library")
- provide an app shell with a layout and some shared dependencies
- scaffold, write, and deploy individual pilets for each feature

The whole flow looks like the following diagram.

![Development Flow](https://dev-to-uploads.s3.amazonaws.com/i/z5cs37fm97dyb3hmsmcc.png)

After the application shell reaches a certain maturity we start the module factory. Beforehand, we may develop one or the other module to see if our application shell has already reached a point where it makes sense to start producing microfrontends.

There are many articles our there how the development of an app shell and its modules is done. A good starting point would be [introduction to microfrontends with Piral](https://dev.to/florianrappl/introduction-to-microfrontends-with-piral-4mpp).

To summarize the development approach quickly: We would start by using the scaffolding capability.

```sh
mkdir my-app-shell
cd my-app-shell
npm init piral-instance
```

After filling out the survey we can make the necessary adjustments (if any).

Once done we can push the code to some repository and trigger a CI/CD process involving:

```sh
piral build
```

This command will produce two artifacts:

1. In `dist/release` we'll find the plain files (html, css, js, ...) to publish our frontend.
2. In `dist/develop` we'll find a *.tgz* file that could be pushed to an NPM repository.

The latter allows using the created Piral instance as an emulator for running microfrontends based on this app shell locally.

As an example, if we pushed the package `my-app-shell` to a private Azure DevOps NPM registry we could scaffold a new microfrontend just like:

```sh
mkdir my-first-pilet
cd my-first-pilet
npm init pilet
```

where we would use `my-app-shell` as the name of the application shell. Importantly, when the address of the registry is asked we'll need to provide the custom address. You can find it in Azure DevOps in your artifacts when you hit "Connect to Feed" and "NPM".

![Azure DevOps Address NPM Feed](https://dev-to-uploads.s3.amazonaws.com/i/ftlem9hzx67hc0hoq99v.png)

At this point we could happily develop the pilet, place an `azure-pipelines.yml` and go home.

But where does Piral meet Azure DevOps?

## Azure + Microfrontends = :package:

Deploying microfrontends in Azure using Piral and Azure DevOps boils down to using the Azure Artifacts. Our build pipeline can take care of releasing the packages into the NPM feed.

Working in distributed teams using Azure DevOps will potentially resolve in certain projects being visible or not. Azure DevOps also distinguishes between:

- the NPM feed of the organization (global feed)
- an NPM feed for a project (local feed, may be public, too)

The idea is to use the organization feed for releasing pilets. A release pipeline picks up changes to the NPM feed and - in case of a matching package - triggers a release.

The release could go directly against the [Piral Feed Service](https://feed.piral.io).

Using releases within Azure Pipelines makes it possible to define multiple environments ("stages"). Each stage may be passed automatically, manually, or when certain conditions (e.g., approval gates) are met.

Let's see how it looks with four environments (functional acceptance, systems integration, quality assurance, and production).

![Azure DevOps Releases](https://dev-to-uploads.s3.amazonaws.com/i/o59ynqx9m0x7o68tipi7.png)

The definition of a release is quite straight forward. We create an artifact trigger that is sensitive to the organizational NPM feed. This way we provide teams the capability of pushing their own frontend modules.

The image below shows the settings used for the primary artifact trigger. Only this triggers automatically when the artifact changes.

![Azure DevOps Artifact Trigger](https://dev-to-uploads.s3.amazonaws.com/i/ugydbloos8epind1n9pr.png)

Likewise, we have tooling in place that automatically creates a release pipeline when a certain package naming condition was found.

We also bring in a shell script from an operational repository. This shell script uses the [Piral CLI](https://github.com/smapiot/piral) for publishing the package to an environment specific feed using the API key taken from the variable group.

The result is a beautiful pipeline. In our definition we go automatically from FAE to OAE, however, for the other two environments we need either to manually trigger (QSS) and / or to have the approval of the required gate keepers (PROD).

![Azure DevOps Release Definition](https://dev-to-uploads.s3.amazonaws.com/i/zaph43vr0tvybo8t6s07.png)

Green is good :rocket:!

## Deployment - Trouble = :smiley:

The whole approach so far is quite close to a deployment of (micro)services using docker containers.

Let's compare:

- Initial phase (check :white_check_mark:):
  * Building code for Docker image **vs**
  * Building code for NPM package
- Packaging (check :white_check_mark:):
  * Docker image creation (Dockerfile) **vs**
  * NPM package creation (package.json)
- Providing (check :white_check_mark:):
  * Docker container registry **vs**
  * NPM package registry
- Publish (check :white_check_mark:):
  * Trigger on container tag or version **vs**
  * Trigger on package tag or version
- Run (check :white_check_mark:):
  * Start the container via the entrypoint **vs**
  * Load the pilet via its root module

This way we can push features (i.e., modules) of our frontend the same way as with features (i.e., services) of our backend. Simple, fast, reliable.

## Conclusion

What are you waiting for? Take a look at [Piral](https://github.com/smapiot/piral) and improve your [Azure DevOps](https://azure.microsoft.com/en-us/services/devops/) setup. Create build and release pipelines that are fun to use and efficient in usage.