Quando você diz "nem todos os bancos de dados suportam isso", acho que a melhor maneira de colocá-lo é o seguinte:
Todo banco de dados importante suporta isso, pois oferece suporte a gatilhos, funções e outros recursos avançados extensivamente.
Isso nos leva à conclusão de que isso faz parte do SQL avançado e faz sentido em algum momento.
Do people actually use domains in their database designs?
O menos possível, devido à ampla cobertura necessária (considerando operadores, índices, etc.)
If so to what extent?
Novamente, por mais limitado que seja, se um tipo existente combinado com um pouco de lógica definida adicional (ou seja, verificações etc.) pode fazer o truque, por que ir tão longe?
How useful are they?
Muito. Vamos considerar por um segundo um DBMS não tão bom como o MySQL, que escolhi por este exemplo por um motivo: falta um bom suporte para o tipo inet (endereço IP).
Agora, você deseja escrever um aplicativo focado principalmente em dados IP, como intervalos e tudo mais, e se estiver preso ao tipo padrão e à sua funcionalidade limitada, escreverá funções e operadores adicionais (como os suportados originalmente no postgreSQL for exemplo) ou escreva consultas muito mais complexas para todas as funcionalidades necessárias.
Este é um caso em que você justificará facilmente o tempo gasto na definição de suas próprias funções (inet >> inet no PostgreSQL: range contido no operador range).
Nesse ponto, você já justificou a extensão do suporte ao tipo de dados; existe apenas uma outra etapa para definir um novo tipo de dados.
Agora, de volta ao PostgreSQL, que oferece suporte a tipos realmente agradáveis, mas não possui int. Não assinado ... o que você precisa, porque está realmente preocupado com armazenamento / desempenho (quem sabe ...), bem, você precisará adicioná-lo, bem como o operadores - embora, é claro, isso se deva principalmente aos operadores int existentes.
What pitfalls have you encountered?
Eu não brinco com isso até agora, não tive um projeto que exigisse e justificasse o tempo necessário para isso.
Os maiores problemas que consigo ver são reinventar a roda, introduzir bugs na camada "segura" (db), suporte incompleto ao tipo que você só perceberá meses depois quando o seu CONCAT (cast * AS varchar) falhar porque você não definiu uma conversão (newtype como varchar) etc.
Existem respostas que falam sobre "incomum" etc. Definitivamente, essas são e devem ser incomuns (caso contrário, o dbms carece de muitos tipos importantes), mas, por outro lado, deve-se lembrar que um (bom) db é compatível com ACID ( diferente de um aplicativo) e que qualquer coisa relacionada à consistência é melhor mantida lá.
Existem muitos casos em que a lógica de negócios é tratada na camada de software e isso pode ser feito no SQL, onde é mais seguro. Os desenvolvedores de aplicativos tendem a se sentir mais à vontade na camada de aplicativos e geralmente evitam melhores soluções implementadas no SQL; isso não deve ser considerado uma boa prática.
UDTs podem ser uma boa solução para otimização, um bom exemplo é dado em outra resposta sobre o tipo m / f usando char (1). Se tivesse sido uma UDT, poderia ser um booleano (a menos que desejemos oferecer a terceira e quarta opções). É claro que todos sabemos que isso não é realmente otimização por causa da sobrecarga da coluna, mas a possibilidade existe.