Cuando dices "no todas las bases de datos admiten esto", creo que una mejor manera de ponerlo es la siguiente:
Todas las bases de datos principales lo admiten, ya que admiten extensamente activadores, funciones y otras funciones avanzadas.
Esto nos lleva a la conclusión de que esto es parte de SQL avanzado y tiene sentido en algún momento.
Do people actually use domains in their database designs?
Lo menos posible, debido a la extensa cobertura requerida (considerando operadores, índices, etc.)
If so to what extent?
Nuevamente, tan limitado como pueda ser, si un tipo existente combinado con un poco de lógica definida adicional (es decir, verificaciones, etc.) puede hacer el truco, ¿por qué ir tan lejos?
How useful are they?
Mucho. Consideremos por un segundo un DBMS no tan bueno como MySQL, que escogí para este ejemplo por una razón: carece de un buen soporte para el tipo inet (dirección IP).
Ahora desea escribir una aplicación que se centre principalmente en datos de IP como rangos y todo eso, y está atascado con el tipo predeterminado y su funcionalidad limitada, o escribirá funciones y operadores adicionales (como los admitidos de forma nativa en postgreSQL por ejemplo) o escriba consultas mucho más complejas para cada funcionalidad que necesite.
Este es un caso en el que justificará fácilmente el tiempo dedicado a definir sus propias funciones (inet > > inet en PostgreSQL: rango contenido en el operador de rango).
En ese momento, ya ha justificado la extensión de la compatibilidad con el tipo de datos, solo hay otro paso para definir un nuevo tipo de datos.
Ahora, de vuelta a PostgreSQL, que tiene un soporte de tipo realmente agradable pero no tiene int sin firmar. Lo que necesita, porque está realmente preocupado por el almacenamiento / rendimiento (quién sabe ...), así que deberá agregarlo como así como los operadores, aunque, por supuesto, esto se debe principalmente a los operadores int existentes.
What pitfalls have you encountered?
No he jugado con eso porque hasta ahora no he tenido un proyecto que requiriera y justificara el tiempo requerido para esto.
Los problemas más grandes que puedo ver son reinventar la rueda, introducir errores en la capa "segura" (db), soporte de tipo incompleto que solo se dará cuenta meses después cuando su CONCAT (cast * AS varchar) falla porque no definió una conversión (newtype como varchar), etc.
Hay respuestas que hablan de "poco común", etc. Definitivamente, estas son y deberían ser poco comunes (de lo contrario, significa que la dbms carece de muchos tipos importantes), pero por otro lado, uno debe recordar que una (buena) db es Compatible con ACID (a diferencia de una aplicación) y que todo lo relacionado con la consistencia se mantiene mejor allí.
Hay muchos casos en los que la lógica de negocios se maneja en la capa de software y se puede hacer en SQL, donde es más seguro. Los desarrolladores de aplicaciones tienden a sentirse más cómodos dentro de la capa de aplicación y, a menudo, evitan las mejores soluciones implementadas en SQL, esto no debe considerarse como una buena práctica.
Los UDT pueden ser una buena solución para la optimización, se da un buen ejemplo en otra respuesta sobre el tipo m / f usando char (1). Si hubiera sido un UDT, podría ser un booleano (a menos que queramos ofrecer las opciones tercera y cuarta). Por supuesto, todos sabemos que esto no es realmente una optimización debido a la sobrecarga de la columna, pero existe la posibilidad.