Autonomous Development

The examples provided in this section are based on the motivations of Autonomous Development, Authoritative Release.

Imperative Build, Declarative Deployment

Creating an artefact for compiled languages is well understood, and is an integral part of software delivery for languages such as .NET, Java and Typescript, however, for interpretive languages (Python, Ruby, PHP, Javascript), because the code in source control can be run without a “build”, it is tempting to deploy from source control. This has the following challenges:

  • Fulfilling dependencies in production environment, can have network issues and, even with lock files, can result is different runtime outcomes.
  • Manual steps are required as branches are used to separate environments, e.g. test, staging, production. Which requires deploy-time developer effort and can lead to errors, i.e. untested code being merged into production.

Package & Publish

Resolving dependencies at build time, adding any other runtime components and creating an immutable package for deployment can be achieved using the CDAF technology-agnostic package mechanism. The “build” artefact completes the development team’s Continuous Integration (CI) stage.

The Continuous Delivery (CD) would be limited to automated testing of the package, and then publication. Publication can be to a Container Registry, Package Registry (Nexus, Artifactory, Azure DevOps, GitLab, GitHub, etc.) or a proprietary asset registry such as Octopus Deploy or Mulesoft AnyPoint Exchange. The following example uses a Container Registry.

The following overview has two examples, one using the CDAF release package with automated testing, and one performing direct image build and push.

  • pip resolves Python dependencies, and gathers these, along with helper scripts, to produce a release package. The release package is then used to construct a runtime image, which in turn is smoke tested using docker-compose. The tested image is then pushed to the registry.

  • NPM resolves NodeJS dependencies, builds an image and pushes it to the registry.

graph LR

  subgraph python["Python"]
    python-git[(Git)]
    python-build-artefact[(Build)]
    python-release.ps1
    subgraph docker-compose
      image-container
      test-container
    end
    push
  end

  subgraph node["NodeJS"]
    node-git[(Git)]
    node-build
    node-push["push"]
  end

  registry[(Docker Registry)]

  python-git -- "CI (pip)" -->
  python-build-artefact -- "CD" --> 
  python-release.ps1 -->
  image-container -->
  push --> registry
  test-container -. "smoke test" .-> image-container

  node-git -- "CI (NPM)" -->
  node-build -->
  node-push --> registry

classDef dashed stroke-dasharray: 5, 5
class python,node dashed

classDef dotted stroke-dasharray: 2, 2
class docker-compose dotted

classDef blue fill:#007FFF
class registry blue

Note: the Python release.ps1 is an intermediary artefact, and not used to deploy to the runtime environments.

Subsections of Autonomous Development

Authoritative Release

Declarative Deployment

A declarative deployment ensures all components are Released in a predictable way, with the assurance the same combination of component versions that were tested align with what is released.

Release Manifest

The release contains a manifest of components and their version. This is the release declaration. The deployment is responsible for ensuring these components are applied at as declared at each promotion stage, e.g. test, staging, production. In the flow below, the release is continuously deployed through to staging, but continuously deployed, i.e. gated, to production.

For each deployment, the same image is used to create the running container.

flowchart LR
  registry[(Docker Registry)]

  subgraph test
    p1["Python v0.2.135"] ~~~
    n1["NodeJS v1.0.3"]
  end

  subgraph staging
    p2["Python v0.2.135"] ~~~
    n2["NodeJS v1.0.3"]
  end

  subgraph production
    p3["Python v0.2.135"] ~~~
    n3["NodeJS v1.0.3"]
  end

  test -- "auto promote" --> staging
  staging -- "gated promote" --> production

  registry --> test
  registry --> staging
  registry --> production

classDef blue fill:#007FFF
class registry blue

Tests as a Product

Declarative Deployment

For context see Behaviour Tests as a Product. The development of test automation is autonomous and is not tightly coupled with the solution development. When the solution is ready to execute a set of tests, the versioned package is obtained and executed.

Environment Definitions

Configuration Management within the source allows for test execution by simply invoking the test package for the desired environment, e.g. ./package.ps1 PROD, or targeted testing ./package.ps1 PROD_WEB

context  target      testSuite       featureSet  resources                                 secrets
local    PROD_CLASS  SpecFlow.Specs  data        supabase                                  creds_prd
local    PROD_WEB_1  Selenium.Specs  login       https://example.com/Calculator.html       vault_prd
local    PROD_WEB_2  Selenium.Specs  dashboard   https://example.com/Calculator.html

local    TEST_CLASS  SpecFlow.Specs  data        supabase                                  creds_tst
local    PROD_WEB_1  Selenium.Specs  login       https://test.example.com/Calculator.html  vault_tst
local    PROD_WEB_2  Selenium.Specs  dashboard   https://test.example.com/Calculator.html

The delivery phase of the pipeline pushes the package to a registry, with a semantic version. It is these versions that the solution delivery process can consume to provide predictable test results.

graph LR

  subgraph Component A
    Rbuild["Build"] -->
    Rtest["Test"] -->
    Rpublish["Publish</br>1.0.1"]
  end
  subgraph Component B
    Pbuild["Build"] -->
    Ptest["Test"] -->
    Ppublish["Publish</br>0.5.9"]
  end
  subgraph Test Product
    Sbuild["Build"] -->
    Stest["Test"] -->
    Spublish["Publish</br>1.2.0"]
  end

  subgraph Release
    TEST:::release
    PROD:::release
  end

  store[(Registry)]
  
  Rpublish --> store
  Ppublish --> store
  Spublish --> store
  store --> TEST
  TEST --> PROD

classDef release fill:lightgreen

In this example, current production tests are at version 1.1.0. While the next release of 1.2.0 is in progressing through the development lifecycle, the scheduled tests can still consume version 1.1.0 with confidence. The scheduled tests may have a reduced scope, i.e. web only, this is where the ability to select a subset is valuable, e.g. ./package.ps1 PROD_WEB.