Home / Blog / Migration & Interoperability

Keeping Architecture in Git: Branching, Reviewing and Merging Models

Ask most architects where the current, authoritative version of the application landscape lives, and the honest answer is a shared drive, a folder called "Architecture 2026," and a file named something like landscape_v4_final_reviewed.vsdx. There is no history of who changed what and why, no way to see what a proposed change actually alters before it lands, and no way to undo one bad edit without also undoing every good edit that happened after it. Code stopped working this way decades ago. Architecture, for the most part, never did — not because architects don't value history and review, but because a diagram file has never given them anything to branch, diff, or merge.

That's a tooling problem, not a discipline problem, and it's solvable the same way the equivalent problem was solved for code: put the artifact in version control, make changes as branches, review them as diffs, gate them with automated checks, and merge. The reason this hasn't been standard practice for architecture models isn't that architects don't want it. It's that a binary diagram file is structurally unable to support it. Git can technically store a .vsdx or a .qea file, but it can only tell you that the file changed, not what changed inside it — and it has no way to combine two people's edits to the same file into one, because there's no meaningful text-level notion of "the same file, edited two ways." Two binary diagram edits don't merge. They collide, and the second one to save wins, silently overwriting the first.

What has to be true before Git even makes sense

Putting a model in Git usefully isn't a matter of running git init in the right folder. It requires the model itself to have properties that most architecture file formats don't have. Three matter more than the rest.

The first is deterministic serialization. When a model is saved to a file, the order in which elements, relationships, and properties are written out has to be stable and predictable — not whatever order a hashmap happens to iterate a program's internal object graph in. If saving the same model twice with no changes produces two byte-different files, or if adding one relationship shuffles the position of forty unrelated lines, then Git will faithfully report a change touching forty lines, and a reviewer will have no way to tell which one of those forty lines is the actual edit and which thirty-nine are noise from how the serializer felt like ordering things that day. Determinism is what makes a one-line semantic change show up as a one-line diff.

The second is stable identity. Every element and relationship needs an identity that survives being renamed, re-described, or moved between views — an ID that isn't derived from the display name. Without this, renaming "Legacy CRM" to "CRM (Sunset)" looks, at the file level, exactly like deleting one element and creating an unrelated new one. That's a serialization detail with real consequences for a diff and a merge: a diff tool without stable IDs will show a rename as a large deletion plus a large addition, and a merge tool will have no way to know that a second branch's edits to "Legacy CRM" and this branch's rename of it are touching the same object at all.

The third is keeping layout separate from semantics. Where a box sits on a canvas — its x/y coordinates, which view it's shown in, how it's colored for a particular audience — is a presentational fact, not an architectural one. If layout and semantic content are serialized together, then every time someone drags a box two pixels to tidy up a diagram, the diff shows a change, and reviewers learn to stop reading diffs carefully because most of them are noise. Keep the two separate, and a diff shows what actually changed architecturally: an element added, a relationship's protocol updated, a rule violation introduced — not that someone nudged a rectangle.

Mooodels is built around all three of these as first-class properties of the model, not as an afterthought bolted on for a Git integration. That's what makes the rest of this workflow — branch, review, gate, merge — actually work instead of just technically running.

Branching a proposed change

Take a concrete scenario: a team is retiring a legacy CRM in favor of a SaaS replacement, and as part of that, an old permit backend needs to sit behind a new API gateway instead of being called directly. This is exactly the kind of change that should be proposed, reviewed, and merged deliberately — it touches security boundaries, it affects anyone who currently calls the backend directly, and it's the sort of thing an architect wants a second pair of eyes on before it becomes the record of what the organization actually runs.

The architect branches the model — retire-legacy-crm, say — and makes the change. It doesn't matter whether they do it by dragging a new gateway element onto the canvas and drawing a connection, or by describing the change to an AI assistant and reviewing the patch it proposes. Both routes converge on the same underlying model edit, and neither one touches the main branch until the change has been reviewed.

