DEV Community

Harper Xu
Harper Xu

Posted on

Don't Promote the Sketch to Topology

A generated architecture is still only a sketch today. You should not promote that sketch to topology.

Cheap generation makes those diagrams look fully complete. Completeness is not the same as real ownership.

You have seen this exact failure before. A model draws boxes and arrows overnight. The picture names services you never approved.

Pretty labels then hide the true blast radius. That picture is not architecture at all.

Treat it as a sketch that demands review. The review itself becomes the actual product.

You do not start this work with tools. You start with constraints that cannot move.

Constraints are the walls of the building. Data flow is plumbing through those walls. Failure domains are the rooms that flood together.

What you change next is the renovation plan. A sketch that skips these questions is decoration. You must treat decoration as untrusted input.

Imagine a planner who trusts a tourist map. The map is pretty and mostly wrong. You would not pour concrete from it.

Generated topology works in that same way. You pin constraints, then interrogate every arrow.

Write the walls down

You write constraints before rereading the sketch. The contract file lives inside your repository. Version history is part of the review.

This file should feel boring on purpose. Boring files survive an actual review board. Pretty diagrams rarely survive first contact.

# arch/constraints.yaml
system: checkout-api
reviewer: on-call-platform
constraints:
  - id: C1
    statement: "PII stays inside the payment VPC"
    movable: false
  - id: C2
    statement: "Writes go through one outbox table"
    movable: false
  - id: C3
    statement: "Public ingress is HTTPS only"
    movable: false
data_flows:
  - from: edge-proxy
    to: checkout-api
    data: "cart, session cookie"
    auth: "mTLS"
    failure_domain: edge
  - from: checkout-api
    to: payments
    data: "amount, merchant_id"
    auth: "signed JWT"
    failure_domain: payments
owners:
  edge-proxy: platform
  checkout-api: commerce
  payments: fintech
blast_radius:
  edge: "anonymous traffic only"
  payments: "card network plus ledger"
next_change:
  - "Split refunds out of checkout-api"
Enter fullscreen mode Exit fullscreen mode

Copy that contract beside the service code. Keep the sketch proposal in a branch. Never merge YAML the checker has not seen.

Interrogate every arrow

Every arrow in the sketch is a promise. You make the sketch name each promise.

North-south arrows usually look honest in sketches. East-west arrows hide the real coupling inside.

You force east-west flows to declare auth. Cookie forwarding is not an auth story.

You ask what bytes actually move across. You ask who authenticates those exact bytes. You ask which room floods if they stop.

If the sketch cannot answer, reject the arrow. Rejection stays cheaper than a night incident.

A payments arrow without auth is a rumor. A rumor should not reach your cluster.

# tools/review_arch.py
from pathlib import Path
import sys
import yaml

REQUIRED_FLOW = {"from", "to", "data", "auth", "failure_domain"}
REQUIRED_TOP = {
    "system",
    "reviewer",
    "constraints",
    "data_flows",
    "owners",
    "blast_radius",
    "next_change",
}

def fail(msg):
    print(f"REVIEW REJECT: {msg}")
    sys.exit(1)

def check_flood_owners(doc):
    by_domain = {}
    for flow in doc["data_flows"]:
        owner = doc["owners"].get(flow["to"], "unknown")
        by_domain.setdefault(flow["failure_domain"], set()).add(owner)
    for domain, owners in by_domain.items():
        if len(owners) > 1:
            fail(f"{domain} floods {sorted(owners)} together")

def main(path):
    doc = yaml.safe_load(Path(path).read_text())
    missing = REQUIRED_TOP - set(doc)
    if missing:
        fail(f"missing top-level keys: {sorted(missing)}")
    if not doc["constraints"]:
        fail("no constraints recorded")
    for c in doc["constraints"]:
        statement = c.get("statement", "")
        if not statement:
            fail("constraint without a statement")
        if c.get("movable") is True and "PII" in statement:
            fail(f"{c.get('id')} treats PII as movable")
    for i, flow in enumerate(doc["data_flows"]):
        gap = REQUIRED_FLOW - set(flow)
        if gap:
            fail(f"flow {i} missing {sorted(gap)}")
        if not flow.get("auth"):
            fail(f"flow {i} has empty auth")
        if flow["from"] not in doc["owners"]:
            fail(f"{flow['from']} has no owner")
        if flow["to"] not in doc["owners"]:
            fail(f"{flow['to']} has no owner")
        domain = flow["failure_domain"]
        if domain not in doc["blast_radius"]:
            fail(f"domain {domain} has no blast radius note")
    if not doc["next_change"]:
        fail("review board left next_change empty")
    check_flood_owners(doc)
    print("REVIEW PASS: sketch may be discussed, not shipped")

if __name__ == "__main__":
    main(sys.argv[1])
Enter fullscreen mode Exit fullscreen mode

Run the checker like a fast unit test. Do not run it like a quarterly ceremony.

