Cuando dice "no todas las bases de datos admiten esto", creo que una mejor manera de decirlo es la siguiente:
Todas las bases de datos principales lo admiten, ya que son ampliamente compatibles con disparadores, funciones y otras características 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 amplia cobertura requerida (considerando operadores, índices, etc.)
If so to what extent?
Una vez más, tan limitado como puede ser, si un tipo existente combinado con una pequeña lógica definida adicional (es decir, verificaciones, etc.) puede hacer el truco, ¿por qué ir tan lejos?
How useful are they?
Un montón. Consideremos por un segundo un DBMS no tan bueno como MySQL, que elegí 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 IP como rangos y todo eso, y está atascado con el tipo predeterminado y su funcionalidad limitada, o bien escribirá funciones y operadores adicionales (como los que se admiten de forma nativa en postgreSQL para 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 ampliar la compatibilidad con el tipo de datos, solo hay un paso más para definir un nuevo tipo de datos.
Ahora volvamos a PostgreSQL, que tiene un soporte de tipo realmente agradable pero no int sin firmar ... lo que necesita, porque está realmente preocupado por el almacenamiento / rendimiento (quién sabe ...), bueno, deberá agregarlo así como operadores, aunque, por supuesto, esto deriva principalmente de los operadores int existentes.
What pitfalls have you encountered?
No juego con eso porque hasta ahora no he tenido un proyecto que requiera y justifique el tiempo requerido para esto.
Los problemas más importantes que puedo ver con eso serían 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) falle porque no definió un elenco (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 el dbms carece de muchos tipos importantes), pero por otro lado, uno debe recordar que un (buen) db es compatible con ACID ( a diferencia de una aplicación) y que todo lo relacionado con la coherencia se mantiene mejor allí.
Hay muchos casos en los que la lógica de negocios se maneja en la capa de software y podría hacerse 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 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, un buen ejemplo se da 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.