InfoQ: An Approach to Internal Domain-Specific Languages in Java

来源:百度文库 编辑:神马文学网 时间:2024/04/16 15:24:23
An Approach to Internal Domain-Specific Languages in Java
Posted byAlex Ruiz and Jeff Bay onFeb 19, 2008 04:29 PM
Community
Java
Topics
Domain Specific Languages
Introduction
A domain-specific language (DSL) is commonly described as acomputer language targeted at a particular kind of problem and it isnot planned to solve problems outside of its domain. DSLs have beenformally studied for many years. Until recently, however, internal DSLshave been written into programs only as a happy accident by programmerssimply trying to solve their problems in the most readable and conciseway possible. Lately, with the advent of Ruby and other dynamiclanguages, there has been a growing interest in DSLs amongstprogrammers. These loosely structured languages offer an approach toDSLs which allow a minimum of grammar and therefore the most directrepresentation of a particular language. However, discarding thecompiler and the ability to use the most powerful modern IDEs such asEclipse is a definite disadvantage with this approach. The authors havesuccessfully compromised between the two approaches, and will arguethat is quite possible and helpful to approach API design from the DSLorientation in a structured language such as Java. This articledescribes how it is possible to write domain-specific languages usingthe Java language and suggests some patterns for constructing them.
RelatedVendorContent
IBM Web 2.0 Developer eKit: Free Tutorials, Webcasts, Whitepapers
IBM IMPACT Conference: Open Source tips, plus how to enhance J2EE apps with Ajax & Web 2.0
Create a photo album application with Project Zero and REST design principles
Info 2.0: IBM‘s vision for the world of Web 2.0 and enterprise mashups (Webcast)
Delivering a Breakthrough Java ™ Computing Experience
Is Java suited for creation of internal Domain-Specific Languages?
Before we examine the Java language as a tool for creation of DSLswe need to introduce the concept of "internal DSLs." An internal DSL iscreated with the main language of an application without requiring thecreation (and maintenance) of custom compilers and interpreters. MartinFowler has written extensively on the various types of DSL, internaland external, as well as some nice examples of each. Creating a DSL ina language like Java, however, he only addresses in passing.
It is important to note as well that it is difficult todifferentiate between a DSL and an API. In the case of internal DSLs,they are essentially the same. When thinking in terms of DSL, weexploit the host language to create a readable API with a limitedscope. "Internal DSL" is more or less a fancy name for an API that hasbeen created thinking in terms of readability and focusing on aparticular problem of a specific domain.
Any internal DSL is limited to the syntax and structure of its baselanguage. In the case of Java, the obligatory use of curly braces,parenthesis and semicolons, and the lack of closures andmeta-programming may lead to a DSL that is more verbose that onecreated with a dynamic language.
On the bright side, by using the Java language we can takeadvantage of powerful and mature IDEs like Eclipse and IntelliJ IDEA,which can make creation, usage and maintenance of DSLs easier thanks tofeatures like "auto-complete," automatic refactoring and debugging. Inaddition, new language features in Java 5 (generics, varargs and staticimports) can help us create a more compact API than previous versionsof the language.
In general, a DSL written in Java will not lead to a language thata business user can create from scratch. It will lead to a languagethat is quite readable by a business user, as well asbeing very intuitive to read and write from the perspective of theprogrammer. It has the advantage over an external DSL or a DSL writtenin a dynamic language that the compiler can enforce correctness alongthe way, and flag inappropriate usage where Ruby or Pearl would happilyaccept nonsensical input and fail at run-time. This reduces theverbosity of testing substantially and can dramatically improveapplication quality. Using the compiler to improve quality in this wayis an art however, and currently, many programmers are bemoaning the"hard work" of satisfying the compiler instead of using it to build alanguage that uses syntax to enforce semantics.
There are advantages and disadvantages to using Java for creationof DSLs. In the end, your business needs and the environment you workin will determine whether it is the right choice for you.
Java as a platform for internal DSLs
Dynamically constructing SQL is a great example where building a"DSL"appropriate to the domain of SQL is a compelling advantage.
Traditional Java code that uses SQL would look something like the following:
String sql = "select id, name " +
"from customers c, order o " +
"where " +
"c.since >= sysdate - 30 and " +
"sum(o.total) > " + significantTotal + " and " +
"c.id = o.customer_id and " +
"nvl(c.status, ‘DROPPED‘) != ‘DROPPED‘";
An alternative representation taken from a recent system worked on by the authors:
Table c = CUSTOMER.alias();
Table o = ORDER.alias();
Clause recent = c.SINCE.laterThan(daysEarlier(30));
Clause hasSignificantOrders = o.TOTAT.sum().isAbove(significantTotal);
Clause ordersMatch = c.ID.matches(o.CUSTOMER_ID);
Clause activeCustomer = c.STATUS.isNotNullOr("DROPPED");
String sql = CUSTOMERS.where(recent.and(hasSignificantOrders)
.and(ordersMatch)
.and(activeCustomer)
.select(c.ID, c.NAME)
.sql();
The DSL version has several advantages. The latter version was able to accommodate a switch to using PreparedStatements transparently - the Stringversion requires extensive modification to switch to using bindvariables. The latter will not compile if the quoting is incorrect oran integer parameter is passed to a date column for comparison. Thephrase "nvl(foo, ‘X‘) != ‘X‘" is a specific form found inOracle SQL. It is virtually unreadable to a non-Oracle SQL programmeror anyone unfamiliar with SQL. That idiom in SQL Server, for example,would be "(foo is null or foo != ‘X‘)." By replacing this phrase with the more easily understandable and language-like "isNotNullOr(rejectedValue),"readability has been enhanced, and the system is protected from a laterneed to change the implementation to take advantage of facilitiesoffered by another database vendor.
Creating internal DSLs in Java
The best way to create a DSL is by first prototyping the desiredAPI and then work on implementing it given the constraints of the baselanguage. Implementation of a DSL will involve testing continuously toensure that we are advancing in the right direction. This "prototypeand test" approach is what Test-Driven Development (TDD) advocates.
When using Java to create a DSL, we might want to create the DSLthrough a fluent interface. A fluent interface provides a compact andyet easy-to-read representation of the domain problem we want to model.Fluent interfaces are implemented using method chaining. It isimportant to note that method chaining by itself is not enough tocreate a DSL. A good example is Java‘s StringBuilder which method "append" always return an instance of the same StringBuilder. Here is an example:
StringBuilder b = new StringBuilder();
b.append("Hello. My name is ")
.append(name)
.append(" and my age is ")
.append(age);
This example does not solve any domain-specific domain.
In addition to method chaining, static factory methods and importsare a great aid in creating a compact, yet readable DSL. We will coverthese techniques in more detail in the following sections.
1. Method Chaining
There are two approaches to create a DSL using method chaining, andboth are related to the return value of the methods in the chain. Ouroptions are to return this or to return an intermediate object, depending on what we are trying to do.
1.1 Returning this
We usually return this when calls to the methods in the chain can be:
optional
called in any order
called any number of times
We have found two use cases for this approach:
chaining of related object behavior
simple construction/configuration of an object
1.1.1 Chaining related object behavior
Many times, we only want to chain methods of an object to reduceunnecessary text in our code, by simulating dispatch of "multiplemessages" (or multiple method calls) to the same object. The followingcode listing shows an API used to test Swing GUIs. The test verifiesthat an error message is displayed if a user tries to log into a systemwithout entering her password.
DialogFixture dialog = new DialogFixture(new LoginDialog());
dialog.show();
dialog.maximize();
TextComponentFixture usernameTextBox = dialog.textBox("username");
usernameTextBox.clear();
usernameTextBox.enter("leia.organa");
dialog.comboBox("role").select("REBEL");
OptionPaneFixture errorDialog = dialog.optionPane();
errorDialog.requireError();
errorDialog.requireMessage("Enter your password");
Although the code is easy to read, it is verbose and requires too much typing.
The following are two methods from TextComponentFixture that were used in our example:
public void clear() {
target.setText("");
}
public void enterText(String text) {
robot.enterText(target, text);
}
We can simplify our testing API by simply returning this, and therefore enable method chaining:
public TextComponentFixture clear() {
target.setText("");
return this;
}
public TextComponentFixture enterText(String text) {
robot.enterText(target, text);
return this;
}
After enabling method chaining in all the test fixtures, our testing code is now reduced to:
DialogFixture dialog = new DialogFixture(new LoginDialog());
dialog.show().maximize();
dialog.textBox("username").clear().enter("leia.organa");
dialog.comboBox("role").select("REBEL");
dialog.optionPane().requireError().requireMessage("Enter your password");
The result is more compact and readable code. As previouslymentioned, method chaining by itself does not imply having a DSL. Weneed to chain methods that correspond to related behavior of an objectthat together solve a domain-specific problem. In our example, thedomain-specific problem was Swing GUI testing.
1.1.2 Simple construction/configuration of an object
This case is similar to the previous one, with the difference thatinstead of just chaining related methods of an object, we create a"builder" to create and/or configure objects using a fluent interface.
The following example illustrates a "dream car" created using setters:
DreamCar car = new DreamCar();
car.setColor(RED);
car.setFuelEfficient(true);
car.setBrand("Tesla");
The code for the DreamCar class is pretty simple:
// package declaration and imports
public class DreamCar {
private Color color;
private String brand;
private boolean leatherSeats;
private boolean fuelEfficient;
private int passengerCount = 2;
// getters and setters for each field
}
Although creating a DreamCar is easy and the code is quite readable, we can create more compact code using a car builder:
// package declaration and imports
public class DreamCarBuilder {
public static DreamCarBuilder car() {
return new DreamCarBuilder();
}
private final DreamCar car;
private DreamCarBuilder() {
car = new DreamCar();
}
public DreamCar build() { return car; }
public DreamCarBuilder brand(String brand) {
car.setBrand(brand);
return this;
}
public DreamCarBuilder fuelEfficient() {
car.setFuelEfficient(true);
return this;
}
// similar methods to set field values
}
Using the builder we can rewrite the DreamCar creation as follows:
DreamCar car = car().brand("Tesla")
.color(RED)
.fuelEfficient()
.build();
Using a fluent interface, once again, reduced noise in code, whichresulted in more readable code. It is imperative to note that, whenreturning this, any method in the chain can be called at any time and any number of times. In our example, we can call the method coloras many times as we wish, and each call will override the value set bythe previous call, which in the context of the application, may bevalid.
Another important observation is that there is no compiler checkingto enforce required field values. A possible solution would be to throwexceptions at run-time if any object creation and/or configuration ruleis violated (e.g. a required field missing). It is possible to achieverule validation by returning intermediate objects from the methods inthe chain.
1.2 Returning an intermediate object
Returning an intermediate object from methods in a fluent interface has some advantages over returning this:
we can use the compiler to enforce business rules (e.g. required fields)
we can guide our users of the fluent interface through a specific path by limiting the available options for the next element in the chain
gives API creators greater control of which methods a user can (or must) call, as well as the order and how many times a user of the API can call a method
The following example illustrates a vacation created using constructor arguments:
Vacation vacation = new Vacation("10/09/2007", "10/17/2007",
"Paris", "Hilton",
"United", "UA-6886");
The benefit of this approach is that it forces our users to specifyall required parameters. Unfortunately, there are too many parametersand they do not communicate their purpose. Do "Paris" and "Hilton"refer to the city and hotel of destination? Or do they refer to thename of our companion? :)
A second approach is to use setters as a way to document each parameter:
Vacation vacation = new Vacation();
vacation.setStart("10/09/2007");
vacation.setEnd("10/17/2007");
vacation.setCity("Paris");
vacation.setHotel("Hilton");
vacation.setAirline("United");
vacation.setFlight("UA-6886");
Our code is more readable now, but it is also verbose. A thirdapproach could be to create a fluent interface to build a vacation,like in the example in the previous section:
Vacation vacation = vacation().starting("10/09/2007")
.ending("10/17/2007")
.city("Paris")
.hotel("Hilton")
.airline("United")
.flight("UA-6886");
This version is more compact and readable, but we have lost thecompiler checks for missing fields that we had in the first version(the one using a constructor.) In another words, we are not exploitingthe compiler to check for possible mistakes. At this point, the best wecan do with this approach is to throw exceptions at run-time if any ofthe required fields was not set.
The following is a fourth, more sophisticated version of the fluentinterface. This time, methods return intermediate objects instead of this:
Period vacation = from("10/09/2007").to("10/17/2007");
Booking booking = vacation.book(city("Paris").hotel("Hilton"));
booking.add(airline("united").flight("UA-6886");
Here we have introduced the concept of Period, a Booking, as well as a Location and BookableItem (Hotel and Flight), and an Airline. The airline, in this context, is acting as a factory for Flight objects; the Location is acting as a factory for Hotelitems, etc. Each of these objects is implied by the booking syntax wedesired, but will almost certainly grow to have many other importantbehaviors in the system as well. The use of intermediate objects allowsus to introduce compiler-checked constraints of what the user can andcannot do. For example, if a user of the API tries to book a vacationwith a starting date and without an ending date, the code will simplynot compile. As we mentioned previously, we can build a language thatuses syntax to enforce semantics.
We have also introduced the usage of static factory methods in theprevious example. Static factory methods, when used with staticimports, can help us create more compact fluent interfaces. Forexample, without static imports, the previous example will need to becoded like this:
Period vacation = Period.from("10/09/2007").to("10/17/2007");
Booking booking = vacation.book(Location.city("Paris").hotel("Hilton"));
booking.add(Flight.airline("united").flight("UA-6886");
The example above is not as readable as the one using staticimports. We will cover static factory methods and imports in moredetail in the following section.
Here is a second example of a DSL in Java. This time, we are simplifying usage of Java reflection:
Person person = constructor().withParameterTypes(String.class)
.in(Person.class)
.newInstance("Yoda");
method("setName").withParameterTypes(String.class)
.in(person)
.invoke("Luke");
field("name").ofType(String.class)
.in(person)
.set("Anakin");
We need to be cautious when using method chaining. It is quite easyto overuse, resulting in "train wrecks" of many calls chained togetherin a single line. This can lead to many problems, including asignificant reduction in readability and vagueness in a stack tracewhen exceptions arise.
2. Static Factory Methods and Imports
Static factory methods and imports can make an API more compact andeasier to read. We have found that static factory methods are aconvenient way to simulate named parameters in Java, a feature thatmany developers wish the language had. For example, consider this code,which purpose is to test a GUI by simulating a user selecting a row ina JTable:
dialog.table("results").selectCell(6, 8); // row 6, column 8
Without the comment "// row 6, column 8," it would beeasy to misunderstand (or not understand at all) what the purpose ofthis code is. We would need to spend some extra time checkingdocumentation or reading some more lines of code to understand what ‘6‘and ‘8‘ stand for. We could also declare the row and column indices asvariables or better yet, as constants:
int row = 6;
int column = 8;
dialog.table("results").selectCell(row, column);
We have improved readability of code, at the expense of adding morecode to maintain. To keep code as compact as possible, the idealsolution would to write something like this:
dialog.table("results").selectCell(row: 6, column: 8);
Unfortunately, we cannot do that because Java does not supportnamed parameters. On the bright side, we can simulate them by using astatic factory method and static imports, to get something like:
dialog.table("results").selectCell(row(6).column(8));
We can start by changing the signature of the method, by replacingall the parameters with one object that will contain them. In ourexample, we can change the signature of selectCell(int, int) to:
selectCell(TableCell);
TableCell will contain the values for the row and column indices:
public final class TableCell {
public final int row;
public final int column;
public TableCell(int row, int column) {
this.row = row;
this.column = column;
}
}
At this point, we just have moved the problem around: TableCell‘s constructor is still taking two int values. The next step is to introduce a factory of TableCells, which will have one method per parameter in the original version of selectCell. In addition, to force users to use the factory, we need to change TableCell‘s constructor to private:
public final class TableCell {
public static class TableCellBuilder {
private final int row;
public TableCellBuilder(int row) {
this.row = row;
}
public TableCell column(int column) {
return new TableCell(row, column);
}
}
public final int row;
public final int column;
private TableCell(int row, int column) {
this.row = row;
this.column = column;
}
}
By having the factory TableCellBuilder we can create a TableCell having one parameter per method call. Each method in the factory communicates the purpose of its parameter:
selectCell(new TableCellBuilder(6).column(8));
The last step is to introduce a static factory method to replace usage of TableCellBuilder constructor, which is not communicating what 6 stands for. As we did previously, we need to make the constructor private to force our users to use the factory method:
public final class TableCell {
public static class TableCellBuilder {
public static TableCellBuilder row(int row) {
return new TableCellBuilder(row);
}
private final int row;
private TableCellBuilder(int row) {
this.row = row;
}
private TableCell column(int column) {
return new TableCell(row, column);
}
}
public final int row;
public final int column;
private TableCell(int row, int column) {
this.row = row;
this.column = column;
}
}
Now we only need to add to our code calling selectCell is include an static import for the method row in TableCellBuilder. To refresh our memories, this is how calls to selectCell look like:
dialog.table("results").selectCell(row(6).column(8));
Our example shows that with some little extra work we can overcomesome of the limitations of our host language. As we mentioned before,this is only one of the multiple ways we can improve code readabilityusing static factory methods and imports. The following code listingshows an alternative way to solve the same problem of table indices,using a static factory methods and imports in a different way:
/**
* @author Mark Alexandre
*/
public final class TableCellIndex {
public static final class RowIndex {
final int row;
RowIndex(int row) {
this.row = row;
}
}
public static final class ColumnIndex {
final int column;
ColumnIndex(int column) {
this.column = column;
}
}
public final int row;
public final int column;
private TableCellIndex(RowIndex rowIndex, ColumnIndex columnIndex) {
this.row = rowIndex.row;
this.column = columnIndex.column;
}
public static TableCellIndex cellAt(RowIndex row, ColumnIndex column) {
return new TableCellIndex(row, column);
}
public static TableCellIndex cellAt(ColumnIndex column, RowIndex row) {
return new TableCellIndex(row, column);
}
public static RowIndex row(int index) {
return new RowIndex(index);
}
public static ColumnIndex column(int index) {
return new ColumnIndex(index);
}
}
The second version of the solution is more flexible than the firstone, because allows us to specify the row and column indices in twoways:
dialog.table("results").select(cellAt(row(6), column(8));
dialog.table("results").select(cellAt(column(3), row(5)); Organizing Code
It is a lot easier to organize code of a fluent interface which methods return this,than the one which methods return intermediate objects. In the case ofthe former, we end up with fewer classes that encapsulate the logic ofthe fluent interface, allowing us to use the same rules or conventionswe use when organizing non-DSL code.
Organizing code of a fluent interface using intermediate objects asreturn type is trickier because we have the logic of the fluentinterface scattered across several small classes. Since these classes,together, as a whole, form our fluent interface, it makes sense to keepthem together and we might not want them to mix them with classesoutside of the DSL. We have found two options:
Create intermediate objects as inner classes
Have intermediate objects in their own top-level classes, all in the same package
The decision of the approach to use to decompose our system candepend on several factors the syntax we want to achieve, the purpose ofthe DSL, the number and size (in lines of code) of intermediate objects(if any,) and how the DSL can fit with the rest of the code base, aswell as any other DSLs.
Documenting Code
As in organizing code, documenting a fluent interface which methods return this is a lot easier than documenting a fluent interface returning intermediate objects, especially if documenting using Javadoc.
Javadoc displays documentation of one class at a time, which maynot be the best in a DSL using intermediate objects: the DSL iscomposed of a group of classes, not individual ones. Since we cannotchange how Javadoc displays the documentation of our APIs, we havefound that having an example usage of the fluent interface (includingall the participating classes) with links to each of the methods in thechain, in the package.html file, can minimize the limitations ofJavadoc.
We should be careful and not duplicate documentation, because itwill increase maintenance costs for the API creators. The best approachis to rely on tests as executable documentation as much as possible.
In Conclusion
Java can be suited to create internal domain-specific languagesthat developers can find very intuitive to read and write, and still bequite readable by business users. DSLs created in Java may be moreverbose than the ones created with dynamic languages. On the brightside, by using Java we can exploit the compiler to enforce semantics ofa DSL. In addition we can count on mature and powerful Java IDEs thatcan make creation, usage and maintenance of DSLs a lot easier.
Creating DSLs in Java also requires more work from API designers.There is more code and more documentation to create and maintain. Theresults can be rewarding though. Users of our APIs will seeimprovements in their code bases. Their code will be more compact andeasier to maintain, which can simplify their lives.
There are many different ways to create DSLs in Java, depending onwhat we are trying to accomplish. Although there is no "one size fitsall" approach, we have found that combining method chaining and staticfactory methods and imports can lead to a clean, compact API that isboth easy to write and read.
In summary, there are advantages and disadvantages when using Javafor creation of DSLs. It is up to us, the developers, to decide whetheris the right choice based on the needs of our projects.
As a side note,Java 7may include new language features (such as closures) that may help uscreate less verbose DSLs. For a comprehensive list of the proposedfeatures, please visitAlex Miller‘s blog.
About the Authors
Alex Ruiz is a Software Engineer in the development toolsorganization at Oracle. Alex enjoys reading anything related to Java,testing, OOP, and AOP and has programming as his first love. Beforejoining Oracle, Alex was a consultant for ThoughtWorks. Alex maintainsa blog athttp://www.jroller.com/page/alexRuiz.
Jeff Bay is a Senior Software Engineer at a hedge fund in New York.He has repeatedly built high quality, high velocity XP teams working ondiverse systems such as program enrollment for Onstar, leasingsoftware, web servers, construction project management, and others. Heapproaches software with a passion for removing duplication andpreventing bugs in order to improve developer efficiency and time ontask.
Resources
Domain Specific Language by Martin Fowler
DSL Boundary by Martin Fowler
Language Workbenches: The Killer-App for Domain Specific Languages? by Martin Fowler
Fluent Interface by Martin Fowler
Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides
Effective Java by Joshua Bloch
Static Import atJava.sun.com
Generics by Gilad Bracha
Proposed features in Java 7 by Alex Miller
Test-Driven Development definition from Wikipedia
Simulating Named Parameters in Java by Alex Ruiz
TableCellIndex by Mark Alexandre (first comment atSimulating Named Parameters in Java)
Swing GUI test examples taken fromSwing module ofFEST (Fixtures for Easy Software Testing)
Reflection examples also taken fromReflection module ofFEST (included with Swing module)
SQL example coming up in a open source project near you :)
Bookmark
digg+,
reddit+,
del.icio.us+,
dzone+
Tags
Patterns,
Language Features,
Languages
14 comments
Reply
byHenri FrilundPostedFeb 19, 2008 12:39 PM
byErkki LindperePostedFeb 19, 2008 1:09 PM
byMichael HungerPostedFeb 20, 2008 12:24 AM
byRichard L. Burton IIIPostedFeb 19, 2008 1:37 PM
byManuel PalacioPostedFeb 19, 2008 4:16 PM
byMichael HungerPostedFeb 19, 2008 5:10 PM
byMichael HungerPostedFeb 19, 2008 5:30 PM
byMichael HungerPostedFeb 19, 2008 5:47 PM
byJacob NortheyPostedFeb 19, 2008 9:13 PM
byMichael HungerPostedFeb 20, 2008 12:13 AM
byJacob NortheyPostedFeb 20, 2008 7:40 AM
byRon KersicPostedFeb 20, 2008 2:30 AM
byJohn DeHopePostedFeb 20, 2008 10:42 AM
byDavid GrecoPostedFeb 26, 2008 7:07 AM
Sort by date descending
Back to top
Brilliant article
Feb 19, 2008 12:39 PM by Henri Frilund
Very interesting article. I‘m right now working on a personal project trying to create a tool for creating web applications with an internal Java-hosted DSL, backed by a component model (Wheel). Designing the API is indeed quite difficult especially if the DSL is rather large. Worst problems for me so far have been limitations posed by the static type system (which on the other hand helps a lot when using the DSL with a modern IDE), method grouping and not having named attributes. Thanks to some of the ideas in your article, I think I can get around the missing named attributes. The grouping-issue...by which I mean the situation where you start to have dozens of methods (DSL expressions) in a class and you need some way to group them functionally, but can‘t break the class-hierarchy either (in my case the DSL is used by extending a a class containing the DSL so I can‘t split the class itself). I ended up creating sort of "side-show" classes that hold the methods, but share state with the "main" class. Example: A Configuration-class that holds method for manipulating the "main" classes configuration properties. The "main" class then as a method that returns the Configuration-object. In code it looks like this: config(). initialFieldValue("guess", "give a number"). exposeField("guess"); What the config() does is that is returns the Configuration-object with methods that will return this. It‘s not very OO, but it works and the result looks pleasing. Not sure if the grouping-issue is common, but thought I‘d mention it. -Henri
Reply
Back to top
A Pointless Exercise
Feb 19, 2008 1:09 PM by Erkki Lindpere
Creating internal DSL-s in Java is a pointless exercise. Scala, Groovy, JRuby or another DSL-friendly language should be used instead. My personal preference is Scala. It has limitations on how nice the syntax can be, but it performs as well as Java, unlike the dynamically typed languages.
Reply
Back to top
Very good read.
Feb 19, 2008 1:37 PM by Richard L. Burton III
I know a lot of developers are often reluctant to follow a DSL naming convention like expressed in the examples contained within this article. I‘ve recently become a firm believer in method chaining and following what I like to call "DSL Naming Convention". Good job guys. Best Regards, Richard L. Burton III
Reply
Back to top
Good stuff
Feb 19, 2008 4:16 PM by Manuel Palacio
Very good article. Looking forward to seeing more of the SQL example.
Reply
Back to top
Re: SQL-Example
Feb 19, 2008 5:10 PM by Michael Hunger
Perhaps you‘d like to have a look at the embedded Java-SQL DSL named JEQUEL. Seehttp://www.jequel.de.If someone is interested to join we also have a codehaus project athttp://jequel.codehaus.org. Small example: public void testSimpleSql() { final SqlString sql = select(ARTICLE.OID) .from(ARTICLE, ARTICLE_COLOR) .where(ARTICLE.OID.eq(ARTICLE_COLOR.ARTICLE_OID) .and(ARTICLE.ARTICLE_NO.is_not(NULL))); assertEquals("select ARTICLE.OID" + " from ARTICLE, ARTICLE_COLOR" + " where ARTICLE.OID = ARTICLE_COLOR.ARTICLE_OID" + " and ARTICLE.ARTICLE_NO is not NULL", sql.toString()); } For some more material on DSLs have a look at the DSL-Book (Work in Progress) and the thoughtworks podcast by Martin Fowler. Have fun Michael
Reply
Back to top
Additional resources
Feb 19, 2008 5:30 PM by Michael Hunger
Nat Pryce and Steve Freeman did a lot of this java dsl stuff during the jMock and Hamcrest development. Martin Fowler links to the paper regarding jmock at the end of theexpression builder bliki entry. The blog ofNat Pryce is an invaluable source regarding java DSLs in the domain of testing (i.e. jmock, test data builders, etc.) There is an ongoing effort of theQuaere Team led by Anders Norås to build a LINQ like DSL for java. Michael
Reply
Back to top
Re: Additional resources
Feb 19, 2008 5:47 PM by Michael Hunger
Sorry, I forgot: Guice binding setup is also an internal DSL in java. The DSL booking example is totally confusing. You have inconsistent syntax mixed with imperative methods (add) and I doubt that a user of such an DSL would know which methods to call on which objects and which Parameter objects to fill in created by the (he has to know this) statically imported methods. There is also a missing paren at the last line (in both boxes). Something more interesting would be: Trip trip = on("10/09/2007").flyWith(airline("united").flight("UA-6886")) .to(city("Paris").hotel("Hilton")) .andReturnWith(airline("united").flight("UA-6887")).on("10/17/2007") What I also miss is the discussion of the concrete usage of interfaces as intermediary builder objects. This is much more important as it is java‘s only way to support multiple inheritance (and you need this when composing grammars from reusable expressions). See Martins Book for details. Michael
Reply
Back to top
Very Beneficial
Feb 19, 2008 9:13 PM by Jacob Northey
Great article, I‘m sure we will see some if not all of this content in an upcoming DSL book. Although I would never use an internal DSL in Java as a customer facing DSL, using the builder pattern and method chaining is extremely beneficial for generating test objects in unit tests. It is much more communicative than a simple ObjectMother which hides a lot of the test values inside static factory methods. Somebody should create a code generation library for generating builders from POJOs. That would reduce a lot of time spent keeping builder code current with the POJOs.
Reply
Back to top
Re: Very Beneficial
Feb 20, 2008 12:13 AM by Michael Hunger
The book by Martin Fowler is as cited already in progress. Nat Pryce did quite a lot with the TestDataBuilders you mention. I agree that an internal DSL in a language like Java is nothing for customers (neither reading nor writing) although reading may be improved by changing the color scheme of the IDE to fade parentheses, semicolons, brackets, language identifiers as suggested by Nat Pryce, Anders Noras and others. (see http://nat.truemesh.com/archives/000188.html) The generic pojo builder generation thing would only work if you could specify the relevant attributes that shall be a part of the language and also define the associations between your POJOs. Choosing the right names for the fluent interfaces (which often don‘t correspond to the names of the attributes set but are more natural language constructs) is another problem with a generic approach. Michael
Reply
Back to top
Re: A Pointless Exercise
Feb 20, 2008 12:24 AM by Michael Hunger
I don‘t agree with you. Please don‘t forget that there is not only the top notch of the developer crowd you see here at infoq, the other online resources and the conferences. If I look around my workplace there is a lot of people who just do java and have neither heard nor used Scala, Ruby, Groovy and the like. So a DSL in Java is a first step to take in this direction when introducing DSLs and fluent interfaces in an development team/organisation. The static typing helps a lot when someone learns a new DSL (if you are fluent in your DSL a dynamic language should pose no problem but I don‘t want to imagine having a customer staring at the error messages of the ruby or groovy runtime due to misspelled syntax). So editor support is quite important here. You could argue that a tool like the xText facilities of openArchitectureWare (http://openarchitectureware.org/) would be a better choice their with their full fledged text editor generation support. But they don‘t generate DSLs for executable languages per se but parsers for creating/reading models. Michael
Reply
Back to top
Very nice (but is it a DSL?)
Feb 20, 2008 2:30 AM by Ron Kersic
Nice article! But I’d argue that it is more on fluent interfaces then on (internal) DSL’s. I know the issue of is-it-a-DSL-or-not is a bit of a grey area but my personal litmus test is on the errors and warnings thrown at me when creating a program with the DSL. I expect a DSL to be supported by errors and warnings at the level of the DSL and not at the level of the language implementing the DSL. In case of the of the booking example, I’d guess that if I type booking.add(airline(“united”)) the error would be that the booking.add method expects a BookableItem (instead of an Airline). But for a ‘real’ DSL I want the error message to state that I need to specify a flight to go with that airline. Likewise, if I’d type booking.add(flight(“UA-6868”)) I want a warning message stating that this flight is interpreted to be by United Airlines and I want to be offered a quick fix to add the airline(“united”) bit. I guess it comes down to the fact that you need to have some processing on top of what the IDE does on processing Java in order to get at a internal DSL in Java. The Spoon framework (http://spoon.gforge.inria.fr/) is a relatively easy way of getting such processing in place. I’m building an internal DSL with Spoon (http://funkydsl.wiki.sourceforge.net/, no code yet alas) and from what I know, the errors and warnings for the booking example above would be very easy to implement with Spoon. Most certainly worth checking out. \Ron
Reply
Back to top
Re: Very Beneficial
Feb 20, 2008 7:40 AM by Jacob Northey
Thanks for pointing out Nat‘s blog. The entry http://nat.truemesh.com/archives/000728.html is exactly what I was looking for in terms of using TestDataBuilders instead of an ObjectMother. As for the generic pojo builder generation, I would be willing to give up a little readability for a lot less effort. This is especially the case for large generated domain models on small projects where the effort required to maintain TestDataBuilders is not warranted. The main goal I‘m looking for in TestDataBuilders is conciseness. public class Car { private Color color; private Model model; private Manufacturer manufacturer; // Getters and Setters ... } Car car = car().color(RED).manufacturer(manufacturer("Ford")).model(model("Fusion")).build(); The above code, similar to the article‘s dreamCar code, can be generated straight from POJOs where the static builderfactory methods manufacturer and model reflect required constructor parameters. Another approach I like is using variable length argument lists with named parameters instead of chaining with() calls: Car redFusion = car(color("red"), manufacturer("Ford"), model("Fusion")); Car blueIon = car(year(2005), color("blue"), manufacturer("Saturn"), model("Ion"));
Reply
Back to top
A Great Article
Feb 20, 2008 10:42 AM by John DeHope
A fantastic article. I really enjoyed the discussion about how to actually build a fluent API. You see a lot of blog articles about what they are, and what they look like. This was the first article I read that explained some of the thought process that goes into actually building them up from scratch. Nice!
Reply
Back to top
Another nice example of an internal DSL for integration
Feb 26, 2008 7:07 AM by David Greco
Camel defines an internal DSL based on the enterprise integration patterns. Using this nice DSL it‘s possible to easily define messaging routes among different endpoints. http://activemq.apache.org/camel/
InfoQ: An Approach to Internal Domain-Specific Languages in Java Language Workbenches: The Killer-App for Domain Specific Languages?(转 Martin Fowler) DSL(Domain Specific language): How to get it Creating a Domain-Specific Modeling Language for an Existing Framework (转www.metacase.com) Google官方声明:A new approach to China: an update... An Approach to Classroom Research: Designing, Implementing, and Writing Up an Action Research Study Domain-Specific Modeling (转与 ITarchitect) How to Install an Innovation Process in Your Company Does python have an equivalent to Java Class.... InfoQ: Eric Evans on Domain Driven Design DSM:Domain-Specific Modeling (转与 板桥里人) Lightweight Domain Specific Modeling (转与 theserverside.com) DSL development: 7 recommendations for Domain Specific Language design based on Domain-Driven Design Speaking in 65 different languages胶南 Innovate: Moving from Theory to Real-World Experiences in an e-Learning Community To have an image of bamboo in one's mind 入木三分... An Introduction to Virtualization Ajax: A New Approach to Web Applications ajax: a new approach to web applications Ajax: A New Approach to Web Applications Java自由人 (当你不能再拥有时,你唯一能做的,就是让自己不要忘记)| Ajax: A New Approach to Web Applications(中英对照) Domain-Specific Modeling for Full Code Generation(转 www.metacase.com) Tech Per: 10 Tips on JPA Domain Modelling [An... Static Detection of Security Vulnerabilities in Scripting Languages