python3 -m pip install pyyaml
python3 tools/review_arch.py arch/constraints.yaml
Enter fullscreen mode Exit fullscreen mode

A pass means the sketch is only discussable. A pass does not mean production is allowed. That distinction is the entire review board.

Walk a rejected sketch

You learn more from a reject than a pass. You feed the checker a confident lie.

# arch/bad-sketch.yaml
system: checkout-api
reviewer: on-call-platform
constraints:
  - id: C1
    statement: "PII stays inside the payment VPC"
    movable: true
data_flows:
  - from: checkout-api
    to: payments
    data: "card pan"
    auth: ""
    failure_domain: shared-db
owners:
  checkout-api: commerce
blast_radius: {}
next_change: []
Enter fullscreen mode Exit fullscreen mode

Now run it and read the first failure.

python3 tools/review_arch.py arch/bad-sketch.yaml
Enter fullscreen mode Exit fullscreen mode

You should see a hard reject immediately. PII cannot be a movable wall.

Empty auth is not a real authentication story. payments has no owner in this file.

The shared-db domain has no blast radius note. The empty next_change field is theater with no renovation.

That reject is the review board speaking. You do not negotiate with the first pretty diagram.

You fix only one field at a time. You re-run the checker after each honest edit. This tight loop is the architecture practice.

Rooms that flood

Think of a ship that needs real bulkheads. A bulkhead is not a microservice name. A bulkhead is the water line you accept.

Shared disks turn two boxes into one flood. The sketch may still draw separate boxes. The flood still occupies a single room.

Write that flood under blast_radius without apology. Then compare the floods against the arrows.

Two owners inside one flood will page together. You want one owner for one flood.

The checker already rejects mixed owners per flood. That rule will annoy you at least once. The annoyance is cheaper than a dual-homed outage.

You will hit false positives sometimes here. You then edit the file with intent. You do not silence checks from chat.

Sketch plane, not control plane

You may want a remote model proposing YAML. That stays fine if the proposal stays disposable.

Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode offers free model access and a free server option. You can treat that pair as a sketch plane.

You paste constraints into the sketch plane. You ask only for a YAML proposal. You pull that proposal back to your laptop.

You run the checker on your own tree. The remote box behaves like a powered whiteboard. Whiteboards do not merge straight into main.

Keep the prompt boring, strict, and local.

Propose arch/constraints.yaml for checkout-api.
Do not invent owners.
Do not mark PII constraints movable.
Every data_flow needs auth and failure_domain.
Leave next_change with one concrete split.
Return YAML only.
Enter fullscreen mode Exit fullscreen mode

Then you diff the result like an adult.

git checkout -b review/checkout-sketch
python3 tools/review_arch.py arch/constraints.yaml
git diff -- arch/constraints.yaml
Enter fullscreen mode Exit fullscreen mode

If the diff invents a service, delete it. If the diff invents an owner, reject everything. The model is not staffed for your on-call.

Name the renovation

A review without a renovation plan is theater. You force next_change to exist in YAML.

Good next_change names a split or deletion. Bad next_change says improve resilience somehow. That vague sentence does no operational work.

For checkout-api the honest step stays small. You split refunds away from capture paths. You keep capture inside the payments flood.

You give refunds a separate named owner. You write that plan inside the file. You refuse to leave it on a slide.

next_change:
  - id: N1
    action: split
    from: checkout-api
    extract: refunds
    reason: "refunds page the payments flood today"
    owner_after: commerce-risk
Enter fullscreen mode Exit fullscreen mode

Re-run the checker after that small edit. The sketch now carries a dated future. Topology without a future is quiet drift.

You can hang the same command in CI. The gate only proves the contract is complete.

# .github/workflows/arch-review.yml
name: arch-review
on:
  pull_request:
    paths:
      - "arch/**"
      - "tools/review_arch.py"
jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install pyyaml
      - run: python3 tools/review_arch.py arch/constraints.yaml
Enter fullscreen mode Exit fullscreen mode

This CI job is not a taste test. The job is only a memory aid for walls.

Limitations

This workflow only rejects incomplete YAML contracts. It does not understand live production traffic. It cannot see a hidden queue hop.

It cannot prove encryption on the wire. It cannot replace a real threat model.

Do not use this for air-gapped systems. Do not use this as production change control.

Do not skip a named human reviewer. Do not place secrets on any remote sketch box.

The checker will pass a well-labeled lie. Labels are not truth about data flow. You still read every arrow yourself.

A single process does not need this board. A review board for one box is costume jewelry.

If you cannot name an owner, stop generating. Ownership is the constraint that never moves.

You now hold a file, checker, and rule. The operating rule stays simple even under pressure.

Your generated sketches only propose while reviews dispose. Real topology is whatever survives both gates.

Keep the YAML file beside the service. Run the checker in CI as a gate.

If you already sketch in another editor, keep this review file anyway.

Top comments (0)