Concretely, the branch adds a single new element — a PermitGateway application, described as the managed gateway sitting in front of the legacy backend — and rewires two relationships around it. A new HTTPS relationship runs from the gateway to PermitAPI, and the portal's existing HTTPS relationship, which used to point straight at PermitAPI, is repointed at the gateway instead. The old direct call is gone. Nothing about this is exotic — it's the kind of change an architect makes routinely. What's different is that it now happens on a branch, against a model with stable identity for every object it touches, so the eventual diff can show exactly that and nothing more: one element added, two relationships changed, everything else untouched.

Opening the pull request

Once the branch is ready, it becomes a pull request against main, the same as it would for a code change. The description says what and why — "route Portal through a managed gateway ahead of the direct-call deprecation" — and the diff is what a reviewer actually reads, not a pair of exported screenshots with the reviewer expected to spot the difference by eye.

Change summary — retire-legacy-crm → main

+ Added element: application "PermitGateway"
      description: Managed API gateway in front of PermitAPI

+ Added relationship: PermitGateway calls PermitAPI (HTTPS)
- Removed relationship: Portal calls PermitAPI (HTTPS)
+ Added relationship: Portal calls PermitGateway (HTTPS)

  No other elements, relationships or properties changed.
  4 views reference PermitAPI; all re-render with the new route.

This is the entire architecturally meaningful change. No layout coordinates, no view-positioning noise, no unrelated reordering of elements that happened to sit near this one in the file. A reviewer can look at this and ask the questions that actually matter: does the gateway belong in the same security zone as the backend it fronts, does anything else still call PermitAPI directly and now bypass the gateway, does removing the direct call break a rule that quietly assumed it existed. Those are architecture questions. Answering them from two side-by-side screenshots — which is the review process most teams actually have today — means spotting a removed line by comparing pixels, which is slow, error-prone, and the reason a lot of architecture review in practice amounts to a rubber stamp.

It also means review comments can attach to the actual thing being changed, instead of to a general "looks fine" on an exported image. A reviewer can leave a note on the newly added relationship between PermitGateway and PermitAPI, asking whether the gateway also needs to be added to the existing PCI-scope view, and get an answer, before the branch merges — the same back-and-forth a code review thread supports, applied to a relationship instead of a function. None of that is available when the artifact under review is a flattened image; there's nowhere for the comment to attach except the diagram as a whole.

Why the diff has to stay small: a five-line semantic change that renders as a two-hundred-line diff doesn't get reviewed carefully — it gets skimmed and approved. Deterministic serialization isn't a nicety here; it's the difference between a diff a person will actually read and one they'll wave through.

The rule check as a gate, not a suggestion

Code review has a second layer underneath the human one: CI. Tests run, linters run, and the pull request can't merge until they pass — not because a human forgot to check, but because the check runs automatically on every proposed change without anyone having to remember to ask for it. Architecture rules can work exactly the same way, because they're queries over the model rather than something a reviewer has to eyeball.

A rule like "public-facing applications must sit behind an approved gateway" or "Tier-1 applications must have an owner" is deterministic: it either holds against the model or it doesn't, and it holds or fails the same way every time it runs, unlike a human reviewer's attention on a Friday afternoon. Wired into the same pull request, it runs the moment the branch is proposed:

Rule check: gateway-required-for-public-services — PASS. PermitAPI no longer has an inbound relationship crossing the Public boundary directly; all inbound public traffic now routes through PermitGateway.
Rule check: tier1-must-have-owner — PASS.
Rule check: no-direct-database-access-from-portal — PASS.

If the change had instead left a second, forgotten relationship from some other public-facing service straight into PermitAPI, the same rule would fail on that branch specifically, and the pull request would show it failing before a human reviewer ever needs to notice it by hand. This is what turns architecture governance from a periodic audit — someone spends a week every quarter checking whether the model still complies with the rules — into something closer to a build gate: violations get caught at the point they're introduced, on the branch that introduced them, instead of being discovered months later by whoever happens to be doing the next audit.

Merging: the case that usually just works

