Nice article. I've used composite types with success for similar problems. Some tips:<p>- Composite types are useful for data validation. Money types and unit-based types are good for composite types.<p>- Avoid over-using composite types. Most of the time, regular columns are better. Not many tools interact well with composite types, like reporting tools or database libraries.<p>- Like the article notes, avoid using composite types for data types that may change.<p>- A JSONB domain type is a good alternative for data types that change often. Note that if you put the validation into a helper function, Postgres will not revalidate the domain if the function definition changes.<p>- Using composite types is mildly annoying since you must wrap the composite column in parenthesis to access the field.<p><pre><code> -- Bad
SELECT package_semver.major FROM package_version;
-- Errors with `missing FROM-clause entry for table "package_semver"`
-- Good
SELECT (package_semver).major FROM package_version;
</code></pre>
- When defining a domain type, separate checks into named constraints with ALTER DOMAIN. The Postgres error message for check validation failures is lackluster and provides little beyond "it failed."<p><pre><code> CREATE DOMAIN item_delta_node AS jsonb NOT NULL;
ALTER DOMAIN item_delta_node
ADD CONSTRAINT item_delta_node_is_object
CHECK (coalesce(jsonb_typeof(value), '') = 'object');
ALTER DOMAIN item_delta_node
ADD CONSTRAINT item_delta_node_item_delta_id_null_or_num
CHECK (coalesce(jsonb_typeof(value['item_delta_id']), 'number') IN ('null', 'number'));</code></pre>