
Sometimes in the process of describing business logic, it is necessary to compile a graph of asynchronous operations with internal dependencies, i.e. when tasks are performed asynchronously, but some tasks depend on others and thus have to “wait” until it can be started. In this post, I want to show how this problem can be solved by creating a
graphical DSL that allows the developer to visually determine the dependency graph.
Nb: English article and source code are
here.Introduction
Generally speaking, domain-specific languages ​​(domain-specific languages, DSL for short) come in three forms. The first type is text DSL, which is defined solely through text and structure, and is associated with a specific process of converting this text into code. The second type is structural DSL, where content is defined using a tree or graph-like editor. I want to discuss the third type - this is a graphical DSL, where the developer works with a graphical editor to create a visual structure, which can later be turned into code.
')
In this article, we will create a simple graphical DSL, which allows the end user to define asynchronous operations that will be organized using Pulse & Wait. To build the attached example, you need Visual Studio 2008 with the Visual Studio SDK. We will use Microsoft DSL Tools (included in the SDK) to create our DSL.
Description of the problem
Since working with Pulse & Wait is difficult, I want to make a graphical DSL that would allow me to determine the sequence of operations that can be organized using the Pulse & Wait mechanism. In particular, I want to be able to drag and drop asynchronous blocks in the editor, as well as the ability to define links between them to form rules for asynchronous, dependent execution.
Create DSL
Before we begin, let me explain the most important points when working with DSL Tools:
- In DSL Tools, graphic DSLs themselves are made using graphic DSL. At first glance this may seem confusing, but, in principle, you should understand that most of our asynchronous DSLs (which I call here AsyncDsl) will be developed using visual elements — not with a programming language. Of course, there will be a lot of code behind the scenes, but we will not encounter it often.
- DSL tools widely use T4 technology. Our graphical DSL is actually only a visual representation of XML, and T4 turns this XML into code. That way, when you edit visuals with DSL Tools, you are really editing XML.
- Your DSL is still created using C #, and it is compiled. You can extend it with partial classes, etc., which allow your DSL to behave in a certain way. We will not do anything like this in this article.
- Creating a DSL using the tools of DSL Tools applies only to the visual part - the part that allows the user to visually create XML models. The part that turns it into plain text code is a separate problem, which we will meet later.
To create a DSL project in Visual Studio, select New Project, and then Other Project Types → Extensibility → Domain-Specific Language Designer.

After clicking the OK button, you will be shown a wizard where you can identify some of the features of the DSL you are creating.
- On the first page, in addition to determining the name of your language, you can also select the initial template. This pattern defines what DSL has the initial capabilities — for example, by choosing Task Flow, you determine that the initial DSL elements will relate to flowchart structures. Regardless of which template you choose at this stage, you can always redefine the behavior of your DSL by deleting the initially generated elements.
- The second page allows you to choose a file extension for your DSL. This extension will appear in places where you will insert your DSL into your own project. In addition to the extension, the wizard generates an icon.
- The third page allows you to set some lines that define your DSL, such as the name of the product to which your DSL belongs.
- The fourth page actually forces you to sign your build — either already existing or a new key.
When done with the wizard, you get a DSL definition framework. If you have never worked with DSL functionality in Visual Studio, then the screenshot may shock you a bit.

The following elements take part in the DSL editing process:
- Dsl Designer Toolbox. This panel contains all the elements that you will work with when designing your DSL. These elements are used in the same way as in WinForms - take an element and drag it into the editor window (the central window with strange boxes).
- The DSL designer himself. Actually, this is a file with the
.dsl
extension, but as you can see, the mega-visual editor is, as I said, DSLs themselves are built using other DSLs. This DSL has two parts - on the left side there are classes and relations between them, and on the right side there are visual elements, i.e. visual reflections of DSL concepts with which the end user will work. Thus, you can imagine DSL like this: on the right is the visualization, on the left - the logic.
- Solution Explorer. When creating a DSL, you will get two projects — one defining the DSL you are doing and the other defining the editor for components related to DSL. We will talk about this later - all that is important now is to mark one single button that transforms all the patterns:

