In terms of DSLs, this is the crux of Puppet vs. Chef.
The danger (and power) of the Chef DSL is it allows imperative Ruby anywhere, including shops that liberally use monkeypatching "to put out fires".
Puppet is a custom DSL that doesn't have this problem but it lacks the power of Chef.
Declarative is generally even more limited than DSLs like Puppet and can't support complex logic. It maybe worth externalizing complex logic into "helpers" or other systems, but this creates a "Javascript frontend + something else backend" code-synchronization problem.
Declarative seems "easy" and "looks like data" but it rapidly runs into limitations.
(The other confusion with Chef is without unified_mode, there are 2 main execution phases that run on clients: compile-time and converge-time. unified_mode makes it easier for novices whereas 2 phase allows for the compile phase to adjust node settings when using an "API" model without databags or run_state.)