The split between parameterized classes and logic sounds a bit like the split between Puppet and Hiera. The idea was probably a good one, but something about the implementation made people go overboard with it. I feel IaC really peaked around Puppet 3 and Chef 1. IaC should be simple enough that people use it, and trivial to write providers for. People tend to glue much too large libraries to their IaC platforms and…
Chef and Puppet are configuration management systems, not really Infrastructure as Code.
But hardware needs to be configured. And load balancers, firewalls, clusters etc. are perfectly suited for the declarative style of the Ansible/Puppet/Chef type of tools. That is what people usually mean by "IaC", as silly as it may be.
Those tools really shine when used end-to-end. The defintion of an application can contain which ports need to be open towards backends, database users, and health check parameters for the load balancer. The system can then declaratively sort out the respective templates, and configuration really lives in one place. Shared secrets are defined exactly once, and rotation is deterministic across the entire environment.