This is a very important button. As I said before, DSLs are XML specifications that translate into code. This means that in order to update our DSL definition (and definition is also DSL), you need to transform all the templates in C #. The button above does just that. Therefore, if you suddenly in the final DSL does not reflect any changes that you are sure that you have done - then you forgot to press this button.
- The DSL explorer. This is a completely new panel in the studio, which presents your DSL in the form of a tree, and looks like this:

This tree encapsulates many structural aspects of DSL. It is important to note that some tree nodes have their own set of properties, which can be seen by pressing F4.
- Pages for editing property pages exist for both elements of the DSL Explorer tree and visual elements in the DSL editor. Some DSL elements can be edited directly “in the editor” —for example, you can define the shape of a relationship (one-to-many, many-to-many, etc.) between two elements without opening the properties panel. How is it easier to do - you decide.
Let's now delve into the process of creating our DSL.
Arranging Visuals
As I already wrote, the toolbox contains all the elements with which you have to work. These elements are divided into two groups - “logical” and “visual”. The gates are those that define the structure (i.e., domain) in your DSL. Visual elements reflect those rectangles, lines, and similar elements that the user operates on when working with DSL.
The core concept of the DSL logical structure is the domain class. This class can represent
anything , depending on what subject area you are working with. As we work with asynchronous operations, one of our domain classes will be called
Operation
:

A domain class may have
properties , i.e. values ​​that the user can set. Our
Operation
class has
Timeout
,
Name
and
Description
properties that the end user can determine after dragging the instance of the
Operation
object to his model.
There is a small problem here - in fact, the user is not dragging the domain class directly into its model. Instead, it drags itself into the
OperationShape
model, which is a visual reflection of
Operation
. This class is formed from
GeometryShape
(taken from the same toolbox):

Having defined the domain class
Operation
as well as its visual representation of the
OperationShape
, they need to be linked together (if run as it is, nothing will work). For this, the Diagram Element Map element is used. In fact, this thing is the line that connects the two elements, defining the association between them. But even if you add it, still nothing will work.
Relationship between elements
Before we start working with creating toolbox controls for our DSL (which is fun), we need to talk about the relationships between the elements. There are two types of relationships - Embedding Relationship and Reference Relationship. If you use an embedding relationship, element A will be completely enclosed in element B. For example, if I have a swimlane (a large horizontal piece of visual space) and need to embed whole classes in it, then it makes sense to use an embedding relationship. If I just have blocks to attach comments to, the reference relationship will also go.
Let's look at how we will use the elements for our specific task. In the “root” of our mozheli we have an element
ExampleModel
. I will not even change the name of this element, since he will not be featured in the ultimate DSL. In order to determine if my model contains processes and comments, I draw the embedding relationship lines between the respective classes and get the following picture:

Orange boxes symbolize the relationship, with the names and cardinality of the relationship on both sides. Cardinality is later regulated by the DSL designer, so that the end user cannot break it. As for the relationship, the meaning of these orange boxes is that they allow you to link together different domain classes when editing an already finished DSL.
Note: The DSL designer applies a set of rules to your language, one of which requires that all elements are part of something. This means that all elements must be reduced to a single, “primary” container. (If you recall that DSL == XML, the reason for this requirement should be obvious.)
We used the embedding relationship to tell our DSL that both processes and comments are part of a general model. We can now use the reference relationship to determine that processes can have comments, and that these two elements can be linked.

The dotted line above indicates a reference relationship, i.e. in our case, the operation can simply
refer to the comment - and not contain it. Of course, this relationship has its own visual element (the line that connects the operation and comment), which we now talk about.
Toolboxes, finally
Having obtained the logical and visual part of your DSL, you need to give users the ability to drag and drop items from this DSL onto their designer. This is where to start - from the Editor node in DSL Explorer:

To create a new item for the toolbox, right-click on the entire DSL. You will get the following menu:

There are two options - connectors and elements. Connectors are the lines (even perhaps with arrows) that connect elements together. And the elements are block-like structures.
After creating a new item, press F4 and you will see the properties of this item:

What is important here is that several of these properties must be filled in, otherwise DSL will not start. From those that obviously need to be defined - the definition of a domain class that reflects the element, as well as the definition of the icon. (A couple of default icons have already been provided, so if you are too lazy to create your own, you can use ready-made ones.)
Run!
Summarize the DSL creation process:
- Made a base DSL using a wizard
- Added domain classes representing the concepts we need, such as a process .
- The relationships between the doom classes have been added - in our case, they determined that the operations belong to the general model and that they have comments. Also added operations transition between operations, as well as elements of the beginning and end.
- Identified the visual elements that our DSL will use.
- Associated visual elements with domain classes.
- Created toolbox controls and associated them with the corresponding classes.
Our DSL is half ready: we have defined only the visual part. After we have transformed all the templates and launched our language, we can finally start playing with our DSL:

Concepts
For our asynchronous DSL, we defined the following idioms:
- Operation
This is our unit of work, for example, to make tea. We mean that the operation can take place without any failures.
- Process
A process is a sequence of operations in a graph. The only reason we added this element is to be able to keep several operation graphs in the same class.
- Start and Finish
The process must begin and end somewhere, so we created two elements to label the beginning and end states.
- Finish-to-start transition
This transition determines that the operation can only be started after another operation is completed.
- Start-to-start transition
This transition determines that the operation can begin only when another operation has been started, and not earlier.
Let's look at a real example: the process of eating breakfast (I know, not very clever). To make breakfast, you need to put the kettle, as well as put the bread in the toaster - in any order. While everything is being prepared, I want to get some jam, but only if I have already turned on the toaster. When I got the finished bread and got the jam, I can make a sandwich. And only when both the sandwich and the tea are ready, can I start absorbing the breakfast.
Using our DSL, the whole process can be defined like this:

