Define a contract between autonomous teams
Mar 1, 2022 · 6 min readSo you have a bunch of autonomous engineering teams that are allowed to work in their own way. Fantastic. However, maybe you want to provide centralised support or build tooling that exposes things in a certain way. For example, maybe you want to build a department wide Open API documentation site for engineering teams and partners. You don’t want to enforce anything on the teams, such as making them all create specific folders in their repo. You do, however, need to know where certain files are.
One solution that a couple of us came up with over the last two years was called [acme]-json
. Note, I’ve used ACME here rather than the name of the company I worked for. We created an acme.json
file in the root of every git repo. This was the only mandate, the file had to exist. This single file provided a contract between all teams and automated tooling.
This single file provided a contract between all teams and automated tooling.
The project
The project had the following scope:
- Define a JSON Schema that was open to contributions from the community in the business. Teams were encouraged to add to the schema. We used conditional validation based on defined attributes, so we didn’t have many breaking changes.
- Provide an npm package that had a validator for the
acme.json
file. This allowed teams to validate theiracme.json
files were syntactically correct. - Rich documentation to explain what each attribute in the schema was used for.
- Ideally, do the heavy lifting in the first instance. We didn’t want teams to have a hurdle to adoption.
We defined a mission statement:
The
acme-json
project should be seen as a public contract between a team and the rest of the engineering department. The schema is there to expose internals (the way your team wants to work) to other teams, and tooling, so they can consistently understand information they need, without dictating an implementation to you.
The schema
The schema was iterated on over a number of weeks, by many of the engineering teams. A cut down version (for brevity and intellectual property reasons) is shown below.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://company-url.com/schemas/acme-json/v1#",
"type": "object",
"properties": {
"accounts": {
"description": "This describes all of your AWS accounts that you look after or use for this module.",
"type": "array",
"items": {
"$ref": "https://company-url.com/schemas/acme-json/v1#account"
}
},
"apiDocPaths": {
"description": "An array of locations that expose your Open API documentation files.",
"type": "array",
"items": {
"type": "string"
}
},
"ci": {
"description": "The details of your CI/CD instance.",
"type": "object",
"anyOf": [
{
"$ref": "https://company-url.com/schemas/acme-json/v1#ci"
}
]
},
"integrations": {
"description": "A list of integrations that consume this module.",
"type": "array",
"items": {
"$ref": "https://company-url.com/schemas/acme-json/v1#integration"
}
},
"moduleName": {
"description": "The official short name of the module.",
"type": "string",
"anyOf": [
{
"$ref": "https://company-url.com/schemas/acme-json/v1#moduleNames"
}
]
},
"productionBranch": {
"description": "The name of the branch where your production code resides. For example, in a GitFlow scenario where you have a 'develop' and 'main' branch, this would be 'main'",
"type": "string"
}
},
"required": ["moduleName", "productionBranch"]
}
I won’t go over each item, but some call outs:
- We could provide diagrams of all AWS account interactions via this file, in an automated way.
- We could build Open API reference documentation web sites based on the paths to the specifications in a repo.
- We had automated scripts to clone all the repos, use the
acme.json
contract to understand where the Open API Specifications sat in the repo, and scraped them into a website showing all the API endpoints.
- We had automated scripts to clone all the repos, use the
- We could document module interactions using config as code.
- Rather than manually updating Architecture diagrams, we could rely on teams to complete the information.
- For example we had the Tracking Product Team use
integrations
to onboard other modules into their system. Theacme.json
file was consumed by their Infrastructure as Code. This made theacme.json
file the driving force for contracts between teams.
The validator
As engineers we lint and validate everything. This was no different. We wanted the acme.json
file for a reason. Therefore we also needed those files to be “correct”. Therefore we created a validator that could be added to the CI/CD tooling the teams used. So, we created an npm package, written in JavaScript that validated the JSON file against the schema.
We followed semver release processes for the Schema and the Validator.
The documentation
We wanted to clearly document the intent of every attribute in the schema. So whilst we provided comments directly in the schema, we also made use of the GitHub wiki for the repo. Every attribute was explained in depth as to why it was there, what options were available, and how the attribute could be used.
Documentation can often be overlooked, but I firmly believe it can save you time and effort in the long run.
The mindset
Getting engineering teams to agree on things is hard. Getting them to do something they don’t want to do can be harder. When we started this project, there were around 300 repos in GitHub for our business. Trying to identify and co-ordinate all the owners for those repos was going to be a tall order. We agreed it would be easier to create the acme.json
file, populate where we could (based on other data points at our disposal), and raise a pull request.
We therefore set off to write a load of bash scripts to:
- Clone each repo in GitHub.
- Produce the
acme.json
file. - Add the validator to the
package.json
file. - Raise a Pull Request, linking to a centralised issue in our repo.
- This was a good idea, as it provided one long list of linked pull requests we could check were being merged.
With wide communication in the department, by the time the Pull Requests were raised, engineering teams just knew to approve and merge them.
Adoption
What did we end up using this project for? Here’s a few things:
- A full detailed list of AWS accounts owned by teams, that could be verified by our AWS Account Management team.
- This included what type of account it was: testing, production, etc.
- A schema that could show “flow” in our platforms. You could determine who had integrated with each module, and which AWS accounts talked to which AWS accounts.
- It provided a centralised touch point that allowed teams to structure their repos internally how they wanted, but still expose key details, such as their Open API Specifications for the API Documentation site.
- A common file that allowed our GitHub auditing CLI tool to understand what branches to audit in GitHub.
- It also had data points that allowed us to route failures in the audit to Engineering Directors.
- Teams consumed the file in AWS CDK to help build and deploy their infrastructure.
- The automated release processes used the file for the
productionBranch
knowledge.
Summary
This post provided a summary of what we did in order to allow teams to be autonomous and work in their own ways, whilst still providing enough structure to automate department wide initiatives. As with all things in life, it wasn’t as straight forward as the post makes out, but that’s half the fun isn’t it!