I'm way late to think of this and circle back here, but if you don't mind briefly switching languages to F#, I first grasped these concepts via "Designing with types: Making illegal states unrepresentable"
https://fsharpforfunandprofit.com/posts/designing-with-types...
... and I would suggest actively playing around with the code examples, extending them, breaking them intentionally etc. somewhere like https://dotnetfiddle.net/
I think the article is brilliant but as I recall I wasn't able to grasp the concepts from reading alone.
That same website has a separate article on building a calculator (in code) that I recall was also excellent.