Aiming for the configuration by file support in Magnolia 5 Groovy scripts are considered as a somewhat hybrid solution with the features of config by code. Our goal is to research the options to design a DSL (Domain Specific Language) that would have lean syntax and at the same time would benefit from the possibility to write Groovy code within a definition (loops, ifs, business logic etc). Another important aspect is the code completion support in IDE's.
Dynamic builder stands for a universal tool that can describe any definition object in a convenient way. Such builders utilize Groovy's introspection and dynamic class nature goodness.
There are three ways to implement a dynamic builder with Groovy:
- Generate builder methods and properties with interceptors
- (+) Full control over DSL structure and syntax.
- (-) Slightly tedious task since all the corner cases and patterns have to be handled by hand.
- By means of
- (+) Provides factory methods for creation of DSL nodes.
- (+) Convenient for deep hierarchical structures.
- (-) However, does not suit cases when the node types vary.
- (+) Improves and complements the above mentioned approaches.
- (+) Provides a flexible strategy for object creation, method/property invocation, relation modelling etc.
- (+) A lot of default strategy parts make sense.
The main benefit of the such builders is obvious - a single builder can handle almost all the definitions. However, the dynamic nature makes them rather obscure and complicates the auto-completion support.
UI Framework already contains some definition builders written in Java (e.g.
info.magnolia.ui.dialog.config.DialogBuilder) used for instance in Blossom module. It is possible to bind them to the Groovy script.
Due various syntax sugar provide by Groovy the way those builders are used may vary. The following example shows more Java-like way of builder pattern usage.
The other snippet shows the usage of Groovy's
with operator which allows for writing the builder code in a flatter way.
Builder extension via Groovy categories
During the previous attempts of config-by-code design one of the main problems was the extensibility of the builders. As an example we could take a look at the
TabBuilder. In order to add fields to it we could have methods like
TabBuilder#select("name") etc. However, since the amount of field types is virtually infinite, such API is not possible. In order to work that around the so-called config objects were introduced.
However, with Groovy categories feature it is possible to add methods to builders as mix-ins.
Perks of existing builders
- Control over the syntax and methods of the builders
- Code completion is fully enabled apart from Script-bound variables, for the latter - there are workarounds in at least IntelliJ and Eclipse.
- Less magic - clear and sane logic
- We have write and maintain the builders ourselves
Code completion tools
Since the builders are bound to the script from the outside - an IDE will have a hard time guessing which builder is used in a current script. IntelliJ IDEA as well as Eclipse try to approach the problem of IDE DSL awareness. Both do it in a similar way but not exactly the same.
IntelliJ - GroovyDSL framework
- Allows for defining suggestion rules for scripts and classes with Groovy (partial wrappers of IntelliJ code completion foundation).
- Based on simple ideas (scopes/contributors)
- Powerful but has limitations
- worst I experienced - hard to determine the delegate type in closure, but that probably comes from dynamic nature of groovy
Eclipse - DSLD
- Similar architecture (scope/contributors)
- Not tested yet - TBD
How does it work (obsolete schema...)
Relations between modules, definition managers and registries.
Proposal: Annotated Groovy scripts
The previously observed approaches of configuration via Groovy scripts suffer from some pain points:
- Obscurity: The binding between the script and the configuration provider is implicit and not very flexible: the configuration provider would have to bind the builder and all other necessary components (config objects like
UiConfig etc) to the script via
Bindingobject. That means that the script writer (and IDE) would not be aware of what is available in the script without additional support (like code completion scripts GDSL and DSLD).
- Complexity: GDSL and DSLD would require thorough documentation and in case third party developer would want to expose their own component to the script - they'd have to write their own script to have code completion and that process has a certain learning curve.
- Abstraction issue: Definition provider must know the definition type coming from the script and hence we would need definition providers for all the configurable components (dialogs, apps, templates etc).
- Lack of IoC: Not possible to inject the components into the script via
- Builder type restriction: Relatively hard to substitute the builder implementation used in the script: since one definition provider takes care of all the dialog scripts - they all would have to share the same builder.
In order to overcome the listed drawbacks it is proposed to make the configuration Groovy scripts more explicit and powerful. Instead of treating the script as a black box that takes the bound arguments and produces some definition the type of which we can only guess - we can introduce special builder script methods:
@Dialog is a special annotation that points to the fact that the following method provides a dialog definition. It's declaration looks like this:
In such case when the script is executed the annotated methods are resolved. The method is considered to be relevant to configuration if it declares annotation that in turn is annotated with
@Definition, which also points how the resulting definition should be registered. These methods later can be invoked with all the arguments injected by the
ComponentProvider. Such an approach leads to the following perks:
- Script clearly states which objects it needs for definition construction.
- Easier code completion in IDE's
- Easier for developer to know what is available within a script
- Developer is free to chose what builder to use for definition creation.
- Definition manager is aware of:
- how many definitions are provided by the script,
- what kind of definitions they are,
- what arguments the script needs to construct each of the definitions.
- Decoupling the definition resolution process and the process of their registration:
- definitions are resolved from the script,
- registration is delegated to an object declared in
- hence we can have one definition manager that reads the scripts and automatically dispatches incoming definitions to the corresponding registries.
Source code references
Currently the annotation-based approach as a most promising is implemented as a proof of concept in the magnolia-ui project on the feature/config-sprint-1 branch (https://git.magnolia-cms.com/gitweb/?p=magnolia_ui.git;a=shortlog;h=refs/heads/feature/config-sprint-1).
On 2014-12-23, this stuff was moved into https://git.magnolia-cms.com/internal/sandbox/config-by-groovy
The previous attempt that utilises GroovyDSL code completion scripts and binding of concrete builders to Groovy is stored on the https://git.magnolia-cms.com/gitweb/?p=magnolia_ui.git;a=shortlog;h=refs/heads/feature/config-sprint-1-obsolete.