Домой United States USA — software Creating a Groovy DSL for Structurizr Creating a Groovy DSL for Structurizr

Creating a Groovy DSL for Structurizr Creating a Groovy DSL for Structurizr

236
0
ПОДЕЛИТЬСЯ

Creating a configuration DSL in Groovy is a piece of cake. Once you grasp the concepts of Closures and delegation, just choose the syntax, and you’re ready.
In the previous post, we took a quick look into generating documentation with Structurizr. I really enjoyed playing with the tool, but I wasn’ t aesthetically pleased with the code necessary to create a simple diagram. Well, seems like a perfect chance to introduce you to creating Groovy DSLs and produce something useful at the same time.
We’ re going to start from the very end so that you know what we’ re aiming for and better understand the things that I’ m going to explain. One of the code samples in the previous post looked like this:
As you can see, there are quite a lot of objects and non-fluent method calls involved. With a little help from Groovy, we will be able to replace the code above with something like this:
This version is a bit longer, but that’s the price I’ m willing to pay for more expressiveness and readability. Let’s do this!
If you don’ t know Groovy well enough, the code above might seem like some sort of dark magic. But don’ t worry, it should seem super easy in just a moment.
The first thing that you need to know in order to understand the code above is that Groovy allows you to skip parenthesis in method calls. It means that the lines…
…are equivalent in Groovy to:
The second thing that you should know about Groovy is the Closure class and the related syntax. A closure in Groovy is like a pimped out version of the Java 8 lambda expression. Similarly to Java’s lambda expressions, to define a closure, we need to use the curly braces. The difference between the two is that closure’s parameters are specified inside the braces, instead of outside. When there are no arguments, you just skip the argument name and the “arrow”.
When you combine the previous two sections together, you have the answer how the softwareSystem, person, and systemContextView methods work. These methods simply take a single Closure parameter and, by omitting the parenthesis, we get a nice Config-ish syntax:
The last piece required to create a Groovy DSL like the one above is closure delegation. By default, you are allowed to use in a closure all the methods that are available in its outer scope (AKA the owner) .
For DSL purposes, the Closure class has a delegate parameter that allows us to extend the pool of available methods with methods of an arbitrary object. An example might do a better job explaining this than words:
If we were to convert the take method to an English conversation, it basically tells the closure: “Hey, if you can’ t find a required method, try delegating to this Talker object.” And so it does delegate in our example.
Now, this is enough to get some basic delegation working, but the IDEs will get lost without a bit of help. How is the IDE supposed to know what kind of object are you going to delegate to, huh? Well, there’s an annotation for that.
With this annotation in place, the IDE correctly suggests using Talker’s methods inside the closure. At this point, it should be clear where the methods like person, name, description etc. are taken in subsequent closures of our HelloWorld example.
Now, I’ d love to say that it’s enough to start writing the DSL itself, but there’s one more little thing. By default, the closure first looks for methods inside its outer scope and then asks the delegation object. This default behavior would be bad for our softwareSystem method, which has two different meanings in two different scopes.
Luckily, Groovy allows us to change this default behavior. We simply need to change the Closure’s resolveStrategy.
Huh, that’s it! We’ re ready to create an actual, useful DSL.
I trust that you’ re a smart person and that I did a good job explaining in the previous section, so we’ ll rush through the DSL implementation, rather than walking it step by step.
The entry point to our DSL is the Structurizr class. It has only one method, which initializes configuring a workspace. It is the same method that we used in the first line of our HelloWorld example’s main.
As you can see, we’ re just delegating to a WorkspaceConfigurer here and expecting it to return a complete Workspace object afterward.
This “configurer” is just one of many similar classes, each responsible for configuring a single thing in the workspace. Each of them keeps necessary data and references to other related “configurers.” Our WorkspaceConfigurer currently looks like this:
This piece of code might seem complicated at first, but actually, it’s really simple. We’ ve got two “real” fields in the class: name and description. For these fields, we expose methods with the same names. For each of the elements that will be configured in a separate closure, we need to expose a delegating method and remember the configurer that we’ re delegating towards. After all the configuration is done and apply is called, we simply set the name and description, and let the other configurers do their job.
Now, we could go through each of the configurers one by one and explain what they do, but there’s no point. They are really similar and we’ d be wasting your precious time. If you want to see a more complete version of the DSL’s codebase, you can find it here.
Creating a configuration DSL in Groovy is a piece of cake. Once you grasp the concepts of Closures and delegation, all you need to do is simply bash the necessary methods through some helper (configurer) objects. As you can see, although there is some typing involved, you can easily create a nice DSL for the tools you like if they lack a friendly API. Mine was Structurizr. What’s yours?

Continue reading...