C# – DSL vs Method calls: pros and cons

cdslnet

I have a rather peculiar data source I have to work with (an interface to an accounting application actually). While it is pretty powerful, I have to jump through pretty many hoops to get the data I want out of it. For example, if I want to get the contents of a table and specify which columns it should return, I have to iterate through the .Columns collection and call .SetVisible() to the ones I want.

Currently we have a method that wraps around this and allows us to specify things in an more simple way, but the parameter list to that function is growing fast, and most of the time we only need to specify a few of them when we call it. In short – it's an inflexible solution.

The first solution that came to my mind was something like this:

DataTable result = DataSourceWrapper.StartQuery("TableName")
    .SetVisibleColumns("Col1", "Col2", "Col3")
    .SetCriteria("CriteriaName", "Param1Name", CriteriaParam1, "Param2Name", CriteriaParam2)
    .SetFilter("Col4 = ? AND Col5 = ?", FilterParam1, FilterParam2)
    .SetReportParams("Param1Name", ReportParam1, "Param2Name", ReportParam2)
    .Execute();

Criteria, Filters and ReportParams are some things specific to the application and I will not discuss them here. But the general idea is like this. It's practically analogous to calling a method, except you can choose which parameters to specify (by calling specific methods) and have a bit more IntelliSense assistance. And you can also play with the order of the method calls.

Note that SetFilter() has an expression to parse. This is another thing that the DataSource makes difficult – it can process expressions quite nice, but you have to pass them as a tree of special objects, which again is pretty lengthy to write. In a previous question I asked for help on parsing such expressions. The current wrapper-method has a homebrew expression parser, which can parse simple expressions, but I thought of making the support for them more complete.

In that question, the Irony project was suggested. After checking it out I decided that it was indeed suited for this need. But after a while it dawned on me, that it was even more powerful than that. Why not make my own query language suited for exactly this task? The above would then look like:

DataTable result = DataSourceWrapper.Query(@"
    SELECT Col1, Col2, Col3
    FROM TableName
    WITH CRITERIA CriteriaName(Param1Name={0}, Param2Name={1})
    WITH REPORTPARAMS (Param1Name={2}, Param2Name={3}
    WHERE Col4 = {4} AND Col5 = {5}",
    CriteriaParam1, CriteriaParam2,
    ReportParam1, ReportParam2,
    FilterParam1, FilterParam2
);

But… would this not be an overkill? What are the pros and cons of either approach? What I see is:

Pro DSL:

  • query more concise;

Pro Methods:

  • More IntelliSense support;
  • Method names/parameter names (and comments) make less need for documentation (DSL will have to be documented thoroughly);
  • Might be quicker to create? I've never created my own DSL, so I don't know how much work that is. Irony seems to take a lot of the burden from my shoulders, but how much is still left there?

Added: To clarify, both approaches will be used only by coders. External people and business analysts will not use it.

Best Answer

You have to be careful about what you call a DSL. See Martin Fowler's Bliki post on DSL. Your example of the method chaining is very close to an internal DSL. Modify it slightly and it is:

DataTable result = DataSourceWrapper.Query("TableName")
    .With(new Columns("Col1", "Col2", "Col3"))
    .Where(new AndCritera("CriteriaName",
        new Criterion("Param1Name", CriteriaParam1),
        new Criterion("Param2Name", CriteriaParam2))
    .Filter(
        new Filter("Col4").Equals(FilterParam1),
        new Filter("Col5").Equals(FilterParam2))
    .With(
        new ReportParam("Param1Name", ReportParam1),
        new ReportParam("Param2Name", ReportParam2));

That being said, this is still very much within the C# realm, and only programmers will be able to write these queries. If your requirements are pushing you toward making queries available to non-programmers, then you might consider undergoing the effort of creating an external DSL as you specified in the second example.