For integer ids I've been trying units of measure instead of single-case unions, and I'd like a second opinion before it spreads. CustomerId of int allocates unless it's marked [<Struct>], and every use goes through a constructor or a match. With [<Measure>] type customerId the id is int<customerId>, the unit is erased at run time, and passing an int<orderId> where a customer id is expected fails to compile.
The costs I've found so far are small but real. Only numeric types take a unit, so string ids still need a union. Getting a plain number back means int id, which reads fine. And a reflection-based JSON serializer sees a plain int, so the unit is reapplied by hand on the way in, which is exactly the spot where a mixup could slip through.
Maybe units for internal ids and a union at the edges where values come from outside? I'm not sure the split is worth explaining to the next reader.