A Domain-Specific Language is, as the concept name suggests, related to a Domain. Eric Evans defines that as:
Every software program relates to some activity or interest of its user. That subject area to which the user applies the program is the Domain of the software.
When using Domain-Specific Languages we have several options on how to Model the underlying Domain. Although all options share the final result of creating a specialized programming language as the main interaction channel between programmer and the system they have different strategies for Domain modeling.
I’ll use as an example a very simple Internal DSL that has operations on Cartesian coordinates as its Domain. This DSL was created to exemplify concepts, this code shouldn’t be considered production-ready.
Language as Model
There is no Domain Model, the Language is the Model
In an Internal or External DSL, the language processor will often be a program written in a powerful programming language. Using Language as Model you will implement your Model into the Language Processor itself.
In an External DSL this means that its Domain concepts will be implemented among the code that parses the language. In an Internal DSL you will implement those in the same code that you use to adapt the host language to your syntax.
Here is an example using Ruby:
class Array def +(other) modified = self.clone modified[other.axis] = modified[other.axis] + other modified end end class Fixnum attr_reader :axis def X @axis = 0 self end def Y @axis = 1 self end end #test describe 'language as the Model' do it '' do result = [1,3] + 2.Y result.should eql([1,5]) end end
Developing the Model inside the language is a simple solution that can be easier to implement and require less Modeling effort than its alternatives. One of its problems is that although it works quite well for small, simple and stable languages it won’t scale to fulfil the needs of real-world languages -that will always evolve. Change is too painful in this strategy and even the simplest syntax modification can break your underlying logic.
Integration may be a problem as well, especially for Internal DSLs. By not using the host language’s usual practices -like using proper objects to represent concepts- it is very likely that your code will be very hard to integrate with libraries, frameworks and even your own code from other modules.
I’d recommend this approach for prototypes, frozen-syntax DSLs or simple External DSLs.
Language as Interface
The Language is an interface to the Domain Model
Using DSLs doesn’t really require anything to change in the way we Model Domains. We can still apply techniques like Domain Driven Design and use a rich object graph to represent the software. The language is a way to interact with this graph.
Using Language as Interface there is nothing that you do using the DSL that you can’t possibly do just by calling objects in your host language; the language has no business rule at all. The DSL is basically a set of Domain-oriented syntactic sugar constructs.
An example in Ruby:
#domain class Cordinate attr_reader :x, :y def initialize(x,y) @x=x @y = y end def +(other) Cordinate.new(@x+other.x, @y+other.y) end end #language class Array def +(other) coordinate_to_array(self.to_coordinate + other) end def to_coordinate Cordinate.new(self[0], self[1]) end def coordinate_to_array(coordinate) [coordinate.x, coordinate.y] end end class Fixnum def Y Cordinate.new(0,self) end def X Cordinate.new(self, 0) end end #test describe 'language as interface' do it '' do result = [1,3] + 2.Y result.should eql([1,5]) end end
This is probably the more sensible approach for DSLs using the current mainstream technology. In this strategy you have the power of Domain-specific constructs while still keeping the actual object Model that can be used and reused by non-DSL clients.
It adds a new step to the development, though. It’s not just a matter of defining the Domain Model, you have to create the language that interacts with that.