WORK-IN-PROGRESS: - this material is still under development
Last significant update: 09 Apr 08
Contents
Domain Specific Language is a useful term and concept, but one that has very blurred boundaries. Some things are clearly DSLs, but others can be argued one way or the other. But now we've seen examples of the various kinds of DSLs that exist it's a good time to step back and try to get a sense of what makes a DSL.
It's hard to state definitions for a term like this that has been widely used for a long time, but since I'm writing this book I ought to try. (This will recap a bit material I went over in the example.)
Domain Specific Language (noun): a computer programming language of limited expressiveness focused on a particular domain.
There are four key elements to this definition:
As the example suggests, there are three main styles of DSL: External, Internal, and Language Workbenches. Each style of DSL has its own community with often very little overlap between them. People who have lots of experience doing internal DSLs in Lisp or Ruby may have very little knowledge of using parser generators for external DSLs, despite the fact that many techniques are the same.
External DSLs are DSLs that use a different language to the main language of the application that uses them. Often they have a custom langauge, but another common case is to tag along with another syntax - XML is a common choice. The Unix tradition of little languages is this style. Examples of external DSLs that you probably have come across include regular expressions, SQL, awk, and XML configuration files for things like struts and hibernate.
Internal DSLs use the same general purpose programming language that the wider application uses, but uses that language in a particular and limited style. Only a subset of langauge constructs are used, and only to drive a particular aspect of the application. The classic example of this style is Lisp, where lisp programmers often talk about lisp programming as creating and using DSLs. Ruby has also developed a strong DSL culture: many Ruby libraies come in the style of DSLs. In particular Ruby's most famous framework, Rails, is often seen as a collection of DSLs.
In the state example, the internal DSL looked rather like the external little language. I should also point out that another style of internal DSLs involves tagging the elements of the programming language itself using things like annotations.
[TBD: Can I do something like this in an annotative style? (see diary Aug 13) No, but should add discussion here.]Language Workbenches are IDEs designed for building DSLs. Such tools allow you to define the abstract syntax of the language together with editors and generators. The editors allow you to have sophisticated programming environments for your DSL, similar to that of modern IDEs. Language Workbenches store DSL programs in an abstract form and project the program through the editor in a different cycle to the traditional parse and generate cycle and source driven langauges.
Another way of looking at a DSL is that it is a way of manipulating an abstraction. One way of looking at software development is that we build abstractions and then manipulate these abstractions, often in multiple levels. The most common way for us to build in abstraction is to implement a library or framework. The most common way we then manipulate this framework is through API calls. In this view a DSL is a front-end to a framework providing a different style of manipulation to the usual API calls. A consequence of this is that DSLs tend to follow libraries. You can think of a DSL as nothing more than a veneer over the library.
When people talk about DSLs it's easy to think that building the DSL is the hard work. In fact the usual hard work is building the framework, the DSL then just layers on top of it. It's still effort to get a DSL that works well, but the effort is usually much smaller than the underlying library.
To help explain this relationship I used an example that started with a library and showed me layering multiple DSLs on top of it. Although many DSLs are developed that way it isn't the only way. In many situations it's better to start with the DSL and use that to drive the library design.
As I said a few paragraphs ago, DSLs are a concept with blurry boundaries. While I don't think anyone would disagree that regular expressions are a DSL, there's plenty of cases that are open to reasonable argument. As a result I think it's worth talking about some of these cases here as they help provide a better idea of how to think about DSLs.
Each style of DSL has different boundary conditions, so I'll discuss them separately. As we go through these it's worth remembering the distinguishing characteristics of DSLs are their language nature, domain focus, and limited expressiveness. As it turns out the domain focus isn't a good boundary condition - the boundaries more commonly revolve around limited expressiveness and the language nature.
I'll start with internal DSLs. Here the boundary question is the difference between an internal DSL and a command-query API. In many ways internal DSLs are nothing more than a quirky API (as the old Bell labs saying goes: "library design is language design"). In my view the heart of the difference is the language nature. Mike Roberts suggested to me that a command-query API defines the vocabulary of the abstraction, but an internal DSL adds a grammar. As a result an internal DSL should have the feel of putting together whole sentances, rather than a sequence of disconnected commands. This is the basis for calling these kinds of APIs fluent interfaces.
Limited expressiveness for an internal DSL is obviously not a core property of the language since the language of an internal DSL is a general purpose language. In this case limited expressiveness comes from the way you use it. When forming a DSL expression you limit yourself to a small subset of the general language features. It's common to avoid conditions, looping contstructs, and variables. Piers Cawley called this a pidgin use of the host language.
With external DSLs the boundary is with general purpose programming languages. Languages can have a domain focus but still be general purpose langauges - a good example of this is R, a language and platform for statistics. R is very much targetted at statistics work, but has all the expressiveness of general purpose programming langauges. Thus, despite its domain focus, I would not call it a DSL.
A more obvious DSL is regular expressions. Here the domain focus (matching text) is coupled with limited features - just enough to make text matching easy. As a result a common indicator of a DSL is that it isn't turing complete. DSLs usually avoid the regular imperative control structures (conditions and loops), don't have variables, and can't define sub-routines.
But these tests don't completely solve the question. A good example of a really awkward boundary case is XSLT. XSLTs domain focus is that of transforming XML documents, but it has all the features one might expect in a regular programming language. In this case I think it's more how its used that matters rather than the language itself. If XSLT is being used to transform XML, then I would call it a DSL. However if it's being used to solve the nine queens problem then I would call it a general purpose language. A particular usage of a language can put it on either side of the DSL line.
Another boundary with external DSLs is with serialized data structures. So is a list of property assignments (color = blue) in a configuration file a DSL? Here I think the boundary condition is the language nature. A series of assignments lacks fluency, so doesn't fit the criteria.
A similar argument lies with many configuration files. Many environments these days provide a lot of their programability through some kind of configuration file, often using XML syntax. In many cases these XML configurations are effectively DSLs. However this may not always be the case. Sometimes the XML files are intended to be created by other tools, so that the XML is only used for serialization and not intended to be used by humans . In that case, since humans aren't expected to use it, I wouldn't classify it as a DSL. Of course it's still valuable to have a storage format be human readable, as it can be useful in debugging. The question isn't whether it's human readable or not, but rather whether the representation is a human's main way of interacting with that aspect of the system.
In practice one of the biggest issues with these kinds of configuration files that even though they aren't intended to be human-edited, they end up being the primary editing mechanism in practice. In this case the XML becomes a DSL by accident.
With language workbenches the boundary is between a language workbench and any application that allows a user to design their own data structure and forms - something like Access. After all it's possible to take state models and represent them in a relational database structure (I've seen far worse ideas). You can then produce forms to manipulate them. Here there are two issues: is Access a language workbench and is such a thing you define a DSL?
I'll start with whether a state machine embedded in Access forms is a DSL. Because we are building a particular application for the state machine, we have both domain focus and limited expressiveness. The critical question is over the language nature. If we are putting data in forms and putting them in a table then there usually isn't a real language like feel to it. A table can be an expression of a language nature - FIT and Excel both use a tabular representation and both have a language feel to them (I would consider FIT to be domain specific and excel to be general purpose). But most applications do not try to acheive that kind of fluency. Most applications create forms and windows that don't stress the interconnections. The textual interface of MPS has a very different feel to most form based UIs. Similarly few applications allow you to lay out a diagram to define how things are put together.
As to whether Access is a langauge workbench, I'd go back to intention. Access isn't design to be a language workbench, sure you can use it that way if you really want. Look at how many people use Excel as a database - but it isn't designed to be one.
In a broader sense is a purely human jargon a DSL? A common example that's bandied around is the language used to order a coffee at Starbucks: "venti, half-caf, non-fat, no-foam, no-whip latte". The language is nice because it has limited expressiveness, a domain focus, a sense of grammar as well as vocabularly. It falls outside my definition, however, because I use DSL to only mean a computer language. If we implemented a computer language to understand starbucks expressions then that would truly be a DSL, but the words we spout when getting our caffine fix are a human language. I use domain language to mean a domain specific human language and reserve Domain Specific Language for computer langauges.
So what has this discussion of the boundaries of DSLs taught us. Hopefully one thing that is clear is that there are few sharp boundaries. Reasonable people can disagree on what is a DSL. Tests like language nature and limited expressiveness are themselves very blurry, so we should expect the result to exhibit the same blur.
Often the essence of the boundary doesn't lie in the characteristics of the language, but in the intent of either the designers or users of this language. If the designers of XSLT designed it to be about XML transformations then I'd argue it's a DSL. If a uses a DSL for its purpose then it stays a DSL, but if someone uses a DSL in a general purpose manner, then it's no longer a DSL (in this usage).
As so often with blurry boundaries, fundamentally it doesn't matter that much. If you have a useful tool, whether it's a DSL or not is far less important than whether the tool is useful. Calling something a DSL is mostly about helping to describe it to other people. It's also about knowing whether the techniques in this book are likely to be relevant to building and using it.
The secret panel state machine example I used as the introductory example is a stand-alone DSL. By this I mean that you can look at a block of DSL script, typically a single file, and it is all DSL script. If you were familiar with the DSL but not familiar with the the host language of the application, you should be able to understand what the DSL is because the host language either isn't there (in the external case) or is subdued by the internal DSL.
Another way DSLs appear is in a fragmentary form. In this form little bits of DSL appear inside host language code. You can think of them as enhancing the host language with additional features. In this case you can't really follow what the DSL is doing without understanding the host language.
For an external DSL, a good example of a fragmentary DSL is regular expresssions. You don't have a whole file of regular expression in a program, instead you have little snippets of regular expressions interspersed with regular host code. Another example of this is SQL, which is often used as occasional SQL statements within the context of a larger code routine.
Similar fragentary approaches are used with internal DSLs. A particularly fruitful area of internal DSL development has been in the unit testing world, particularly expectation grammars in mock object libraries. Again these use short bursts of DSLs within a larger host code context. A particularly popular language feature for internal fragmentary DSLs is the annotation. Annotations allow you to add metadata to host code programming elements, which makes them very suitable for fragemtary DSLs but useless for stand-alones.
So now I hope were pretty much on board with what a DSL is, the next question is why should we consider using one.
DSLs are a tool with limited focus. They aren't something like Object-Orienation or Agile Processes which are fundamental shift in how we think about software development. Instead DSLs are a very specific tool for very particular conditions. A typical project might use half a dozen or so DSLs in various places, indeed they already do.
I said earlier on that a DSL is a thin veneer over a model. This phrase should remind us that whenever you think about the benefits (or disadvantages) of a DSL it's important to separate the benefits provided by the model from the benefits of the DSL. I find it's a common mistake that people confuse the two.
DSLs have the potential to realize certain benefits. When you are considering using a DSL you should consider these benefits and decide which ones are applicable to your circumstances.
The heart of the appeal of a DSL is that it provides a means to more clearly communicate the intent of a part of a system. The contention is that if you read Miss Grant's controller definition in a DSL form it's easier for you to understand what it's doing than the command-query API of the model alone.
This clarity isn't just an aesthetic desire. The easier it is to read a lump of code the easier it is to find mistakes and the easier it is to modify the system. (It's also usually easier to write too - but tools can often reduce that improvement.) So for the same reason we encourage meaningful variable names, documentation, clear coding constructs - we should encourage DSL usage.
The model alone provides a considerable improvement in productivity. It avoids duplication by gathering together common code, above all it provides an abstraction to think about the problem that makes it easier to specify what's going on in an understandable way. A DSL enhances this by providing a more expressive form to read and manipulate that abstraction.
I belive that the hardest part of software projects, the most common source of project failure, is communication with the customers and users of that software. By providing a clear yet precise language to deal with domains, a DSL can help improve this communication.
This benefit is more nuanced than the simple productivity argument. For a start many DSLs aren't suitable for domain communication - DSLs for regular expressions or build dependencies don't really fit in here. Only a subset of stand-alone DSLs really apply to this communication channel.
When people talk about DSLs in this context it's often along the lines of "now we can get rid of programmers and have business people specify the rules themselves". I call this argument the COBOL fallacy - since that was the expectation with COBOL. It's a common argument, but I don't think it improves with repetition.
Despite the COBOL fallacy, I do think DSLs can improve communciation. The key to this is not that domain experts will write the DSLs themselves, but that they can read them and thus understand what the system thinks its doing. By being able to read them, domain experts can spot mistakes. They can also talk more effectively to the programmers who do write the rules, perhaps writing some rough drafts which can be refined into proper DSL rules.
I'm not saying here that domain experts should never write DSLs themselves. I have run into cases where they want and embrace that ability. However I think the biggest gain from using a DSL in this way comes when they start reading the DSL.
Involving domain experts in a DSL is very similar to involving domain experts in building a model. I've often found great benefit by building a model together with domain experts because it deepens the communication between software developers and domain experts. A DSL provides another technique to engage that communication. Depending on the circumstances you might find domain experts participating in the model and the DSL, or the DSL only.
That said, even getting to the point of domain experts reading a DSL is a difficult task. Even if you don't succeed however, the developer productivity gain may well be worth the effort. I look at the domain communication benefit as one that's difficult to acheive but has a high payoff. Developer productivity has a lower payoff, but is much easier to pull off.
When talking about why we might want to express our state machine in XML, one strong reason was that the definition could be understood at run time rather than compile time. This kind of thing, where we want code to run in a different environment, is a common driver for using a DSL. For XML configuration files, shifting logic from compile time to runtime is a common reason.
There are other useful shifts in execution context. One project I looked at needed to trawl though databases to find contracts that matched certain conditions and tag them. It would be slow to read all of the contracts into memory to carry out the checks specified by the DSL. However once the team had a DSL read into an abstract representation they could then generate SQL to perform the queries and modifications and do all the processing in the database. Writing the rules in SQL by had was too difficult for the developers to do let alone the business people. However the business people could read (and in this case) write the appropriate expressions in the DSL.
Using a DSL like this can often make up for limitations in a host language like, allowing to express things in a comfortable DSL and then generating code for the actual execution environment to use.
A model can always facilitate this kind of shift. Once you have a model it's easy to either execute it directly or generate code from it. Models can be also be populated from a forms-style interface as well as a DSL. A DSL helps here becuase it allows the represenations of particular configurations to be captured and controlled using the same mechanisms that we use for regular code and thus better integrated with our development process.
This relates to a spurious benefit of a DSL. I've heard people argue that the good thing about a DSL is that it allows the same behavior to be executed in different language environments. One could write business rule that generate code in C# and Java, or describe validations that can run in C# on the server and javascript on the client. This is a spurious benefit because you can gain this just by using a model, you don't need a DSL at all. A DSL can make it easier to understand these rules, but that's a separate issue.
Mainstream programming is pretty much all done using an imperative model of computation. This means that we tell the compter what things to do in what sequence, control flow is handled using conditionals and loops, we have variables - indeed lots of things that we take for granted. Imperative computation has become popular because it's relatively easy to understand and easy to apply to lots of problems. However it isn't always the best choice.
The state machine is a good example of this. We can write imperative code and conditionals to handle this kind of behavior - it can be pretty nicely structured too. But thinking of it as a state machine is often more helpful. Another common example is defining how to build software. You can do it with imperative logic, but after a while most people recognize that it's easier to do with a Dependency Network (to run tests you have to have your compilations up to date). As a result languages designed for describing builds (such as make and ant) use a dependencies between tasks as their primary structuring mechanism.
As with most of these things, the core of using an alternative computational model is to build a model that supports it - what I call a Adaptive Model. Such a model can handle an alterantive compuational sytle without any DSL present. But once you've done this, it's usually easier to manipulate that model with some form of DSL because the command style that makes sense in your alternative computational model won't be the same as that that makes sense in the default imperative model. A DSL thus makes it easier to use an alternative compuational model and realize its full benefits.
Having talked about when to use a DSL, it only makes sense that I talk a bit about when not to use them, or at least the problems involved in using them.
Fundamentally the reason why you shouldn't use a DSL is because you don't see any of the benefits of a DSL applying to the situation you're considering. Or at least you don't see the benefits being worth the cost of building the DSL.
Even when DSLs are applicable they do come with problems. On the whole I think these problems are currently overstated, usually because people aren't familiar enough with how to build DSLs and how they fit the broader software development picture. Also many commonly stated problems with DSLs run into the same confusion between DSL and model that plague many stated benefits with DSLs.
Many problems with DSLs are specific to one of the particular styles of DSL and to understand these issues you need to have a deeper understanding of how these DSL are implemented. As a result I'll leave the discussion of these problems till later, for now I'll just look at the broad problems in keeping with what we've currently discussed.
As with any new technique, there's cost involved to mount the learning curve to learn how to use it, and how to use it well. The learning curve only has to be mounted once, when you're up it you can use that knowlege on all future projects, but you always have to pay it that first time. This is a particularly big issue with DSLs because the knowledge of how to work with them is not widespread enough.
Different styles of DSLs have different things to learn which involve different costs, as I'll outline later. But there's also much that comes in common - particularly as you get used to the notion of populating a Semantic Model.
The biggest problem with getting up the learning curve is that there's very little been written to help you get up it. This is what motivated me to write this book. My hypothesis is that with a book like to this to get you going, you'll get up the learning curve faster and reduce the cost.
Even once you're up the learning curve, it still takes time and effort to build a DSL. While this cost isn't zero, I do belive it's usually overstated because people aren't familiar with how to do them and focus on the cost of the learning curve. To truly evaluate the costs you should use the knowledge in this book and experiment with some of the technologies I mention. Once you've done some experimentation you'll have much better feel for how to do this kind of work.
The cost of building a DSL shouldn't be confused with the cost of building a model. In many situations building a good model is a worthwhile way of managing the complexity of a part of a system. The cost of building a DSL to populate that model is usually much smaller than the cost involved in building the model itself.
Many people voice a concern that language design is hard, and therefore is beyond the skill level of most project teams. As with the language cacophohy problem, again we need to remember that DSLs are limited in expressiveness, thus the effort to design and understand them is much less than for a general purpose language. We also must remember that libraries and their APIs are just as difficult to design.
I've no doubt that a wider adoption of the techniques in this book will lead to some pretty awful DSLs. The question however isn't whether those DSLs are bad, but are the bad DSLs worse than the command-query APIs that they wrap? The nice thing about DSLs is they are relatively easy to change or discard should they turn out badly. If you screw up a DSL design it's easy to replace it with another that wraps the same model.
The most common objection I hear to DSLs is what I call the language cacophany problem. This concern is that languages are hard to learn, so using many languages will be much more complicated than using a single language. This makes it harder to work on the system, since you need to know multiple languages and thus slower to introduce new people to the project.
When people talk about this concern, there's a couple of misconceptions that they commonly have. The first is they often mistake the effort of learning a DSL with the effort of learning a general purpose language. DSLs are far, far simpler than a general purpose language, and thus much easier to learn.
Many critics understand this, but still object to DSLs because even if they are relatively easy to learn, having many DSLs make it harder to understand what's going on in a project. The misconception here is that a project will always have complicated areas that are hard to learn. Even if you don't have DSLs you will typically have many abstractions in your code-base that you need to understand. Any non-trivial library takes a while to get the hang of. The learning costs of understanding these models are there even without a DSL in sight.
The true learning cost question is how much harder is it to learn a DSL than to learn the underlying model on its own. I'd argue that the incremental cost of learning the DSL is quite small compared to the cost of understanding the model. Indeed since the whole point of a DSL is to make it easier to understand and manipulate the model, having a DSL should reduce the learning cost.
This leads me neatly to another issue, that of migrating DSL code. If you have thousands of lines of DSL code, what happens if you want to change the DSL?
The usual point that people forget here is what the alternative is. A DSL usually wraps an API - so if you don't have the DSL you have thousands of lines of code that's dependent on an API, which leads to the same question. The answer is also the same, which is that once you have code dependent on your code, particulary code external to your project, you have to be careful about change. It doesn't mean you can't change anything, just that things have to be done slowly if they break backward compatability.
For an API that's not published (available outside your project) one true disadvantage of DSL code is that it can be harder to refactor. Changing an API is often easy now with automated refactoring tools. Internal DSLs can often use the same tools but External DSLs are stuck.
One technique that can be very effective is to use Migration Execution. The point here is that having done all the work to define a parser for your DSL, it's then relatively easy to get the Semantic Model to generate source code for itself, incorporating any changes you might wish to make. The same basic technique can often work for APIs as well, but the extra layer of a DSL usually makes it easier to do.
Another way of dealing with this is to continue to support old versions of a DSL. Just as there's no reason why you can't have multiple DSLs for the same model, there's no reason why you can't have multiple versions of a DSL translated into the same model. Obviously you don't want to go mad on this, but it's a plausible tactic for a few versions.
One way to reduce the number of versions, for both DSLs and command-query APIs, is to keep changes in the DSL be backwards compatable. This takes care, but is often easier with external DSLs than for internal DSLs and APIs. This reduces the need to do migrations.
This book is about Domain Specific Languagues, but it's also about techniques for Language Processing. The two overlap because 90% of the use for language processing techniques in average development teams is for DSLs. But these techniques can be used for some other things as well and I would be remiss not to discuss some of these.
I ran into one excellent example of this when visiting a ThoughtWorks project team. They had the task of communicating to a third party system by sending messages whose payload was defined by COBOL copybooks. COBOL copybooks are a data structure format for records. There were a lot of them, so my colleague Brian Egge decided to work with them by building a parser for the subset of COBOL copybook syntax in use, and generating java classes to interface to these records. Once he'd built the parser he could happily interace to as many copybooks as he needed, none of the rest of the code needed to know about cobol data structures, and any changes could be handled with a simple regeneration. It would be an appalling strech to call cobol copybooks a DSL - but the same basic techniques that we use for external DSLs did the trick.
Another example of this is one that caused quite a discussion on the net. The well known tech writer Joel Spolsky posted an article where he described a custom language his firm had written called Wasabi. Originally they built their bug-tracking product "FogBugz" in VBScript. That was fine until they got potential customers who wanted to run FogBugz on unix servers. To support unix they needed something that ran in PHP and they didn't want to rewrite the whole application.
Their solution was to write a parser for VBScript, well actually the subset of VBScript that they actually used, and then write a code genertor for PHP. Bingo they ran on PHP. Over time they decided to evolve their host language into a mutant form of VBScript that supported some useful extra language features. They could do this simply adding a code generator for VBScript. Essentially they ended up with their own custom general purpose programming language implemented using source-to-source translation.
This isn't a story about DSLs, although some commentors did think it was. VBScript goes nowhere near my definition of a DSL - it's a general purpose language instead. But again they used the kind of language processing techniques that I talk about in this book.
I don't know if they would have gone down this route had they known about it in advance. I doubt they would and I certainly would not advise anyone else to. But it was a good solution to the hole they found themselves in to get those Unix sales. It's a tricky route to go becuase general purpose languages are much harder to process than DSLs. But there are times when a difficult route is still better than the alternatives.
In their case they started with VBScript and stayed with a mutated form of VBScript. Another option in this kind of situation is to use the source-to-source translation as a migration step. Generate target code in a new language and then throw away the original code and the translator. The biggest issue with this is that it's hard to produce good code in the target language that will be easy to evolve in the future. But rewriting by hand is rarely a cake-walk either.
In this opening I introduced a DSL by first describing a framework and its command-query API, and then layering a DSL on top of the API to make it easier to manipulate. I used this approach because I think it's easier to understand DSLs that way, but it's not the only way that people use DSLs in practice.
A common alternative is to define the DSL first. In this mode you begin with some scenarios and write those scenarios in the way you'd like the DSL to look. If the language is part of the domain functionality it's good to do this with a domain expert - this is a good first step to using the DSL as a communication medium.
Some people like to start with statements that they expect to be syntactically correct. This means that for an internal DSL they'll stick to the syntax of the host language. For an external DSL they'll write statements they are confident they can parse. Others are more informal at the begining and then take a second pass thorugh the DSL to get it close to a reasonable syntax.
So doing the state machine in this case you'd sit down with some people who understand the customers' needs. You'd come up with a set of example controller behaviors, either based on what people wanted in the past, or you think they'll desire. For each of these you would try to write them in some DSL form. As you work through the various cases you'll modify the DSL to support some new capability. By the end of the exercise you'll have worked through a reasonable sample and got a pseudo-DSL description of each of them.
If you're using a language workbench, you'll need to do this stage outside the workbench, using a plain text editor or regular drawing software or pen and paper.
Once you have a representative set of pseudo-DSLs you can start implementing them. Implementing here invovles designing the state machine model in the host language, the command-query API for the model, the concrete syntax of the DSL, and the translation between the DSL and the command-query API. There are different ways people do this. Some might like to do little bits at a time across all these elements: building a little bit of model, the DSL to drive it, and hooking that thread all up with tests. Others might prefer to build and test the framework first and then layer the DSL over it. Yet others might like to get the DSL in place and then build the library and fit them together. As I'm an incrementalist I prefer thin slices of end to end functionality, so I go with the first of the three.
So I might start with a simplest of the cases that I see. I'd program a library that can support that case with test-driven development. I'd then take the DSL and implement that, tying it to the framework I'd built. I'd be happy to make some changes to the DSL to make it easier to build although I would run those changes past the domain expert to ensure we still have a common communication medium. Once I have one controller working I'd pick the next one. I would evolve the framework and tests first, then evolve the DSL.
This doesn't mean that the model-first route is a bad one, indeed its often an excellent choice. Usually this occurs because you don't think about using a DSL at first, or because you're not sure you'll need one. You thus build the framework, work with it for a while, and then decide that a DSL would be a useful addition. In this case you might have a state machine model up and running and used for many customers. You realize that it's harder than you'd like to add new customers, so you decide to try a DSL.
In this case I'd again like to start with a session where we'd look at all the controllers we currently have and sketch out pseudo-DSL for each one. Then I'd implement the DSL scenario by scenario, much as in the earlier case. The only difference is that I wouldn't make deep changes the underlying framework. I would be happy to add methods to the framework to help support the DSL.
There are many cases, of course, where you don't even know you have a framework. You might build several controllers and only having built a few of them do you realize that there is a lot of common functionality. I'd then refactor the system to make the separation between model and configuration code. This separation step is the vital step here. While I might have a DSL in mind while doing it, I'd be more inclined to get the separation done first before putting the DSL on top.
However there's another evolution strategy. Imagine we have lots of controllers writen, but without any separation into library and assembly code. Another strategy we could use is to start working on a DSL, but have the DSL processor generate code to replace bits of the controller code without trying to refactor the system into framework and configuration. It wouldn't clean up the host code system, but it would provide a way to understand and more easily add new controllers. In general my preference would be to refactor to make the framework/configuration separation, but this strategy might make sense if you're faced with gnarly code with poor test coverage that's not easy to get under test.
[TBD: Include discussion of Syntactic Noise]