Review this design. A delivery-partner app on low-end Android phones reads a device identifier from the operating system at launch and uses it as the primary key of a devices table. The row holds the partner's current shift, an offline cash ledger and a per-device fraud limit. The identifier is Android SSAID or iOS identifierForVendor depending on platform. What would you remove, what would you change, and what would you leave alone?
Show the full answer Hide the answer
What the platform identifiers actually promise
- Android SSAID has been scoped to the combination of app signing key and user since Android 8.0 (2017), and it is regenerated on factory reset. It identifies an installation of your app on a device, not a device.
- identifierForVendor changes when the user removes every app from that vendor and installs one again (Apple's documented behaviour), so an uninstall-reinstall cycle produces a new identity.
- The advertising identifier is not a substitute. From Android 12 (2021), and across Google Play devices from April 2022, opted-out users receive a string of zeros, and apps targeting Android 13 that do not declare the AD_ID permission get zeros too. Used as a key, every zeroed device collapses onto one row.
For a delivery platform in the mould of Swiggy or Zomato, where handsets are cheap, reset often, get handed between people and are replaced mid-shift, those are not corner cases. They are the weekly experience of the support team.
What I would remove
The devices table as the owner of money and shift state. An earnings ledger keyed by an identifier that changes on factory reset loses a partner's balance to a routine troubleshooting step, and a fraud limit keyed the same way resets itself for any partner who reinstalls. Removing this also removes the accidental hot row the zeroed advertising identifier would create.
The one change that matters
Separate three identities that the design has merged into one.
- Installation identity: a random UUID the client generates on first run and stores in app storage. It survives reinstall on neither platform, which is fine, because it only has to be unique.
- Device record: a server-issued identifier created when that installation registers, holding the push token, session credentials and revocation state. The server owns the key, so it is stable by construction.
- Account identity: the partner. Money, shift state and limits hang here, because that is what the business means.
A device trust signal, from Play Integrity or App Attest, is then a signal with a decay, feeding the fraud decision rather than keying it.
What I would leave alone
The offline ledger on the device. Meal-time peaks on congested networks make local-first writes a requirement, not a preference. Keep it, key each entry to the account, and give every entry a client-generated operation identifier persisted before the first send attempt so retries cannot double-credit.
Keep the device record too. Push tokens, session revocation and crash triage all need it; it simply must not be the thing a balance belongs to.
How I would argue this in the review
One question settles it without a debate about identifiers: what happens to this row when a partner factory-resets the handset mid-shift, or swaps to a spare? Then one query: the count of devices sharing the most common identifier value.
When not to bother
An anonymous app with no entitlements and no money can key a preferences cache on the platform identifier all day, and adding a registration round trip there is cost for nothing. The threshold is the first piece of state whose loss or duplication costs somebody money, and the moment the identifier is used for a limit, it has become a security control with a reset button on the device.
The failure mode to quantify before the meeting: a handset returned, wiped and reissued inside 30 days is routine in this kind of operation, and every such cycle is either data loss or a duplicate partner record, depending on which way the code happens to fail.