UI Composition with Yarn

U

In previous posts I introduced how I sliced up my architecture using a composite user interface at the top.

In this post I'll explain in more detail how I use the Yarn package manager with Github Actions and Github Packages to achieve a practical workflow for packaging and composing apps.

Set up a repository

For each of the level 3 business capabilities identified I will set up a github repository to contain the source code of the web components exposed by a particular service supporting the capability, e.g. organizationalplanning.service.components. This particular repository holds the web component code required to plan the organizational structure for next season in our sports club.

Setting up a yarn workspace

Each capability will expose multiple web component packages. At the very least there will be a separation between the read side and the write side, but there can be more.

Yarn is one of the only javascript package managers that supports having multiple packages per repository by means of it's workspace feature. To set up such a workspace, you need to add a package.json file at the root of the repository. This file specifies to the package manager where the folders for each of the packages are. My folders are in a parent folder called _apps, for reasons I will explain in a future post, but you can name it whatever makes sense for you.

{
  "private": true,
  "name": "clubmanagement.organizationalplanning",
  "workspaces": [
    "_apps/*"
  ]
}

As I'm using private github repositories for my component code, I will also need to tell yarn about the existance of my private package feeds. There is a file called .yarnrc to configure yarn. For every package in the @messagehandler scope, yarn will go search for it on https://npm.pkg.github.com the others will be resolved from https://registry.yarnpkg.com.

registry "https://registry.yarnpkg.com"
"@messagehandler:registry" "https://npm.pkg.github.com"
--*.modules-folder "_yarn"
version-sign-git-tag false

Besides setting the package feeds, I've also configured it to put any downloaded dependency in the _yarn folder (node_modules is the default). During the build git tag based versioning will be turned off so that packages inside the workspace can be versioned independently, versioning packages individually will be taken care of in the upcoming build automation section.

Package definitions

In my _apps folder, there is a subfolder per exposed package. E.g:

  • organizationalplanning for the read side components
  • organizationalplanning.admin for the write side components.

Inside each of these folders there is another package.json file describing the content of each respective component package. E.g. the package.json for organizationalplanning looks like this.

{
  "name": "@messagehandler/clubmanagement.organizationalplanning",
  "version": "1.0.22",
  "main": "/js/clubmanagement.organizationalplanning.app.js",
  "repository": {
    "type": "git",
    "url": "https://github.com/MessageHandler/organizationalplanning.service.components.git",
    "directory": "_apps/organizationalplanning"
  },
  "author": "Yves Goeleven <yves@goeleven.com>",
  "license": "UNLICENSED",
  "publishConfig": {
    "registry": "https://npm.pkg.github.com/"
  },
  "dependencies": {
    "clubmanagement.shell": "npm:@messagehandler/clubmanagement.shell",
    "clubmanagement.authentication": "npm:@messagehandler/clubmanagement.authentication",
    "clubmanagement.authorization": "npm:@messagehandler/clubmanagement.authorization",
    "messagehandler.eventsourcing": "npm:@messagehandler/messagehandler.eventsourcing"
  }
}

The package definition contains the following attributes:

  • name: this represents the package name clubmanagement.organizationalplanning in the @messagehandler scope that will identify the package after uploading it to the package feed.
  • version: each package is versioned independently by yarn during the build.
  • main: this is the package entry point javascript file. Irrelevant for this use case.
  • repository: this attribute points to the code repository and directory in that repository where the code for the package resides.
  • author: yours truly.
  • license: as this is proprietary code, it requires an UNLICENSED configuration.
  • publishConfig: this is the github package feed where yarn will push the package to during the build.
  • dependencies: these are other packages that the current package depends upon. All are in the @messagehandler scope and therefore resolved from private repositories in github because of the .yarnrc configuration setting for the scope.

Build automation

Once the package definitions are set, yarn will need to actually create the packages and publish them to the private package feed. The Github Actions infrastructure makes this fairly easy. In the .github\workflows\ folder of your repository, you can add .yml files for each action that you want to run.

Here is an example action where it first checks out the repository on an ubuntu machine, then installs node.js and yarn , which in turn get used to update the version in package.json followed by publishing a new package to the github registry https://npm.pkg.github.com/ in the @messagehandler scope.

name: publish organizationalplanning package

on:
  push:
    branches:
      - master
    paths:
      - '_apps/organizationalplanning/**'

jobs:
  publish:
    runs-on: ubuntu-latest
    env:
        working-directory: ./_apps/organizationalplanning
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 12
          registry-url: https://npm.pkg.github.com/
          scope: '@messagehandler'
      - run: | 
            yarn install
            git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com" && git config --global user.name "$GITHUB_ACTOR"
            yarn version --patch
            git pull --rebase
            git push
            yarn publish
        working-directory: $
        env:
          NODE_AUTH_TOKEN: $

Note: the git pull --rebase command is there because multiple actions will run in parallel and each will update the version of it's component resulting in conflicting commits. Recently however Github Actions introduced a way to chain actions together, which is probably a better way to isolate the builds.

After committing, pushing and running the action a new package version should appear on the repository's package feed.

Github Packages

Composing the app from packages

Once the packages have been published on the Github Packages feed, you can start to use them from within your apps.

The process is similar to setting up an individual package. Make sure you have the .yarnrc file copied in your app and add a package.json file which describes the dependencies of your app.

{
  "name": "@messagehandler/clubmanagement.coordination",
  "version": "1.0.0",
  "main": "index.js",
  "repository": "https://github.com/MessageHandler/coordination.clubmanagement.io.git",
  "author": "Yves Goeleven <yves@goeleven.com>",
  "license": "UNLICENSED",
  "dependencies": {
    "clubmanagement.shell": "npm:@messagehandler/clubmanagement.shell",
    "clubmanagement.authentication": "npm:@messagehandler/clubmanagement.authentication",
    "clubmanagement.authorization": "npm:@messagehandler/clubmanagement.authorization",
    "clubmanagement.coordination": "npm:@messagehandler/clubmanagement.coordination",
    "clubmanagement.segmentation": "npm:@messagehandler/clubmanagement.segmentation",
    "clubmanagement.organizationalplanning.admin": "npm:@messagehandler/clubmanagement.organizationalplanning.admin",
    "clubmanagement.profile.admin": "npm:@messagehandler/clubmanagement.profile.admin",
    "clubmanagement.roster.admin": "npm:@messagehandler/clubmanagement.roster.admin",
    "messagehandler.eventsourcing": "npm:@messagehandler/messagehandler.eventsourcing"
  }
}

After configuring the dependencies you can call yarn install to download them into your local folder. Once new versions become available you can upgrade them using the yarn upgrade --latest command.

And now your web components are available for use in your app.

In future posts I will come back to other pieces of infrastructure code that sit between downloading and actually using specific web components from the downloaded files, more specifically the static site generator I use and the feature toggle infrastructure.

How did you implement UI composition?

I am very interested to hear how you are handling UI composition in your projects, so don't hesitate to reach out!

About the author

YVES GOELEVEN

I've been a software architect for over 20 years.

My main areas of expertise are large scale distributed systems, progressive web applications, event driven architecture, domain driven design, event sourcing, messaging, and the Microsoft Azure platform.

As I've transitioned into the second half of my career, I made it my personal goal to train the next generation of software architects.

Get in touch

Want to get better at software architecture?

Sign up to my newsletter and get regular advice to improve your architecture skills.

You can unsubscribe at any time by clicking the link in the footer of your emails. I use Mailchimp as my marketing platform. By clicking subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp's privacy practices here.