Once the diff is reviewed and the rule checks pass, the branch merges into main. For a large share of real-world architecture changes, this is genuinely uneventful — no different from merging two code branches that touched different functions in the same file.

Consider two branches opened around the same time, both against the same shared element: one team is adding a relationship from a new reporting service into PermitAPI, and a separate team, working independently, is adding a relationship from a new audit-logging service into the same PermitAPI. Neither branch touches the other's addition. In the serialized form, this is exactly like two commits that each add a new, distinct entry to the same file — Git merges it without incident, because the changes are additive and don't overlap. The same is true here: because the serialization is deterministic and each relationship is written out as its own separately addressable entry, both additions land, and PermitAPI ends up with two new inbound relationships that were never in conflict with each other in the first place.

A model change moving from branch to reviewed pull request to merged main main retire-legacy-crm edit model visual · AI rule check review diff merged
A branch carries one proposed model change through an automated rule gate and a human diff review before it rejoins main — the same shape as a code pull request, because the model supports the same operations a codebase does.

Where it genuinely still gets hard

None of this is a claim that model merging becomes trivial. It becomes tractable, in the specific sense that code merging is tractable: most changes merge cleanly because most changes don't actually overlap, and the ones that do overlap surface as a conflict a human has to resolve, rather than as silent data loss. That's a meaningfully better place to be than a binary diagram file, where every simultaneous edit is a potential silent overwrite. But it is not "the tool figures out what you meant."

The case that still requires a person is the same case that requires a person in code: two branches making incompatible decisions about the same object. If one branch renames Legacy CRM to CRM (Read-Only) as an interim step, and a second branch, opened around the same time, deletes Legacy CRM entirely because it assumed the retirement was already complete, the merge has a real conflict. Stable identity means the tool can at least tell you precisely what's in conflict — both branches touched the same element, identified unambiguously by its ID, not by a name that had already changed — but it can't tell you which branch is right. That's a judgment call: does the CRM still exist in a reduced capacity, or is it actually gone. A person has to look at both branches and decide, the same way a person has to decide when two code branches both rewrite the same function in incompatible ways.

The same is true when two branches both rename the same element to different things, or when one branch changes a relationship's protocol while another branch deletes that relationship outright. These are real conflicts, and they show up as conflicts — clearly scoped to the specific element or relationship in question, not as a vague "these two files differ" — rather than being silently resolved in whichever branch happened to merge second. That specificity is the actual win. A conflict that names the exact element and the exact incompatible decisions made about it is something a reviewer can resolve in minutes. A silent overwrite of an entire diagram file, discovered three weeks later when someone notices a relationship is missing, is not.

The same shape of conflict shows up one level down, on relationships rather than elements. If one branch changes the protocol on the relationship from PermitGateway to PermitAPI from HTTPS to an internal mTLS-only variant, and a second branch, opened around the same time, deletes that relationship entirely because a separate initiative rerouted traffic through a different path, the merge can't quietly pick a winner. Both branches made a real, considered decision about the same edge in the graph. The tool's job is to surface that as a conflict precisely scoped to that one relationship, not to guess which decision should win — the same restraint a code merge tool shows when two branches touch the same function differently and it refuses to silently prefer one.

What this workflow promises and what it doesn't: additive, non-overlapping changes — the overwhelming majority of day-to-day architecture edits — merge cleanly and automatically. Genuinely conflicting decisions about the same element still need a human to resolve them. The goal isn't to remove that judgment call; it's to make sure it's the only thing left for a person to do, instead of the whole review being a manual pixel comparison.

How much change belongs in one pull request

Having the mechanics available doesn't tell a team how to use them, and the question that comes up first in practice is granularity: what's worth a branch and a reviewer, and what should simply be edited directly. Code has settled answers here that mostly transfer. A typo fix goes straight in; a change that alters an interface gets reviewed. The architecture equivalent works out to roughly this: correcting a description, fixing a misspelled element name, or tidying up how a view is laid out is a direct edit, while anything that adds, removes, or reroutes a relationship — anything that changes what the model claims about how the organization is actually wired — is a proposed change with someone else's name on the review.

