A project to help define architecture logically as code, and generate living, interactive diagrams.
MIT License
A project to help define architecture logically as code, and generate living, interactive diagrams.
Run npx aac-cli demo
to quickly see this project in action!
We define infrastructure as code, configuration as code, and more - but what about architecture? The goal of this project is to allow teams to collaborate on architecture as code, and build up-to-date, interactive diagrams from existing systems or assets.
To quickly demo this project, just run:
npx aac-cli demo
The goals of this project are to allow a team to define architecture declaratively, as simple yaml
files, and have interactive diagrams and visualisations generated automatically.
At a high level:
A team should be able to use a yaml
file to represent logical or physical architecture. There are three types of entities:
Components
A component is any logical or physical entity. It might be a system, such as an ESB, a server, a switch, and storage service, and so on.
Containers
A conatainer is any logical grouping of components. It could be a logical container (such as 'Front End Services'), or a technical container (such as an AWS region).
Connections
A connection is a relationship between components or containers. It could be logical, such as a dependency between a service and a database, or a technical connection, such as a potential HTTP connection between a mobile app and a web server, via a gateway.
Level
A 'level' is an optional concept which may represent a 'zoom' of the diagram. For example, level 1 in a diagram might be the high level architecture. Level 2 might be the specific services and systems, level 3 might be the physical infrastructure on which an instance resides. By providing levels, diagrams should be able to be 'zoomed' as needed.
Aspect
An 'aspect' is a 'view' of the system. For example, the infrastructure aspect might only show physical and network infrastructure for the on-premise data center, which the 'cloud' aspect might show only the cloud components.
The in-memory structure may be best represented as a graph.
Containers, components and connections should be rendered in a simple 'boxes and lines' fashion.
It should be possible to give a container, component or connection a 'type', from a pre-defined library. For example, a specific type might be an 'EC2 Instance'. Each type has a schema, of required and optional fields. Each type should also have an associated 'renderer', which allows the type to be rendered in a specific way (for example, an EC2 instance might be rendered with the AWS EC2 logo, with the instance name at the lower side and the instance type at the upper right side).
It should be possible to associate arbitrary metadata with entities, to allow custom rendering if needed.
It should be possible to specify 'levels'. Levels would
Below are a few examples, highlighting a proposed format.
The code might look like this:
title: Three Tier Application
author: Dave Kerr
---
container:
id: channels
name: Channels
container:
id: services
name: Services
container:
id: databases
name: Databases
component:
name: Mobile App (iOS)
container: channels
component:
name: Mobile App (Android)
container: channels
component:
name: Website
container: channels
component:
id: api_gateway
name: API Gateway
component:
name: Application Server
container: services
component:
name: Application Database
container: databases
connection:
from: channels
via:
- api_gateway
to: services
connection:
from: services
to: databases
The resulting diagram should be rendered as something like this:
Some additional features would would be useful:
Enforce policies. Go to AWS, highlight any resource which is not part of an architecture schema. Allows existing environments to be audited, e.g show me resources which don’t fit into my defined architecture. This is potentially quite a useful feature, allowing an existing cloud set up to be audited against a well defined architectural spec.
Current solutions which are similar in nature:
model.yaml --> validator --> warnings, errors, model.json
model.json --> inflator --> warnings, errors, model-inflated.json
model-inflated --> renderer --> visualisation
Other options:
A model is simply the yaml
representation of an architecture. This representation is designed to be as easy as possible to be created and maintained by a human. Therefore, we want to provide a simple, declarative schema to define architectures.
However, to render a model, we must first build an intermediate structure, which is the 'compiled model'. The reason that the model must be compiled is that there are elements of a model which do not have to be defined in the yaml
, but which are still required to be rendered. This compiled model is built with the compile
function. The compiler will:
id
to every entity which does not have one explicitly definedroot
entityThe following features of the compiler are not yet completed:
parent
reference, parent entities have a children
reference. NOTE: This brings in a small challenge which is that the compiled model cannot be serialized as a POJO, due to potential circular references. One simple approach to deal with this would be to unlink the model by replacing parent
with parentId
, children
with childrenIds
and so on. This would be straightforward to read in the serialized compiled model, and straightforward to re-link after loading the model.Some ideas for the CLI interface:
aac validate # validate a model
aac render # render a model
aac import # create a model from an external structure
aac diff # compare models, or a model to an external structure
The key APIs are documented below.
Validates the structure of a model YAML, and returns the in-memory representation of the model:
const modelYaml = fs.readFileSync('model.yaml', 'utf8');
const { model, errors, warnings } = validate({ modelYaml });
console.log(`${errors.length} errors`);
console.log(`${warnings.length} warnings`);
console.log(`Model: ${JSON.stringify(model, null, 2)}`);
Common tasks can be run from the makefile
Command | Usage |
---|---|
make test |
Lint and test the code. |
make circle |
Install the CircleCI CLI and run the CircleCI build locally. |
To create a release:
cd aac-cli
npm run release && git push --follow-tags && npm publish
aac ./some/file.yaml
shows no output - it should demand a commandmeta
field on anything, just key value pairs. Use it as a 'dumping ground' (e.g. platform: as400
) when building models, until you find a structured location for it. When rendered, available as a tooltip.aac demo
- creates a three-tier architecture, renders and watches it, with an informative message. One command to show all the good stuff.validate
should give a unique id to all elements