17Aug
2008

Tags:

Although it may seem like an advanced topic, writing a simple Domain Specific Language (DSL) in Groovy is actually pretty easy. Groovy's dynamic nature and metaprogramming capabilities give developers all the tools they need to quickly and easily write their own DSL.

Domain Specific Languages are typically small, simple languages with a highly expressive syntax and grammar. In computing, DSLs are used to provide domain experts (who may or may not have prior programming experience) with programming capabilities limited to their specific area of expertise.

There are two types of DSLs: external and internal. External DSLs allow you to define a new language using any grammar or syntax you wish. External DSLs must then be parsed and executed by the programming language of your choice. Internal DSLs also define a new language, but are limited to the constructs made available by the implementing language. Because of their dynamic features, languages like Groovy and Ruby make writing internal DSLs relatively easy.

A Stock Trading DSL

A few weeks ago I wrote about trading stocks using Google's Finance API. Sticking with that theme, I thought it would be cool to write a Domain Specific Language that could also be used to mimic stock trading activities.

I'm guessing that most Wall Street traders don't know much about programming, but I'm sure most traders could decipher the actions that would take place after executing the following code:

buy 500, "AAPL", 179.30
buy(250, "AAPL", 179.30) //parenthesis are optional
buy 500, "SUNW", 10.14
sell 350, "AAPL", 179.30

show_transactions "AAPL"

print_portfolio_value

The code above is actual Groovy code. It tells the application to buy 750 shares of Apple stock at $179.30 in two separate transactions, buy 500 shares of Sun Microsystems stock at $10.14, and then sell 350 shares of Apple stock at $179.30. After all the transactions take place the code shows the transaction history for the Apple trades and then prints the total value of the portfolio.

The Groovy code to drive the functionality behind this simple DSL looks like this:

portfolio = [:] //Holds all of the stock transactions

/**
 *A simple object to hold the data for each individual transaction
 */
class StockTransaction {
    def tickerSymbol
    def numberOfShares
    def sharePrice
    def type
    def transactionDate
}

def buy(numberOfShares, symbol, sharePrice){
    transaction('Buy', numberOfShares, symbol, sharePrice)
}

def sell(numberOfShares, symbol, sharePrice){
    transaction('Sell', numberOfShares, symbol, sharePrice)
}

def transaction(transactionType, numberOfShares, symbol, sharePrice){
    def transaction = new StockTransaction(tickerSymbol:symbol,
        numberOfShares:numberOfShares, sharePrice:sharePrice,
        type:transactionType, transactionDate:new Date())

    println "${transaction.type}ing ${transaction.numberOfShares} shares" +
        " of ${transaction.tickerSymbol} at ${transaction.sharePrice}"

    //If no transactions exist for a particular stock, check to see that the
    //transaction type is a 'Buy' and if so create a new list to hold
    //transactions for a particular stock.
    if(portfolio[transaction.tickerSymbol] == null){
        if(transactionType == 'Buy')
            portfolio[transaction.tickerSymbol] = [transaction]
        else
            println "You can't sell a stock you don't own."
    } else{
        portfolio[transaction.tickerSymbol] << transaction
    }
}

def show_transactions(tickerSymbol){
    portfolio[tickerSymbol].each { transaction ->
        println "${transaction.transactionDate}: ${transaction.type} " +
          "${transaction.numberOfShares} shares of ${tickerSymbol} at " +
          "${transaction.sharePrice}"
    }
}

def getPrint_portfolio_value(){
    def totalValue = 0

    //Add all of the 'Buy' transactions and subtract all of the 'Sell'
    //transactions from the total portfolio value.
    portfolio.each { tickerSymbol, transactionList ->
        transactionList.each { transaction ->
            if(transaction.type == 'Buy')
                totalValue +=
                    (transaction.numberOfShares * transaction.sharePrice)
            else
                totalValue -=
                    (transaction.numberOfShares * transaction.sharePrice)
        }
    }

    println "Your Total Portfolio Value is: ${totalValue}"
}

The only thing the code really does is define methods for the different types of stock trading activities. I use a HashMap called portfolio to hold each of the individual stock transaction's attributes stored in a simple StockTransaction object. Each time a buy or sell action takes place the portfolio HashMap is updated with the corresponding StockTransaction object.

The readability of the DSL code is greatly improved because Groovy allows for optional parenthesis on methods that take one or more parameters. Notice that the second call to purchase Apple stock uses the parenthesis to illustrate this point. It is important to keep things as simple as possible when creating a DSL. Programmers should try to avoid bogging the domain expert down by forcing him or her to use unnecessary constructs of a language such as parenthesis.

If you look closely at the code that defines the DSL you will notice that in order to make the parenthesis optional for calls to the print_portfolio_value method I had to get a little hacky and define the method as: getPrint_portfolio_value(). This is because Groovy thinks that print_portfolio_value is a call to a property. By adding the "get" in front of the method call, we can "trick" Groovy into calling the method as desired.

After executing the DSL code the output is as follows:

Buying 500 shares of AAPL at 179.30
Buying 250 shares of AAPL at 179.30
Buying 500 shares of SUNW at 10.14
Selling 350 shares of AAPL at 179.30
Sun Aug 17 19:15:05 EDT 2008: Buy 500 shares of AAPL at 179.30
Sun Aug 17 19:15:05 EDT 2008: Buy 250 shares of AAPL at 179.30
Sun Aug 17 19:15:05 EDT 2008: Sell 350 shares of AAPL at 179.30
Your Total Portfolio Value is: 76790.00

Fluency

