[go: up one dir, main page]

DEV Community

Cover image for Write DSLs and Code Faster
Barry O Sullivan
Barry O Sullivan

Posted on

Write DSLs and Code Faster

(Originally published on my blog.)

What is a DSL

DSL stands for Domain Specific language, this means that it is a language that is designed to solve a specific problem in a domain. DSLs are great if you want to write a generic solution to a specific problem without all the boilerplate code.

A good DSL is easy to write and understand. Once someone understands the domain language, they can read the DSL, and understand the problem, even if they're not a coder. DSLs allow us to automate many things, including aspects of development, greatly increasing our speed and lowering codes and error rates.

Here's one I made earlier

At work, I was getting annoyed with how it always took us so long to write request handlers that decode HTTP requests into domain objects. So in my spare time I designed a DSL for automatting this process, and then added it to our codebase. Here is a full example of one of these DSL files.

Exampe Request DSL

User.ScheduleAppointment has { 
  a UserId userId 
  an Appointmentatetime appointmentDatetime
  a Location location from {
    a LocationName locationName from location
    a Latitude latitude
    a Longitude longitude
  }
}

This simple DSL defines a request coming into a server, that is then turned into a command, which is then handled by the system.
The DSL defines the command, the command's inputs, and request parameters used to create those inputs. We're using a CQRS based system with Valueobjects and Commands, so our language is very simple, strongly defined and well structured. (If you don't know that a valueoject is, you can find out here)

So, in the above DSL, we have a Command "User.ScheduleAppointment" (guess what this does), which has many inputs. The first input is called "userId", it is a valueobject of type "UserId". The request parameter that's passed into the ValueObject is implied from from the input's name, which becomes "userId". You can use a different request parameter by using the "from" keyword, which you can see in line 6. This allows us to handle the case that a client is sending a request that has mispelled or misnamed fields (ie, legacy clients).

In practice, sometimes a ValueObject's input is made from other ValueObjects, rather than just a request parameter (string). That's why we have the tabbed tiers of inputs surrounded by parentheses. This allows us to build very complex, tiered objects in a simple, automated manner. From this, we can easily build the tree of inputs required to create a command.

And that's it, we've defined our language.

What are the advantages of this DSL?

You can already see the main advantage of this DSL, it allow us to autoamte a complex, error prone process. It turns out though, that we can use this DSL to solve various other issues, further highlighting how useful they are. Here are a few examples.

1. Generating requests

Making a request object client side is no longer a pain. We can use the same DSL to define the shape of the request that should be sent, and you can validate the request before you send it. Not only that, but you can automate the generation of the request itself. We created a request factory that takes in a form and tries to extract the request parameters out by key. Then we can set any remaining parameters manually. Very handy.

2. Sending and receiving requests

Sending requests to a server is easy, but tiring to implement, especially if the URLs are not uniformly named and structed. You end up writing a lot of boilerplate code that is painful to change. Well, you can automate this. We created a single Command endpoint that takes in the above request, and then decodes the command and runs it. Now we have only one endpoint to hit, so our code is drastially reduced, both client and server side.

3. Documenting requests

We can use the above DSL to create a request schema. That schema can be used in documentation, or you can make the endpoint self documenting. If someone sends a request with the method "OPTIONS", you return the Schema as JSON, making it easier for people to use and understand your API.

4. Speed of development

Once you understand the language, changing or writing a new request is quick and painless. With this DSL, I was able to write and validate an entire collection of requests (20 in total) in under 30 minutes. This would have taken me at least a day if I didn't have the DSL handy.

5. Modular

This DSL is not just specific to our current app, it can used in any app, as long as it uses commands and valueobjects (which all our apps do/will, we're DDD/CQRS junkies). So now have another tool that can rapidly speed up development across all our applications.

DSLs can be used anywhere

What you saw above is one simple DSL that has drastically improved our development speeds. It's automated a painful, error-prone process and it's also offered a host of other benefits. Think about that, that's just one DSL. Imagine what you could do with multiples ones, all working together! You can write DSLs to automated practically any process that can be defined in language. Which, if you think about it, is every process, as we define and comminicate processes to others (and ourselves) via language. The sky's the limit.

So to close, I say this. Start writing DSLs. You'll do what developers do best, automate processes. Learn how to do it, and you won't look back, infact, you'll wonder how you worked without it.

If you want to look at parsing the above DSL in Javascript, stay tuned for my next article.

Top comments (8)

Collapse
 
rafalpienkowski profile image
Rafal Pienkowski

First of all very nice article.

I've implemented a DSL language during my studies. It was generating forms by an application written in Python+Django. DSL (as its name shows) is domain specific and it was easy to use by non-technical people. It was the main part of the project. I still remember how many work cost to define DSL and write a parser to it which has made the right job. BTW the parser was a big pain in the neck.

With my small experience with custom DSL, for the case, you had described, I personally would like to use a builder with fluent methods. In my opinion, it's simpler to set up a builder than define a DSL. An entry threshold is smaller than in DSL scenario. Additionally, this would be fully supported by any IDE. For instance, available methods will be prompted.

An example of usage could look like (I've used C# syntax):

var userScheduleAppointment  = new UserScheduleAppointmentBuilder()
   .WithUserId(userId)
   .WithAppointmentDateTime(apointmentDateTime)
   .WithLocation(location)
   .Build();
Enter fullscreen mode Exit fullscreen mode

It's even possible to remove the last Build() when we declare explicitly expected result type. This is one of the advantages strongly typed languages like C# :)

What do you think about this approach?

Collapse
 
barryosull profile image
Barry O Sullivan

Glad you enjoyed the article, and thank you for the feedback.

I've done both ways, written a DSL parser/interpreter and written a fluent interface. You're right about parsers, they can be a huge pain in the neck, especially if you're writing one yourself from scratch.

I like you're suggestion above, a fluent/builder makes a lot of sense. It's not that complex a language so implementing it as a class structure is fairly simple. As you mentioned, IDE auto completion is another big win. Martin Fowler calls these internal DSLs, and it's usually the first step in creating a DSL (and maybe the last step, if it works it works).

In the next article, I'm going to show how a parser for the above can be implemented using a PEG (pegjs.org/ to be precise), ie. an external DSL. However, that's just to showcase the concept. In reality I would implement this language as you suggested.

I think that external DSLs would gain more traction if we could get better IDE support, make it easier to design the language and plug a parser into the IDE itself. Until then they're always going to be clunkier and harder to write.

Collapse
 
rafalpienkowski profile image
Rafal Pienkowski • Edited

Thanks for your answer.

I'll follow your DSL thread and I'll read your article about parser. I was very interested in this subject a few years ago but it was a very hard to find an interlocutor to talk with him/her about DSLs.

Once again great article and I'm glad that you've brought up DSL subject.

Collapse
 
prestongarno profile image
Preston • Edited

Kotlin has full support for type-safe DSLs from native code, they're easy to write combining applicatives with infix notation:

schemaProvider { kdelegateContext ->
  newBuilder<T>().withArgs<A>() takingArguments ::Always resultingIn { (args, builder) ->
     EnumAdapterImpl(clazz, args, builder.default).toDelegate(kdelegateContext)
  }
}.withAlternate { it allowing Nothing::class }
Collapse
 
barryosull profile image
Barry O Sullivan

I'll need to look into Kotlin, it looks quite concise, though it's not a syntax style I'm used to.

Collapse
 
remojansen profile image
Remo H. Jansen

If you are considering writing your own DSL from scratch, Xtext is a good option.

With Xtext you define your language using a powerful grammar language. As a result you get a full infrastructure, including parser, linker, typechecker, compiler as well as editing support for Eclipse, any editor that supports the Language Server Protocol and your favorite web browser.

More info at eclipse.org/Xtext/

However, I do prefer the fluent approach in most cases.

Collapse
 
barryosull profile image
Barry O Sullivan

Wow, that is very cool. The editing support looks amazing. I will definitely look into that.

Collapse
 
bohdanstupak1 profile image
Bohdan Stupak

I'll just leave it here for the sake of your amusement