func AddMessage(u UserId, m MessageId)
If it's just func AddMessage(userId, messageId string)
it's very easy to accidentally call as AddMessage(messageId, userId)
and then best-case you are wasting time figuring out a test failure, and worst case trying to figure out the bug IRL.V.S. an instant compile error.
I have seen errors like this many times, both written by myself and others. I think it's great to use the type system to eliminate this class of error!
(Especially in languages like Go that make it very low-friction to define the newtype.)
Another benefit if you're working with any sort of static data system is it makes it very easy to validate the data -- e.g. just recursively scan for instances of FooId and make sure they are actually foo, instead of having to write custom logic or schema for everywhere a FooId might occur.
The compiler tests the type is correct wherever you use it. It is also documentation.
Still have tests! But types are great.
But sadly, in practice I don't often use a type per ID type because it is not idiomatic to code bases I work on. It's a project of its own to move a code base to be like that if it wasn't in the outset. Also most programming languages don't make it ergonomic.