Terraform, like so many other great languages and systems, use modules to help you categories specific pieces of code so that you can reuse it. Ever since I started working with Terraform, I've heard so many variants of what one should use modules for, and I thought I'd share what I have concluded here. Please note, these are just my opinions and even though I work according to them, there is always room for interpretation.
Everything is a module in Terraform, but you shouldn't put everything in a module
Just to clarify, your base Terraform code is considered a module, a root module. This one module does keep track of everything and this is where you execute Terraform. When you call a module from the root module, they are called a child module. What should you move out from the root, and what should you keep?
I think that a healthy root module has a mix of resources and modules. When working with Azure, you don't need to move resource groups creation into a module, they are just a few lines to define. They are also resources that is used by other resources, and should probably be left out of a child module so that their lifecycle is not handled outside of the root module. There are some management patterns that contradict that, but those are edge cases which need to be treated differently.
When do we move resources to a child module
I believe that we should keep resources that are coupled in the same module, as long as it simplifies the structure of the root module, and as long as we keep out resources that have their own lifecycle and dependencies. If we use the resource group as an example of what not to bring into a child module, network interfaces and data disks would live together with the virtual machine as they share the same lifecycle. As always, there are edge cases where this is not true and that's why you need to make sure that your module is flexible enough to handle those. That is a totally different topic altogether.
Virtual machines and their supportive resources, that's one thing. What if you add multiple resources that needs more than one provider? Where should one draw the line? I still think that certain complex modules can exist, but the potential upkeep of complexity should be considered. I would argue that deploying a managed Kubernetes cluster should allow the option to create service accounts and namespaces, but incorporating deployments or the likes? That might bring the complexity to a whole different level. But, if you are in the business of deploying many clusters and they all use Prometheus and Linkerd, maybe having the module deal with that is acceptable. If you have a one cluster that needs these deployments, don't make the module more complex than it has to. Also, application deployment should probably follow one workflow so this might not be the right place to streamline your monitoring solution.
I honestly don't know if this post will be of any help to anyone. We can easily conclude with "it depends", which is always an anti-climatic statement that is very often true. But what I always come back to is this:
Use modules, it's a great way to keep yourself from repetition and help you keep your code clean.
Don't stuff everything into a module, if it doesn't follow the lifecycle or process you're trying to simplify.
Start small and expand, complexity makes it hard to maintain.
Feel free to be opinionated, but allow users to decide if they want to follow your opinion. Sometimes you want to bring your own network interface, sometimes you don't, the option is always nice to have.