Microservice Description Language

"Domain Specific Language is a computer language specialized to a particular application domain." [Wikipedia]

Design goal of domain specific language is to be as expressive as possible. This time I'll consider how DSL can be designed in general, and will show concrete example.

What I want to create is the language that allows for generation of microservice code from simple and descriptive phrases. Therefore we want to build description of the microservice infrastructure. Usually when we want to build something (and we are using C#) we can create some sort of fluid API, to make it cleaner!

Fluent builder

Let's consider following example:

var infrastructureDescription = new MicroserviceInfrastructureDesciptionBuilder()  
    .WithDefaultMessageNamespace("DefaultNamespace")
    .WithDefaultCommunicationMean("RabbitMq")
    .WithDefaultMicroserviceNamespace("OurMicroservices")
    .WithDeclaredMessage(
            new MessageTypeDescriptionBuilder()
                    .WithTypeName("EchoMessage")
                    .Create()
                    )
    .WithDeclaredMessage(
            new MessageTypeDescriptionBuilder()
                    .WithNamespace("Some other namespace")
                    .WithTypeName("PongMessage")
                    .Create()
                    )
    .WithMicroservice(
        new MicroserviceDescriptionBuilder()
            .RespondsToWith("EchoMessage", "EchoMessage")
            .RespondsTo("PongMessage")
            .Create()
    )
    .Create();                  

We set up some defaults and created the infrastructure using builders for microservice descriptions.
Isn't it a DSL yet? It's close! We have descriptive names, we focus only on our problem domain, but still it doesn't look anyhow like natural language.
(That part is implemented here.)

States and C# based DSL

It would be nice to remove all usage of "new". It would be better to use sentence-like chains than ".With" method calls.
An example of what we are talking about:

var infrastructureDescription = new MicroserviceDescriptionDSL().  
    Default().Message().Namespace().Is("DefaultMessageNamespace.Messages").
    Message().Class("PingMessage").
        Using().Namespace("HeheLolMessages").
    Message().Class("PongMessage").
    Default().Namespace("AwesomeMicroservicesCreatedUsingDSL").
    Microservice("Pinger").Using("RabbitMq").
        Receives("PingMessage").And().Responds("PongMessage").
    Create();

Here we are! We can read whole description as sentences. But not without a cost. To make implementation possible for chain structures as Default().Message() and Default().Namespace(...) we have to return some specific state information from Default(). It's like implementing full state machine for the language in you code. Probably not very efficient way - but intellisense works perfectly in that case!
Implementation of that part is available here.

Skip parentheses

Unfortunately we have to write empty "()" when calling method without arguments. Let's imagine that compiler is so intelligent, that we can just skip them as well as "dots" between calls. And for that time, use only small-case letters for method name. I will skip the first line (new MicroserviceDescription) and last (Create()) for even better effect:

    default Message Namespace Is "DefaultMessageNamespace.Messages"
    message class "PingMessage"
        using namespace "HeheLolMessages"
    message class "PongMessage"
    default namespace "AwesomeMicroservicesCreatedUsingDSL"
    microservice "Pinger" using "RabbitMq"
        receives "PingMessage" and responds "PongMessage"

Wow! This is what I'm talking about. Simple, clear, natural and expressive. And we have some highlighting features too, thanks to usage of the C# keywords.
That's a bummer that to use that kind of code, we have to implement it as DSL "outside" of the C#.

Code generation

Other way is to just insert parentheses where it is needed, and use some external tool to run our generated code. I've tried this approach using CSScript library with full success.
How? You can check this out here.

####Interpreter + reflection

We can use our previous code and implement interpreter using the reflection:
How it can be done (in a nutshell):
* read lines, split for separate keywords
* separate keywords from parameters ("param" is a parameter, everything else is a keyword here)
* using given states' interfaces, use their methods signature, to determine if method can be called
* use reflection to call apropriate method with (or without) parameter
* use next state for further processing
* finally run Create method to get final description
Of course we use some base state at the beginning. And voila!
I will add code for this later.

Top-down approach

I've showed how we can implement a DSL using bottom-up approach. We created the builders, then some states with descriptive method names and finally an interpreter for our new language.
But of course there is other way (like always, right?).
Our natural-like language

default Message Namespace Is "DefaultMessageNamespace.Messages"  
message class "PingMessage"  
    using namespace "HeheLolMessages"
    ...

can be parsed by some automatic code-generating tool, that all it needs is a grammar file, right?
ANTLR here we come!

ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing, or translating structured text or binary files.

So I declared the grammar(full code):

grammar microservice_description_language;

options { language = 'CSharp3'; }  
microservice_description_language  
        : (statement | COMMENT)+;
statement  
        : default_message_namespace_declaration
    | default_microservice_namespace_declaration
    | default_communication_declaration
    | message_declaration 
    | microservice_declaration
        ;

default_message_namespace_declaration  
    : DEFAULT MESSAGE NAMESPACE IS* (default_message_namespace=ID);
(...)
NAMESPACE : 'namespace';  
MICROSERVICE : 'microservice';  
USING    : 'using';  
RECEIVES : 'receives';  
RESPONDS : 'responds';  
(...)

and I generated the parser code. Done!
Of course I needed some time to debug the grammar as well, but states and everything else were generated later for me. All I had to do after that was calling approriate builders' methods when the parse tree were being traversed.
Neat and simple solution I must say. The major issue is that an only available software allowing debugging features for ANTRL grammar is an old, pretty unstable Java app...

Microservice code generation

The purpose is too generate the microservice code from the description. That part is currently in very early stage. You can generate some report, and some code using T4 templates. It may or may not be upgraded in the future (it depends on the community response too, so who knows).

Through the post I've linked to the full source code multiple times, but in case someone has missed it - it's here.