In the context of trading stocks, the DSL we created above is pretty straightforward, but it does suffer a bit from a problem with fluency. It might be nice if we could make the DSL a bit more readable. For example, what if the DSL was changed to read like this:

buy 500.shares.of("AAPL").at(179.30)
buy 250.shares.of("AAPL").at(179.30)
buy 500.shares.of("SUNW").at(10.14)
sell 350.shares.of("AAPL").at(179.30)

show_transactions "AAPL"

print_portfolio_value

Using categories it is possible to allow for a fluent DSL like the one above. Take a look at the code behind this DSL:

/**
 * The class used for the category
 */
class StockHelper {
    static portfolio = [:]

    static buy(self, stockTransaction) {
        stockTransaction.type = 'Buy'
        transaction(stockTransaction)
    }

    static sell(self, stockTransaction) {
        stockTransaction.type = 'Sell'
        transaction(stockTransaction)
    }

    static transaction(stockTransaction){
        println "${stockTransaction.type}ing " +
        "${stockTransaction.numberOfShares}" +
        " shares of ${stockTransaction.tickerSymbol} at " +
        "${stockTransaction.sharePrice}"

        if(portfolio[stockTransaction.tickerSymbol] == null){
            if(stockTransaction.type == 'Buy')
                portfolio[stockTransaction.tickerSymbol] = [stockTransaction]
            else
                println "You can't sell a stock you don't own."
        } else{
            portfolio[stockTransaction.tickerSymbol] << stockTransaction
        }
    }

    static getShares(self) {
        def startTransaction = new StockTransaction(numberOfShares:self,
            transactionDate:new Date())
        return startTransaction
    }

    static of(self, tickerSymbol) {
        self.tickerSymbol = tickerSymbol
        return self
    }
    static at(self, sharePrice) {
        self.sharePrice = sharePrice
        return self
    }

    static show_transactions(self, tickerSymbol){
        portfolio[tickerSymbol].each { transaction ->
            println "${transaction.transactionDate}: ${transaction.type} " +
              "${transaction.numberOfShares} shares of ${tickerSymbol} at " +
              "${transaction.sharePrice}"
        }
    }

    static getPrint_portfolio_value(self){
        def totalValue = 0

        portfolio.each { tickerSymbol, transactionList ->

            transactionList.each { transaction ->
                if(transaction.type == 'Buy')
                    totalValue +=
                        (transaction.numberOfShares * transaction.sharePrice)
                else
                    totalValue -=
                        (transaction.numberOfShares * transaction.sharePrice)
            }
        }

        println "Your Total Portfolio Value is: ${totalValue}"
    }
}

By chaining methods calls together we can make our code very fluent. We build a StockTransaction object by calling 3 methods: getShares(), of(), and at(). Each of these calls is responsible for building a specific piece of the StockTransaction object and passing it along to the next call. The self keyword is used to reference the target instance (i.e. the StockTransaction object being created). After the StockTransaction is created, the buy or sell methods can be called to update the portfolio HashMap just as in the first example.

Even though parenthesis and the dot notation (for method access) must be used in order to execute this DSL code, I personally think this code is easier to read and understand as compared with the previous example.

Conclusion

The code in this blog entry is pretty brittle, but it illustrates how easily a DSL can be created using the Groovy programming language. In a real life scenario programmers must be diligent about the design and usage of the DSLs they create.

By creating an internal DSL domain experts with some programming knowledge will also be able to exploit other constructs found in the implementing language such as loops and conditional statements. For example, code could be written to do things such as:

if(currentStockPriceVariable < SOME_SHARE_PRICE_CONSTANT){
    buy 500.shares.of("AAPL").at(currentStockPriceVariable)
}

or perhaps something like this:

3.times {
    sell 500.shares.of("AAPL").at(179.30)
}

Over the past few years DSLs have gained popularity along with the rise of dynamic languages. For example, GANT is a popular DSL for the Groovy programming language that can be used to script Ant style builds. It will be interesting to see what other types of DSLs become available in the future. Be sure to look out for Martin Fowler's upcoming book on the subject.

Code

Click here to download the code examples from this post.

Disclaimer

I am in no way endorsing any of the companies mentioned in this blog entry and all company references are for example purposes only.

Share and Enjoy:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • DZone
  • FSDaily
  • Reddit
  • Slashdot
  • StumbleUpon
  • Technorati
  • Twitter
  1. 6 Responses to “Groovy Domain Specific Language Tutorial”

  2. Nice article. Altough I must say that I don’t like the discussions that came up lately about DSL. For me all those examples are just APIs. Maybe the access to them is easier because of the use of dynamic languages like Groovy or Ruby. I wrote such “DSL” years ago, for instance with Python.

    By christian on Aug 18, 2008

  3. I think the diff betwen an API and a DSL is that in DSL you can actually define the boundries of your own productivity space by using the DSL capabilities in easily creating, forming, and implementing new concepts using existing keywords of the pre-defined domain or adding new levels of abstraction by adding new keywords as well. [In a syntax friendly manner for the end-user/peer-developer!

    what do you think ?

    By Mohamed on Sep 1, 2008

  4. Mohamed,
    You make some good points. I agree with you that DSLs offer a lot of flexibility.

    Ideally a DSL would be written in a way to completely abstract the underlying programming language away from the end user/domain expert.

    By Justin Spradlin on Sep 1, 2008

  5. Thank you,
    very interesting article

    By ostrov on Dec 2, 2009

  6. Thank you for this useful blog.
    Could you please update the code download link ?

    By Mani on Feb 6, 2011

  7. Hi Mani,
    I’ve updated the link for the code. Thanks for letting me know it was down.

    By Justin Spradlin on Feb 6, 2011

Post a Comment