Size follows the same logic. A branch that retires one system and reroutes its callers is reviewable in ten minutes. A branch that does that and also reclassifies forty elements into a new domain structure is not, and it will get approved on the strength of its description rather than its diff, which defeats the entire point of having a diff. Splitting the second one into two branches costs almost nothing once the model genuinely supports branching, and it means the domain restructure gets read on its own terms instead of riding along behind a retirement everyone had already agreed to.

Routing matters as much as size. Most organizations past a certain size already have de facto ownership boundaries running through the architecture — an integration team that owns the middleware, a data team that owns the warehouse, a security architect who wants to see anything that crosses a zone boundary. Those boundaries can be expressed the same way a codebase expresses them, as ownership rules over regions of the model, so that a branch touching the payments domain requests review from the person accountable for payments automatically, rather than depending on the author remembering who holds that responsibility this quarter. It's a small piece of plumbing, and it's the difference between review being a process and review being a habit that survives one person going on leave.

History as more than an audit trail

A diff answers what changed in one proposed edit. A commit history answers a question architects get asked just as often, usually months after the fact: why does this relationship exist, who approved it, and what was rejected instead. In a diagram-first world, that answer lives in whoever happens to remember the meeting, or it doesn't exist at all. In a model kept in Git, it's the commit log for that specific element — the branch name, the pull request description, the rule checks that passed against it, and who approved the merge — attached to the exact relationship in question, not to the file as a whole.

This matters most for the changes nobody expects to have to explain again. The gateway relationship from the CRM retirement above is unremarkable on the day it merges. Eighteen months later, when a security review asks why PermitAPI is reachable from two paths instead of one, or a new architect wonders whether the direct relationship was ever meant to be removed entirely, the answer isn't a Slack search or a guess — it's the pull request that made the change, still attached to the exact relationship it touched, because that relationship's identity never changed even as the rest of the model kept moving around it. That's the same reason git blame earns its keep on a codebase: not because anyone reads it daily, but because the one day someone needs it, it's there, pinned to the right line, instead of buried in a changelog nobody kept updating.

Working alongside Archi and Sparx EA

Most teams evaluating this aren't starting from a blank model. There's usually an existing Archi model or a Sparx EA repository with years of accumulated work in it, and nobody is proposing to throw that away in favor of a from-scratch Git workflow. Mooodels imports from both, and round-trips back out, which matters specifically because of the stable-identity point made earlier: an import that doesn't preserve a durable link back to the source element can't tell a rename from a replacement on the next round-trip, and a branching workflow built on top of that would be unreliable exactly where it needs to be trustworthy. In practice, this lets a team keep Sparx EA or Archi as the system a client or a governance board is used to seeing and signing off on, while doing the actual day-to-day propose-review-merge cycle for a specific initiative — a migration, a retirement, a security remediation — in branches that get reconciled back into that repository once the work is settled, rather than asking a governance board to switch tools to see the outcome.

What actually changes for the reviewer

Strip away the Git vocabulary and the change this enables is a fairly simple one: an architecture change stops being something one person makes and everyone else finds out about later, and becomes something one person proposes, a rule engine checks automatically, and a second person actually reads before it becomes the record of what the organization runs. That's not a new idea — it's the same idea that made code review normal practice decades ago, applied to an artifact that finally has the right shape for it. A model with deterministic serialization, stable identity, and layout kept separate from semantics can be diffed, gated, and merged the way a codebase can. A diagram file, however well drawn, still can't.

What's left after that is honest, incremental work: writing rules worth gating on, deciding what counts as a change worth a pull request versus a direct edit, and occasionally sitting down to resolve a real conflict between two people's competing decisions about the same element — which is exactly the amount of manual judgment a mature engineering team expects to spend on merge conflicts, no more, and, with a model that actually supports branching properly, no less.

See the model this article describes, working in a real editor.

Try the live demo