Platform & Architecture

Architecture

The data model decisions you cannot cheaply undo

Most of an org is reversible. A handful of early modelling choices are not, and they get made in the first fortnight by whoever is fastest to a whiteboard.

Macro photograph of a circuit boardPlatform & Architecture

Salesforce is unusually forgiving. Fields can be added, page layouts rearranged, automation rewritten, and most of that happens on a Tuesday afternoon without anyone noticing.

The forgiveness is what makes the exceptions dangerous. Teams carry a reasonable "we can change it later" assumption into the handful of decisions where later means a data migration, a rebuild of everything standing on top, or in two cases nothing at all, because the setting does not come back off.

Those decisions get made early, often informally, and they hold, because within a year the schema is the smallest part of them. The choice also lives in every report filter, integration mapping, automation criterion and permission set built since, and changing the model means changing all of that at once, on a working system, without losing the history.

What "cannot undo" actually means

The phrase covers three situations, and they deserve different responses.

Permanent. The switch has no off position. Person Accounts and multiple currencies sit here, and the only mitigations are deciding correctly or a new org later.

Reversible as a migration. The schema can change, but only by moving data, rewriting dependencies and running a cutover. Relationship types, record types and object choices sit here. These are not disasters. They are quarters.

Reversible in the schema, not in the history. The structure can be added at any time; the data that would have been in it cannot. External IDs sit here, and this class gets underestimated most, because the fix looks cheap until somebody asks about last year.

DecisionWhat reversal costsPoint after which the price rises sharply
Person AccountsPermanent, no off switchBefore the first account exists
Multiple currenciesPermanent, no off switchBefore the first amount is stored
Master-detail or lookupMigration, plus a sharing review on the far sideOnce children are too numerous to re-parent by hand
Junction object added lateMigration, and the second relationship was never storedWhen the first relationship is recorded as a single field
Record type sprawlMigration, layout by layout, rule by ruleWhen automation and permissions start referencing them
Picklist that was an entityMigration, plus reconciling every near-duplicate valueWhen values acquire attributes nobody has anywhere to store
Standard versus custom objectRebuild of everything the platform gave you freeWhen reports, packages and integrations assume the custom one
External ID absentField is cheap, the mapping it would have held is notThe first silent mismatch nobody notices

Read the middle column as the moment the conversation stops being configuration and becomes a programme.

1. Person Accounts

Enabling Person Accounts is the clearest member of the permanent class. It changes the shape of the Account object itself: a person account is an account and a contact held together, behaving in some contexts like one and in some like the other.

Why reversal is not really available. Every downstream consumer inherits new rules at once: reports that assumed an account has contacts beneath it, integrations that map a contact identifier, managed packages built against one model, automation that fires on Contact, duplicate rules and merge behaviour. Turning it off would mean redefining what an account is for all of them simultaneously, which is why the option does not exist.

The cheaper question. Not "do we sell to consumers". It is whether an individual is ever a customer in their own right, with no organisation above them, and is the subject of the relationship rather than a person inside one. Retail, wealth, healthcare, education and member organisations usually answer yes. Pure B2B usually answers no. The hard case is both models in one business: supported, but the design work is real, because automation, reporting and sharing each have to handle two shapes of account. If the answer is anything other than a confident no, model it before the first load.

2. Master-detail, lookup, and the junction added late

Teams treat the relationship type as a tidiness question. It is three decisions bundled into one field.

Master-detail gives roll-up summary fields, cascading delete and, the consequential one, sharing the child inherits from its parent. A detail record has no owner of its own. That is both the reason to choose it and the reason to fear it, because if access to the child ever needs to differ from access to the parent, the field type has already decided that it cannot.

What reversal costs. Conversion between the two types exists and is genuinely constrained. Moving from lookup to master-detail requires every child to have its parent populated, which on a live object means finding and fixing the orphans first, and moving the other way interacts with the roll-up summary fields built on the relationship. Neither is a field edit. Both alter how records are shared the moment they complete, so every sharing rule, report and integration touching the child needs checking on the far side, and the cost scales with the number of children rather than the number of fields.

The junction object added late is the same decision arriving a year after it should have. A many-to-many relationship modelled as a single lookup, a text field, or a picklist of the common counterparties works until the first record needs two of something. Introducing a proper junction then is not only a schema change: the existing relationships have to be reconstructed from a field that could only ever hold one, and whatever the second and third were, they were never stored. The junction goes in easily. The history does not come with it.

