Quando dici "non tutti i database supportano questo", penso che un modo migliore di metterlo sia il seguente:
Tutti i principali database supportano questo, poiché supportano i trigger, le funzioni e altre funzionalità avanzate in modo esteso.
Questo ci porta alla conclusione che questo fa parte di SQL avanzato e ha senso a un certo punto.
Do people actually use domains in their database designs?
Meno è possibile, a causa della vasta copertura richiesta (considerando operatori, indici, ecc.)
If so to what extent?
Ancora una volta, per quanto limitato possa essere, se un tipo esistente combinato con una piccola logica aggiuntiva (ad esempio, controlli ecc.) può fare il trucco, perché andare così lontano?
How useful are they?
Un sacco. Consideriamo per un secondo un DBMS non-così-buono come MySQL, che ho scelto per questo esempio per una ragione: manca un buon supporto per il tipo inet (indirizzo IP).
Ora vuoi scrivere un'applicazione che si concentra principalmente su dati IP come gli intervalli e tutto ciò, e sei bloccato con il tipo predefinito e le sue funzionalità limitate, o scrivi funzioni e operatori aggiuntivi (come quelli supportati in modo nativo in PostgreSQL per esempio) o scrivere query molto più complesse per ogni funzionalità di cui hai bisogno.
Questo è un caso in cui puoi facilmente giustificare il tempo impiegato per definire le tue funzioni (inet > > inet in PostgreSQL: range contenuto nel range operator).
A quel punto, hai già giustificato l'estensione del supporto datatype, c'è solo un altro passaggio per definire un nuovo tipo di dati.
Ora torna a PostgreSQL che ha un supporto di tipo veramente bello ma senza int unsigned .. di cui hai bisogno, perché sei davvero preoccupato per lo storage / le prestazioni (chissà ...), beh dovrai aggiungerlo come così come gli operatori, anche se ovviamente questo deriva principalmente dagli operatori int esistenti.
What pitfalls have you encountered?
Non ci gioco perché finora non ho avuto un progetto che richiedesse e giustificasse il tempo richiesto per questo.
I maggiori problemi che posso vedere sono reinventare la ruota, introdurre bug nel livello "sicuro" (db), supporto di tipo incompleto che ti renderai conto solo mesi dopo quando il tuo CONCAT (cast * AS varchar) fallisce perché non hai definito un cast (newtype come varchar), ecc.
Ci sono risposte che parlano di "non comuni" ecc. Sicuramente queste sono e dovrebbero essere non comuni (altrimenti vuol dire che i dbms mancano di molti tipi importanti), ma d'altra parte, si dovrebbe ricordare che un (buono) db è ACID compatibile (a differenza di un'applicazione) e qualsiasi cosa relativa alla coerenza è meglio conservata lì dentro.
Ci sono molti casi in cui la logica aziendale viene gestita nel livello software e potrebbe essere eseguita in SQL, dove è più sicuro. Gli sviluppatori di app tendono a sentirsi più a proprio agio all'interno del livello applicativo e spesso a evitare soluzioni migliori implementate in SQL, questo non dovrebbe essere considerato una buona pratica.
Gli UDT possono essere una buona soluzione per l'ottimizzazione, un buon esempio è dato in un'altra risposta sul tipo m / f usando char (1). Se fosse stato un UDT potrebbe invece essere un booleano (a meno che non vogliamo offrire la terza e la quarta opzione). Naturalmente sappiamo tutti che questa non è veramente ottimizzazione a causa del sovraccarico della colonna, ma c'è la possibilità.