As you probably already guessed, the bold lines symbolize finish-to-start, and the dotted line, start-to-start.
Transform the model with T4
The visual model of breakfast exists only as DSL, so we need T4 to turn it into a complete code. Fortunately, by the time we have to do the conversion, the model has already been converted to XML format, and all that remains is to go around it and generate what we need.
The production of the final result in T4 moves by several methods, such as
WriteLine()
(writes a line to the final file) and
Push/PopIndent()
(keep the number of indents on the stack).
I will not present here the T4 transformation code - it can be downloaded from the link above. Instead, I will show what our DSL will produce from the breakfast definition.
namespace Debugging<br/>
{<br/>
using System.Threading;<br/>
partial class Breakfast<br/>
{<br/>
private readonly object MakeSandwichLock = new object ();<br/>
private readonly object EatBreakfastLock = new object ();<br/>
private readonly object GetJamLock = new object ();<br/>
private bool MakeTeaIsDone;<br/>
private bool ToastBreadIsDone;<br/>
private bool GetJamIsDone;<br/>
private bool MakeSandwichIsDone;<br/>
private bool MakeTeaStarted;<br/>
private bool ToastBreadStarted;<br/>
private bool GetJamStarted;<br/>
private bool MakeSandwichStarted;<br/>
protected internal void MakeTea()<br/>
{<br/>
MakeTeaImpl();<br/>
lock (EatBreakfastLock)<br/>
{<br/>
MakeTeaIsDone = true ;<br/>
Monitor.PulseAll(EatBreakfastLock);<br/>
}<br/>
}<br/>
protected internal void ToastBread()<br/>
{<br/>
lock (GetJamLock)<br/>
{<br/>
ToastBreadIsDone = true ;<br/>
Monitor.PulseAll(GetJamLock);<br/>
}<br/>
ToastBreadImpl();<br/>
lock (MakeSandwichLock)<br/>
{<br/>
ToastBreadIsDone = true ;<br/>
Monitor.PulseAll(MakeSandwichLock);<br/>
}<br/>
}<br/>
protected internal void GetJam()<br/>
{<br/>
lock (GetJamLock)<br/>
if (!(ToastBreadStarted))<br/>
Monitor.Wait(GetJamLock);<br/>
GetJamImpl();<br/>
lock (MakeSandwichLock)<br/>
{<br/>
GetJamIsDone = true ;<br/>
Monitor.PulseAll(MakeSandwichLock);<br/>
}<br/>
}<br/>
protected internal void MakeSandwich()<br/>
{<br/>
lock (MakeSandwichLock)<br/>
if (!(ToastBreadIsDone && GetJamIsDone))<br/>
Monitor.Wait(MakeSandwichLock);<br/>
MakeSandwichImpl();<br/>
lock (EatBreakfastLock)<br/>
{<br/>
MakeSandwichIsDone = true ;<br/>
Monitor.PulseAll(EatBreakfastLock);<br/>
}<br/>
}<br/>
protected internal void EatBreakfast()<br/>
{<br/>
lock (EatBreakfastLock)<br/>
if (!(MakeTeaIsDone && MakeSandwichIsDone))<br/>
Monitor.Wait(EatBreakfastLock);<br/>
EatBreakfastImpl();<br/>
}<br/>
}<br/>
}<br/>
A lot of code! But this code reflects the structure that we defined. Now it only remains to use the generated structure:
namespace Debugging<br/>
{<br/>
partial class Breakfast<br/>
{<br/>
AutoResetEvent eatHandle = new AutoResetEvent( false );<br/>
Random rand = new Random();<br/>
public void Prepare()<br/>
{<br/>
ThreadStart[] ops = new ThreadStart[] {<br/>
MakeTea,<br/>
GetJam,<br/>
ToastBread,<br/>
MakeSandwich,<br/>
EatBreakfast };<br/>
foreach (ThreadStart op in ops)<br/>
op.BeginInvoke( null , null );<br/>
eatHandle.WaitOne();<br/>
}<br/>
private int RandomInterval<br/>
{<br/>
get<br/>
{<br/>
return (1 + rand.Next() % 10) * 100;<br/>
}<br/>
}<br/>
public void MakeTeaImpl()<br/>
{<br/>
Thread.Sleep(RandomInterval);<br/>
Console.WriteLine( "Make tea" );<br/>
}<br/>
public void ToastBreadImpl()<br/>
{<br/>
Thread.Sleep(RandomInterval);<br/>
Console.WriteLine( "Toast bread" );<br/>
}<br/>
public void GetJamImpl()<br/>
{<br/>
Thread.Sleep(RandomInterval);<br/>
Console.WriteLine( "Get jam" );<br/>
}<br/>
public void MakeSandwichImpl()<br/>
{<br/>
Thread.Sleep(RandomInterval);<br/>
Console.WriteLine( "Make sandwich" );<br/>
}<br/>
public void EatBreakfastImpl()<br/>
{<br/>
Thread.Sleep(RandomInterval);<br/>
Console.WriteLine( "Eat breakfast" );<br/>
eatHandle.Set();<br/>
}<br/>
}<br/>
}<br/>
The result of calling this code is something like this:
Make tea
Toast bread
Get jam
Make sandwich
Eat breakfast
All done
Although of course
Make tea
and
Toast bread
can appear in a different order.
Conclusion
DSL Tools is a sophisticated but powerful tool. The key feature of this package is the ease of working with the language after it has been defined. Here I could only superficially describe working with DSL Tools, since opportunities and nuances a lot. I hope that this post motivates someone to conduct their own research. â–