Ask it while the diagram is still on paper: for every relationship drawn, can one record ever have more than one of these? If the answer is even "rarely", the junction costs an afternoon now and a reconstruction later.

3. External IDs

Every object that receives data from another system needs a unique external ID field carrying that system's key, populated from the first load rather than after the first duplicate incident.

Without one, every integration becomes a matching problem. The code queries by name, by email, or by a composite that mostly works, and mostly-works matching does not fail loudly. It produces duplicates at a slow, steady rate nobody attributes to the original decision, and it produces the worse failure quietly: records matched to the wrong counterpart, which then looks like user error for years.

Why this sits in the third class. The field is cheap to add at any point. What cannot be added retrospectively is the mapping between the records that already exist here and the records that already exist there. Reconstructing it means reconciling against the source record by record, using the same unreliable matching that caused the problem, for every system integrated since.

The cheaper question. For every object an integration will touch, name the field holding the other system's key, mark it unique and external, and insist the integration upserts on it. Upsert against an external ID is idempotent, so a replayed message, a restarted job or a duplicate event does the right thing instead of creating a second record.

4. Where a distinction lives: object, record type or picklist

Most modelling arguments are one question asked badly: this thing varies, so where should the variation live? There are three containers, they are not interchangeable, and choosing wrong is a migration to fix.

Standard object or custom object

The pull toward a custom object is strong, because a fresh object carries no baggage. It also carries none of the platform work standard objects get free: standard reporting behaviour, features that only operate on standard objects, package compatibility, and years of accumulated engineering nobody has to ask for.

Reversal is a rebuild. Every report, layout, automation, integration and permission set is re-pointed and the data migrated with its relationships intact, while the org has spent the intervening years forfeiting features it could have had. Use the standard object unless you can name what breaks if you do. "It has fields we do not need" is not that reason. A custom object duplicating a standard one commits you to maintaining a parallel implementation of everything the platform would have given you, including the parts Salesforce has not built yet.

Record types, past the point anyone can reason about them

Record types are cheap to add and awkward to remove, because page layouts, picklist availability, automation entry criteria, reports, permission sets and assignment rules all reference them. Retiring one means unpicking it everywhere and moving its records elsewhere, which changes what those records display and which values they permit.

Sprawl arrives one reasonable request at a time: a region wants a different layout, a team wants two values hidden, a product line wants its own process. The sum is an object where nobody can say which record type a new record should get without asking somebody, and the cost becomes a tax on all future work, because every automation has to enumerate them and every report has to be checked against all of them.

A record type should represent a genuinely different business process on the same object. Ask at creation: what does this one change that a picklist value could not?

The picklist that turns out to be an entity

The opposite error is quieter. A value goes in as a picklist because at the time it is only a label: a partner, a product line, a referral source, a site. Then it acquires attributes. Somebody wants the partner's owner, the product's list price, the site's address, and those have nowhere to live, so they end up in a spreadsheet or encoded into the value itself.

Converting to a lookup is a migration. The values become records, every existing record is re-pointed, and every report filter, automation criterion, integration mapping and validation rule comparing a text value is rewritten to compare a reference. The near-duplicates a long-lived picklist accumulates are reconciled by hand, each one a judgement call.

Ask at design time: does anything about this value need to be described, owned, or reported on in its own right? If yes it is an entity and wants a lookup.

5. Currency, and the day a second one appears

Multiple currency support is the other setting with no off position. Salesforce states it plainly: once a multicurrency setup is enabled, it cannot be disabled.

It is consequential because it changes the meaning of every stored amount. A currency field stops being a number and becomes a number with a currency, converted for reporting against exchange rates somebody now owns and maintains. Reports gain a currency dimension, and integrations that sent an amount must send a currency alongside it, or accept a default and be silently wrong.

The reverse problem is worse and more common: running a single-currency org while the business already trades in several, and absorbing the difference in spreadsheets or a text field beside the amount. Conversions then happen at whatever rate the report builder chose, historical figures cannot be reproduced, and eventual enablement means revisiting every amount already stored to say which currency it was in. Frequently nobody knows.

The cheaper question. Will any amount here ever be recorded in more than one currency, including inside a subsidiary you have not acquired yet? If the honest answer is probably, decide before the first amount is stored, and settle at the same time who owns the rates.

6. The model decisions that fix your sharing, and your skew

