Architecture area

Platform API & operators

Component is just an identity. Every capability, such as a deployment or a database, is its own resource that points back to it, reconciled by its own controller.

The shape

Component payments sits at the centre. It owns two resources, its GitHubRepository and its ScaffoldRequest. Independent resources point back to it through componentRef: a Release for dev, a Release for prod, a Database for dev and that database's DatabaseSchema. Each is reconciled by its own controller. Component/payments name · owner · repository · scaffold component-operator OWNED · deleted with it INDEPENDENT · point back by componentRef GitHubRepository Crossplane: repo + CI webhook ScaffoldRequest scaffold-operator: one commit owns Release/payments-dev release-operator Release/payments-prod release-operator Database/payments-db Crossplane: RDS in dev DatabaseSchema/payments schema-operator Readers Backstage · Argo CD labels query by componentRef / label
Scroll sideways to see all of it. Highlighted arrows are componentRef: the related resource knows its Component, never the other way round.

How it works

  1. Component is an identity record

    A name, an owning team, a repository and the template it was scaffolded from. It has no fields for deployments, databases or anything else a service might use.

    kind: Component
    metadata: {name: payments}
    spec:
      owner: payments-team
      repository: {name: payments, visibility: public}
      scaffold: {template: golang-service, version: "0.13.0"}
  2. Everything else points back

    A Release, a Database or a DatabaseSchema names its Component in spec.componentRef. That reference is the source of truth; the platform.taskapp.io/component label used for querying is derived from it.

    kind: Release
    metadata: {name: payments-dev}
    spec:
      componentRef: {name: payments}
      environment: dev
      autoDeploy: {branch: main}
  3. Controllers look things up when they need them

    Each controller reconciles only its own type. When it needs shared information it reads the Component: release-operator, for example, looks up the repository to deploy from, and waits until the Component is ready. schema-operator reads the Component for the repository and the Database for where its credentials live.

  4. Readers discover, nobody registers

    Backstage, Argo CD lookups and dashboards find everything about a service by componentRef or the derived labels. Adding a new kind of capability doesn't change the Component or any list of what it has.

The operators

component-operator

Gives a Component its GitHub repository (a Crossplane GitHubRepository) and a ScaffoldRequest, and owns both.

scaffold-operator

Renders a versioned template into the new repository as one commit, once. See Golden-path scaffolding.

release-operator

Turns a Release into the deployment files Argo CD reads, resolves its bindings, and follows main for auto-deploy. It writes to Git, never to a workload cluster.

schema-operator

Turns a DatabaseSchema into a pointer to the service's versioned migration package, which Atlas applies in the workload cluster. Also writes only to Git. See Database schemas.

Crossplane

Not one of the four, but the same pattern: GitHubRepository and Database are Crossplane APIs, shipped as versioned packages, that reconcile into GitHub and AWS.

Design choices

A small API, not a mega-CRD

One resource with fields for every capability would turn into an orchestration engine. Separate resources let a deployment, a database, its schema or a future queue evolve, version and fail independently.

Reference, don't register

A Component keeps no list of what belongs to it, so adding a capability never touches it, and nothing can drift out of sync.

Trade-off: no single object shows everything about a service; readers such as Backstage assemble the view.

Exceptions are written down

The repository and the scaffold request are owned by the Component, because a service without its repository isn't onboarded. Ownership means cascade deletion, so each exception is documented with its consequences before it's built.

Trade-off: deleting a Component doesn't remove its releases, databases or schemas; those are separate, deliberate changes.

The reference is the truth, labels are derived

Labels are only ever derived: component-operator stamps them on what it creates, and the Argo CD Application for every Release, Database and DatabaseSchema takes its label from componentRef. A hand-set label can't attach a resource to the wrong service.

Not built yet

  • More capabilities. Queues, caches and buckets fit the same model as Database; none exist yet.

Code