GUID Matching Explained: How Re-Import Updates Instead of Duplicates
Re-import an architecture model a second time and one question decides whether the result is useful or a mess: does the tool recognize the objects it already has, or does it draw them all over again as strangers? Get this wrong and every re-import doubles your model. Get it right, and updating a model from a refreshed export becomes something you can do weekly without dread. The mechanism that makes the difference is almost always the same one, whatever the tool: matching on a globally unique identifier, a GUID, rather than on a name or a position in a file.
The question every re-import has to answer
Say you exported an architecture model from some source tool six months ago, imported it, and have spent those six months annotating it — adding owners, tagging domains, drawing relationships the original export never had. Now the source system has moved on. New applications exist. Some old ones were renamed. A few were deleted. You export again and import the new file into the same model.
The tool now has to decide, object by object, one of three things: this is an object I already have, so update it in place and keep everything attached to it; this is genuinely new, so create it; or — the case that actually causes damage — this looks new, but it's really the same object under a different name, and treating it as new means silently duplicating it while the original, annotated copy sits there orphaned, no longer touched by the export it came from.
That third outcome is the one every architect who has re-imported a model twice has hit at least once. You end up with "Payment API" and "Payment API (2)" in the same model, both real in some sense, neither one trustworthy, and no way to tell from the model alone which one has the annotations you actually care about. The fix isn't a smarter merge algorithm applied after the fact. It's giving the import step a reliable way to answer one question up front: have I seen this object before?
Why a name is not an identity
The obvious answer is to match on name. If the new export contains an application called "Payment API" and the model already has one, they're probably the same thing — match them, update the existing object, move on. This works often enough that it's the fallback every tool ends up implementing anyway. It also fails in two specific, predictable ways, and both are common enough in a real architecture practice that relying on names alone is a liability rather than a convenience.
Names change on purpose
Renaming is routine, not exceptional. Departments rebrand a system. A merger renames an application to match the acquiring company's naming convention. Someone decides "Customer API" undersold what the service actually does and renames it to "Customer Service." None of this is a modelling event — it's ordinary maintenance in the source tool, and it happens constantly across the lifetime of any system that lives long enough to be worth modelling.
If matching runs on name alone, a rename in the source tool looks, on re-import, exactly like a deletion followed by the creation of an unrelated new object. The old name disappears from the export; the tool has nothing left to match it against. The new name appears with no history behind it. Every relationship, every property, every piece of documentation attached to the original object either has to be manually reattached to the new one or is quietly lost. A rename — which should be the cheapest possible change to absorb — becomes the most destructive one.
Names collide by accident
The opposite failure is just as common. Two distinct objects sharing a name is not a hypothetical edge case in any organization above a certain size — it's what happens when two departments both stand up something called "Reporting Service," when a decommissioned system's name gets reused for its replacement, or when a source model simply has two elements of different types that happen to carry the same label because nobody enforced uniqueness on names in the first place (and most modelling tools don't). Match purely on name, and a re-import can silently merge two genuinely different objects into one, because from the matcher's point of view they're indistinguishable.
Why position in a file is even worse
The other tempting shortcut is to match by position — the third element in the file this time is probably the same as the third element last time. This holds up only as long as nothing about the file's structure changes between exports, which in practice is almost never. Source tools reorder exports based on internal IDs that shift when objects are added or deleted. A model gets restructured, reorganized into different packages or folders, and the export order changes even though nothing about the actual architecture did. Someone sorts the source model alphabetically for readability, and every element's position in the file moves.
Positional matching also breaks in a more subtle way: it can't survive the file being touched by anything other than the exact export path that produced it the first time. A file passed through a formatter, split and reassembled, or even just re-saved by a different tool version can reorder elements with zero semantic change to the architecture itself. Treat position as identity, and the matcher starts making decisions based on a property of the file format that has nothing to do with what the file describes. It's a weaker signal than a name, not a stronger one — at least a name carries some information about what the object is. A row number carries none.
What a GUID actually is, and why it holds up
A GUID — a globally unique identifier, sometimes called a UUID — solves this by being minted once, at the moment an object is created, by the tool that created it, and never reused for anything else. It looks something like {3F2A1C4E-11B2-4A9D-8C3F-9A0E7D2B5C61}: a long, effectively random string generated specifically so that the odds of two independently created objects ever colliding on the same value are close enough to zero to ignore. Crucially, a GUID doesn't describe the object at all — it doesn't encode its name, its type, or anything a human would recognize. Its only job is to be a handle that stays attached to one specific object for that object's entire life, regardless of what else about it changes.
That's exactly the property a rename needs. Rename "Payment API" to "Payments Service" in the source tool, and the GUID doesn't move — it's still attached to the same object, because it was never derived from the name in the first place. Export the model again, and the new export carries the same GUID it always did, attached to a different label. A matcher reading that GUID doesn't need to guess whether "Payments Service" is a new object or the old one renamed. It reads the identifier, finds the same one already in the model, and knows — not infers, knows — that this is an update to an object it has seen before.
In practice, a GUID travels with an exported object as metadata rather than as something visible in a diagram. Enterprise architecture tools that maintain an internal repository — Sparx Enterprise Architect is a common example — assign every element a GUID the moment it's created and carry it through every export format that preserves object-level fidelity. On import, a well-built tool reads that identifier from a reserved property in the exchange file rather than inventing its own, which is what makes round-tripping possible at all: the identity travels with the object, not with whichever tool happens to be looking at it that day.
The matching decision, step by step
Strip away the specifics of any one tool and the decision a re-import makes for each incoming object reduces to a short sequence of checks. Does this object carry a GUID at all? If so, does that GUID already exist in the model? If not, is there something close enough to treat as a probable match anyway? And if none of that resolves, the object gets treated as new — because "probably not new" is not the same claim as "definitely not new," and a matcher that can't tell the difference shouldn't pretend to.
The table version of the same logic, which is closer to how it actually reads in an implementation:
| Signal available | Match found? | Action taken | Confidence |
|---|---|---|---|
| GUID present | Yes, exact match in model | Update the existing object in place | Certain |
| GUID present | No matching GUID in model | Create a new object; store the GUID for next time | Certain it's new to this model |
| No GUID | Exactly one name+type match | Update the matched object, flagged as inferred | Probable, not guaranteed |
| No GUID | Multiple name+type matches | Treat as new (ambiguous) | Low — genuinely can't tell |
| No GUID | No name+type match | Create a new object | Certain enough — nothing to merge with |
Case one: exact GUID match, update in place
This is the case the whole mechanism exists for. An object arrives carrying a GUID, that GUID already exists on an object in the model, and the tool can update that object's properties, keep every relationship it participates in intact, and preserve any annotation added since the last import — because nothing about the object's identity was ever in question. The name may have changed, a description may have been edited, a property may have flipped from one value to another; none of that matters to the match, because the match never depended on any of it.
This is also the case that makes iterative modelling workflows viable at all. If every re-import risked duplicating the whole model, nobody would re-import more than once — they'd treat the first import as final and maintain everything by hand afterward, which defeats the point of importing from a live source system in the first place. Reliable GUID matching is what turns "import" from a one-time migration event into a repeatable synchronization step.
Case two: no GUID, but a unique name+type match — probable, not certain
Not every source produces a GUID. Hand-authored spreadsheets, models from tools that don't expose stable identifiers in their export format, or objects added directly inside the destination tool without ever passing through an import at all — all of these arrive with nothing to match on except what's visible: a name and a type. When exactly one existing object in the model shares both, matching on that pair is a reasonable fallback, and updating the matched object rather than creating a duplicate is usually the right call.
It is worth being honest about what this fallback actually guarantees, though, because it's less than a GUID match guarantees and the difference matters. A name+type match means "an object with this exact name and this exact type already exists, and there's only one of them" — it does not mean "this is definitely the same real-world thing." Two distinct applications, coincidentally both named "Reporting Service" and both typed as Application, would satisfy this match just as confidently as a genuine rename would, and the tool has no way to tell the difference from the data available. A tool that quietly treats a name+type match with the same certainty as a GUID match is overselling its own confidence — and a tool that discloses the distinction, marking the match as inferred rather than confirmed, is giving you the information you actually need to decide whether to trust it or check it by hand.
Case three: ambiguous or no match, treated as new
Two failure shapes land here, and they deserve to be told apart even though the outcome — create a new object — is the same for both. The first is the honest case: nothing in the model resembles the incoming object at all, so it genuinely is new, and creating it is simply correct. The second is the uncomfortable case: more than one existing object shares the incoming object's name and type, so there's no principled way to pick which one it should update. Merging into either would be a coin flip dressed up as a decision.
The right move in the ambiguous case is the same as the right move in the genuinely-new case — create a new object rather than guess — but for a different reason, and a tool worth trusting should be able to tell you which reason applied. "This is new" and "this might not be new, but I can't tell which of two candidates it is" are different findings, and collapsing them into one silent "created new object" outcome throws away information a reviewer would want: specifically, a nudge to go check whether one of those two candidates is actually the object that should have been updated, and clean up the near-duplicate by hand if so.
A worked walkthrough
Take a concrete scenario. An architecture team exports a model from Sparx Enterprise Architect, imports it into Mooodels, spends a few weeks tagging domains and drawing dependency relationships that weren't in the original export, then re-imports a refreshed extract from the same EA repository three months later. The refreshed extract contains three objects worth tracing through the matcher individually.
The first is an application component that existed in the original export, was renamed in EA from "Legacy CRM" to "CRM (Sunset)" ahead of a planned retirement, and now arrives carrying the GUID {3F2A1C4E-11B2-4A9D-8C3F-9A0E7D2B5C61} — the same GUID EA assigned it when it was first created, years before either export. That GUID already exists on an object in the Mooodels model. The match is exact. The object updates in place: its name changes to "CRM (Sunset)," every relationship it participates in stays attached, and the domain tag and dependency notes added over the previous three months survive untouched, because none of that lived on the name — it lived on the object, and the object's identity never moved.
The second is a new integration service, added to the EA repository after the first export was taken, arriving with its own freshly minted GUID that has never been seen before. No match exists in the model under that identifier. It gets created as a new object, and its GUID is stored, so that the next re-import — six months from now, after someone in the source repository renames it too — will match it exactly the same way the CRM object just did.
The third is trickier: a reporting element that, for whatever reason, doesn't carry a GUID in this particular extract — perhaps it was authored directly through a bulk import into EA that didn't preserve one, which happens more often than anyone would like. Its name and type — "Reporting Service," Application — match exactly one existing object already in the Mooodels model. The matcher updates that object, but flags the match as inferred rather than confirmed. If it later turns out there were actually two distinct reporting services that happened to share a name, this is the spot where a reviewer would notice something looks off and correct it — which is exactly why the distinction between certain and probable matters in practice, not just as a theoretical nicety.
Where this shows up in the model itself
None of this works as a one-time trick during import — it depends on the destination model actually remembering where each object came from, indefinitely, so that the next import has something to check against. In Mooodels, every imported object carries a source field recording exactly that: which system it came from and the external identifier that ties it back to the object in that system.
{
"id": "elem_8f21ac",
"name": "CRM (Sunset)",
"type": "ApplicationComponent",
"source": {
"system": "sparx",
"externalId": "{3F2A1C4E-11B2-4A9D-8C3F-9A0E7D2B5C61}"
}
}
The system value matters as much as the identifier does. A GUID from Sparx EA and a GUID from another tool — Archi, for instance — are drawn from different identifier spaces and are not comparable to each other even if they happened to look alike, so matching keys on the pair, not the raw identifier alone. That's also what keeps a model that has been assembled from more than one source tool from getting confused about which imported objects came from where, since re-importing a fresh extract from EA should never accidentally match against something originally imported from Archi just because the identifiers happen to share a format.
Keeping the source field around after import, rather than discarding it once the object exists in the model, is what makes every subsequent re-import a synchronization rather than a fresh migration. It's a small piece of bookkeeping with an outsized effect: it's the entire reason re-importing a model a second, fifth, or fiftieth time doesn't get more dangerous each time you do it.
Edge cases worth knowing about
A few situations push on this mechanism in ways worth understanding before they surprise you.
- Copy-paste inside the source tool. Some tools generate a fresh GUID when an object is duplicated; others, depending on version and setting, can carry the original GUID onto the copy. If the latter happens, two genuinely distinct objects in the source model can arrive at import time claiming the same identity — which the matcher will treat as one object updating itself, silently merging two things that should have stayed separate. This is a source-tool behavior, not an import-tool one, but it's worth knowing about because the symptom shows up on the import side.
- Merging exports from the same source system taken at different times without a common baseline. If an object was deleted and later a new, unrelated object happened to reuse a name that the matcher would otherwise treat as a fallback signal, the identifier space itself hasn't lied — but a long gap between exports raises the odds that a name+type collision like this happened somewhere in between, purely because more changes had time to accumulate.
- An object's type changing in the source tool. If matching keys on name and type together and an object's type is reclassified — an Application recategorized as an ApplicationComponent, say — a name+type fallback match will miss it even though the GUID, if present, would have caught it without issue. This is one more reason a GUID match is strictly more robust than the fallback: it doesn't depend on any property of the object staying the same, including ones that seem stable but sometimes aren't.
- Objects with no stable identifier by design. Hand-drawn diagrams, spreadsheets, and some lightweight tools never had a GUID concept in the first place. For anything imported from a source like that, the fallback isn't a degraded mode — it's the only mode available, and the honest answer is that re-importing this kind of source will always carry more manual-verification overhead than re-importing from a tool with real identifier support.
A short checklist for evaluating any tool's re-import behavior
This isn't specific to one product. Before trusting any architecture tool with a repeatable import workflow, it's worth checking, concretely, how it actually behaves:
- Rename an object in the source tool, change nothing else, and re-import. Does the destination update the existing object, or create a duplicate with the new name?
- Create two objects with the same name and type in the source tool, on purpose, and export both. Does the import flag the ambiguity, or silently merge them into one?
- Ask directly whether a match was made by identifier or by name — does the tool disclose which, or does every match look the same regardless of how confident it actually is?
- Check whether the destination model stores the source identifier permanently, or only uses it in the moment of import and then discards it — the latter means every re-import after the first is really just a first import again.
- Import from two different source systems into the same model and check whether identifiers from one can ever be confused with identifiers from the other.
A tool that handles all five cleanly is one you can re-import into on a schedule without babysitting the result. A tool that fails any of them is one where "we re-imported the model" quietly means "someone needs to check for duplicates afterward" — which, for anything beyond a one-off migration, adds up to real, recurring manual work.
Why this is worth getting right
None of this is exotic engineering. A GUID is a decades-old idea, and matching on one instead of on a name is not a novel insight. What's easy to underestimate is how much day-to-day trust in a model depends on getting this one mechanical detail right, quietly, every time. An architect who has been burned once by a re-import that duplicated half the portfolio stops trusting re-imports at all, and starts maintaining the model by hand instead — which is precisely the manual burden a model-native, import-friendly tool like Mooodels is supposed to remove. Identity matching isn't a peripheral feature bolted onto import. It's the thing that decides whether importing is a workflow you can rely on or a one-time event you dread repeating.
Get the matching right — GUID first, name and type as a disclosed fallback, everything else treated honestly as new — and re-import stops being a risk to manage and becomes what it should have been from the start: a routine way of keeping a model current with the systems it describes, in Mooodels or in any other tool built to take the question seriously.
See the model this article describes, working in a real editor.
Try the live demo