How access is designed is a subject in its own right, covered separately in designing the Salesforce access model on purpose. What belongs here is narrower: the modelling decisions that limit what the access model is later permitted to express, and the ones that make sharing expensive to calculate.

Master-detail removes an option. A detail record cannot be shared independently of its parent, so the relationship type has answered an access question the access design was never consulted on.

Ownership skew. When one user or one queue owns a very large share of the records on an object, the sharing calculations behind that ownership become expensive, and worse as the object grows. It is created by a default owner on an integration user, a catch-all queue, a conversion process that parks everything in one place. Distributing ownership by design costs nothing. Redistributing it later means re-owning records on a live system and waiting out the recalculation.

Data skew. The same shape in the relationship rather than the ownership: one parent with an unusually large number of children, usually a placeholder account or a catch-all record that becomes the destination for everything unclassified. What to do once the symptoms appear is covered in why your Salesforce org feels slow. The point here is that neither started as a performance problem. Both were modelled in.

Ask the volume question early. For the three widest objects: what is the expected record count in three years, who will own those records, and which parent will most of them hang from? The launch dataset shows you neither skew.

7. Where the number lives

For any value that must be right in a report, amount, quantity, score, status, decide whether it is stored, calculated at read time, or calculated on save. Then be consistent.

ApproachGood forCost
Formula fieldAlways current, nothing to keep in stepEvaluated on read, which shows up on large reports
Roll-up summaryAggregating children of a master-detail parentOnly on master-detail, and a limited set of functions
Stored field, set on saveReportable, filterable, fast to queryMust be maintained, and goes stale silently
Calculated downstreamWarehouse-grade analytics and historyNot available in the org for automation or validation

The mistake is mixing them for the same concept. When one report reads the stored field and another recomputes it, the two disagree, and the trust problem that follows is harder to fix than the modelling choice that caused it. Trust in a number is lost quickly and rebuilt slowly, usually by somebody rebuilding both reports and proving which one was right.

How to spend the design time

A day of modelling before the first sprint is worth more than any subsequent week of remediation, and it is a day with a definite agenda rather than an open discussion.

  • Draw the object diagram on one page, with relationship types and directions marked.
  • For every relationship, note whether the child needs independent sharing, and whether one record could ever have more than one of the other.
  • For every object an integration touches, name the external ID field.
  • For the three widest objects, write the expected year-three record count and the likely owner distribution.
  • Write one line of business meaning for every record type, and delete any that share a line.
  • Answer the two permanent questions, Person Accounts and multiple currencies, in writing.

None of that is exotic. It is the set of questions that becomes very expensive to ask late, and the answers get written down because in two years the person who needs them will not be the person who made them.

Sources

  1. Salesforce Trailhead: Data Modeling
  2. Salesforce Trailhead: Large Data Volumes
  3. Salesforce Trailhead: Discover Multiple Currency Settings

Common questions

Answered, directly.

The questions this piece settles about Platform & Architecture, answered in full on this page.

No. Enabling Person Accounts is permanent and changes the shape of the Account object for every downstream consumer, including reports, integrations, managed packages and automation. The realistic reversal is a new org and a migration, so decide before the first data load, based on whether an individual is ever a customer with no organisation above them.

Master-detail gives roll-up summaries and cascading delete, but the child inherits the parent sharing and has no owner of its own. If access to the child could ever differ from access to the parent, use a lookup and build roll-ups another way. Converting between the two later is a migration with a cutover, not a field edit.

No. Salesforce states that once a multicurrency setup is enabled it cannot be disabled. Enabling it changes the meaning of every stored amount, because a currency field becomes a number plus a currency converted against exchange rates somebody has to own. Decide before the first amount is stored, and include subsidiaries you have not acquired yet.

Free architect conversation

Talk to an architect, not a sales rep.

Architecture review, before it gets expensive. 60 seconds to brief us, and a certified architect replies within one business day.

What decision is actually in front of you?

Pick the closest fit. The review is free, and we will tell you to keep your current design if keeping it is right.

What does this decision touch?

Optional. Choose any that apply, or skip ahead.

Where does your org stand today?

Optional. A few sentences is plenty: what is working, what is stuck, and what you want to be true. Or skip ahead and tell us on the call.

Who should the architect reach?

A certified architect will reply to these details.

Takes about 30–60 seconds · No obligation · Architect replies within one business day

Protected by reCAPTCHA. Google's Privacy Policy and Terms apply.

More from Insights

Read by desk

Ten desks, one delivery team. Every piece is written by the people who do the work.