Separating Concerns: Business Logic Implementations round-up
One of the nice things of using a framework that separates concerns cleanly is that it allows developers to choose different ways of implementing the business logic without affecting other parts of the system, like the presentation logic. That means more freedom to choose.
On the other side, since the introduction of the Java Scripting API in Java 6, executing code in languages other than Java is not only possible but quite easily accomplished. Again, more freedom.
But, as always, with the freedom to choose comes the need to decide, if you know what I mean ;). So, how to decide between one language or another? Well, one of the best ways to do so is to test for yourself, and that's what I did. So, I'm here to share the different implementations I came up with in the various different languages. Note that I'm not an expert in all the languages, so if you find you know how to improve some code, you are more than welcome to send your suggestions.
The basic idea of the test is to write a function that returns a list of items from a database, reachable through a data source, table in the form of an XML document*. The document has to be like this static prototype**, but changing the value of the Impl attribute.
<ITEM_QUERY Type="All" Impl="Static">
<ITEM
Code="11111111111111111111111111111111"
Name="First item"
Description="Description of first item"/>
<ITEM
Code="44444444444444444444444444444444"
Name="Fourth item"/>
<ITEM
Code="22222222222222222222222222222222"
Name="Second item"
Description="Description of second item"/>
<ITEM
Code="33333333333333333333333333333333"
Name="Third item"
Description="Description of third item"/>
</ITEM_QUERY>
Why all of this? Well, I was tired of “hello world” or academic examples that show very little and I wanted to see those languages doing something that my applications do every day: Grab some data from a database and spit it out. So, what different solutions have already been implemented?
- Straight JDBC to have the most basic implementation.
- Ibatis, one step above the plain JDBC one but still with full control over the SQL code.
- Hibernate 3, the famous ORM engine.
- JPA (with Hibernate under the hoods), to check the standard API, now that we left EJB entity beans behind.
- Dynamic languages accessed through the Java Scripting API: Groovy, JRuby, Jython, PHP, Pnuts.
- Compiled Groovy, the same Groovy script but instead of accessed through the API, compiled into a class file and added to the classpath as a regular Java class.
- Scala, the functional language that some think it will become the Java successor.
I would divide the implementations in two different categories:
- The first one is “the Java way”, were you have different solutions that might be appropriate for different problems, but all within Java. This would include JDBC, Ibatis, Hibernate and JPA and if you want to have a look at the implementations, you should check the different classes at the org.leaf.test package and subpackages at the Fisheye repository.
The core is quite similar in all the versions and, not surprisingly, the main differences lay in how you create the query and how you retrieve the results.
Using plain JDBC: (see the source code of the main file here)
ResultSet theResultSet = itemsListStatement
.executeQuery("select * from TTST_ITEM order by ITE_NAME");
while (theResultSet.next())
{
… // results retrieved from ResultSet
Using Ibatis: (see the source code of the main file here)
// You need to define the corresponding SQL statement for the "selectAllItes" query
// and the mapping between the resultset and the TstItemBear class in an external
// XML file
List items = theSqlMapClient.queryForList("selectAllItes");
for (Iterator i = items.iterator(); i.hasNext();)
{
TtstItemBear theIte = (TtstItemBear) i.next();
Using Hibernate: (see the source code of the main file here)
// You need to define the mapping between the TstItemBear class and the corresponding
// table through an external XML file. The SQL sentenced is built for you from the HQL query.
List items = theSession.createQuery("from TtstItemBear b order by b.iteName").list();
for (Iterator i = items.iterator(); i.hasNext();)
{
TtstItemBear theIte = (TtstItemBear) i.next();
Using JPA: (see the source code of the main file here)
// You need to define the mapping between the TstItemBear class and the corresponding
// table through annotations. The SQL sentence is built for you from the JPQL query.
List items = theEntityManager.createQuery("select i from TtstItemBear i order by i.iteName").getResultList();
for (Iterator i = items.iterator(); i.hasNext();)
{
TtstItemBear theIte = (TtstItemBear) i.next();
- The second group would be the “In the JVM but not Java” solutions, and even though it is quite clear from the implementation languages that they are not Java, I mean it in the sense that each language has its own way of doing things, and they don't “feel” like Java. Note again that I don't claim to be an expert in all the languages, so there might be more idiomatic ways of accomplishing the same. If you find that's the case, please enlighten me as learning 9 different ways of doing the same thing took its toll ;). You can see all these implementations in the WEB-INF/scr folder at the Fisheye repository. If you have not played with dynamic languages before, you might find them really interesting.
For example, we can see how the different languages are able to grab the DataSource that is published through JNDI and get a connection. Some are similar to Java, some are different:
Jython (see the full source code here)
jndiName = "java:comp/env/jdbc/Test"
factory = System.getProperty("java.naming.factory.initial")
db = zxJDBC.lookup(jndiName, INITIAL_CONTEXT_FACTORY=factory)
Groovy (see the full source code here)
Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/Test");
def sql = new Sql(ds);
Pnuts (see the full source code here)
jdbc = openJDBC("java:comp/env/jdbc/Test")
PHP/Quercus (see the full source code here)
$pdo = new PDO("java:comp/env/jdbc/Test");
JavaScript (see the full source code here)
var ctx = new Packages.javax.naming.InitialContext();
var ds = ctx.lookup("java:comp/env/jdbc/Test");
var conn = ds.getConnection();
Scala (see the full source code here)
val namingContext = new InitialContext()
val theDataSource: DataSource =
namingContext.lookup("java:comp/env/jdbc/Test").asInstanceOf[DataSource]
…theDataSource.getConnection...
JRuby (see the full source code here)
ctx = JavaxNaming::InitialContext.new
ds = ctx.lookup("java:comp/env/jdbc/Test")
conn = ds.getConnection
You can also see very different approaches on how to loop over query results and how to build the XML. If there was no XML language support or I couldn't find an XML library that could be easily included, I reverted to string concatenation.
Jython (see the full source code here)
...
c = db.cursor(1)
c.execute("select ITE_CODE,ITE_NAME,ITE_DESCRIPTION from TTST_ITEM ORDER BY ITE_NAME")
xml = make_query(c.fetchall(), 'All',param['ite_code'],db)
c.close()
…
def make_query(rows, type, ite_code, db):
return '''<ITEM_QUERY Type="%s" Impl="Jython">
%s
</ITEM_QUERY>
''' % (type,'n'.join([make_item(row) for row in rows]))
...
Groovy (see the full source code here)
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
def groovy = xml.ITEM_QUERY(Type:'All', Impl:'Groovy'){
sql.eachRow("select * from TTST_ITEM ORDER BY ITE_NAME",
{
itemToXML(builder,it)
}
);
}
...
def itemToXML(builder, item)
{
builder.ITEM(Code:item.ite_code, Name:item.ite_name, Description:item.ite_description)
}
Pnuts (see the full source code here)
xml = "<ITEM_QUERY Type="All" Impl="Pnuts">"
for (item : jdbc.query("select * from TTST_ITEM ORDER BY ITE_NAME"))
{
xml += "<ITEM Code="" + item.ite_code + "" Name="" + item.ite_name + """
if(item.ite_description)
{
xml += " Description="" + item.ite_description + """
}
xml += " />"
}
xml += "</ITEM_QUERY>"
PHP/Quercus (see the full source code here)
…
$sql = "select * from TTST_ITEM ORDER BY ITE_NAME";
$xml = '<ITEM_QUERY Type="All" Impl="PHP">';
foreach ($pdo->query($sql) as $row)
{
$xml .= itemToXML($row);
}
$xml .= '</ITEM_QUERY>';
return (string)$xml;
...
function itemToXML($item)
{
$itemxml = '<ITEM ';
$itemxml .= 'Code="'.$item['ITE_CODE'].'" ';
$itemxml .= 'Name="'.$item['ITE_NAME'].'" ';
$itemxml .= 'Description="'.$item['ITE_DESCRIPTION'].'" ';
$itemxml .= '/>';
return $itemxml;
}
JavaScript (see the full source code here)
var allItems = '<ITEM_QUERY Type="All" Impl="JavaScript">'
…
var s = conn.createStatement();
var resultSet = s.executeQuery("select * from TTST_ITEM ORDER BY ITE_NAME");
while (resultSet.next())
{
allItems += itemToXML(resultSet);
}
resultSet.close();
...
function itemToXML(theItemRS)
{
var theXML = '<ITEM Name="';
theXML += theItemRS.getString("ite_name");
theXML += '" Code="';
theXML += theItemRS.getString("ite_code");
theXML += '" Description="';
if(theItemRS.getString("ite_description"))
{
theXML += theItemRS.getString("ite_description");
}
theXML += '"/>';
return theXML;
}
JavaScript with E4X (see the full source code here)
var allItems = <ITEM_QUERY />
allItems.@Type = 'All';
allItems.@Impl = 'JavaScript/E4X';
...
var s = conn.createStatement();
var resultSet = s.executeQuery("select * from TTST_ITEM ORDER BY ITE_NAME");
while (resultSet.next())
{
allItems.appendChild(itemToXML(resultSet));
}
resultSet.close();
…
function itemToXML(theItemRS)
{
var theItem = <ITEM />;
theItem.@Code = theItemRS.getString("ite_code");
theItem.@Name = theItemRS.getString("ite_name");
if(theItemRS.getString("ite_description"))
{
theItem.@Description = theItemRS.getString("ite_description");
}
return theItem;
}
Scala (see the full source code here)
/**
* Run some code and close whatever was passed to us
*/
def run[C <: {def close(): Unit}, T](in: C)(f: C => T): T =
try {
f(in)
} finally {
in.close()
}
/**
* Given a connection, run a query and
*/
def runQuery[T](theConn: Connection, query: String)(f: ResultSet => T): List[T] = {
run(theConn.createStatement().executeQuery(query)) {
theRS =>
val ret = new ListBuffer[T]
while (theRS.next) ret += f(theRS)
ret.toList
}
}
def allItems(theDataSource: DataSource, paramMap: java.util.Map[String,Object]): NodeSeq =
run(theDataSource.getConnection)( theConn =>
runQuery(theConn, "select * from TTST_ITEM ORDER BY ITE_NAME")(
theRS =>
<ITEM
Code={theRS.getString("ITE_CODE")}
Name={theRS.getString("ITE_NAME")}
Description={theRS.getString("ITE_DESCRIPTION")}
/>
)
)
…
<ITEM_QUERY Type="All" Impl="Scala" >
{allItems(theDataSource,paramMap)}
</ITEM_QUERY>.toString
JRuby (see the full source code here)
…
theXML = '<ITEM_QUERY Type="All" Impl="JRuby">'
s = conn.createStatement
resultSet = s.executeQuery("select * from TTST_ITEM ORDER BY ITE_NAME")
while resultSet.next do
theXML += itemToXML(resultSet)
end
...
def itemToXML(theItemRS)
theXML = '<ITEM Description="'
theXML += theItemRS.getString("ite_description") ? theItemRS.getString("ite_description") : ""
theXML += '" Name="'
theXML += theItemRS.getString("ite_name")
theXML += '" Code="';
theXML += theItemRS.getString("ite_code")
theXML += '"/>'
return theXML
end
You can see there are styles suitable to everybody. Note that I have not tried to minimise number of lines and I sometimes created methods to encapsulate common behaviour and sometimes not. I did not create all those versions to compare them to one another in a sort of competition, I just wanted to see how it could be done.
After all, I'm quite happy with the results because I've been able to test different approaches and even if using other languages means learning a lot of new things, I didn't want to start all over again, throwing away my whole “bag of tools”. Moreover, changing an implementation language is something much easier to accomplish that changing the whole development process and all the tools involved, as it allows a more steady transition. That also means that if you find unexpected results, you can always go back to the language you are more comfortable with, and that you don't have to be forced to ONE solution for everything. And that's what the JVM and the Scripting API provides.
If you did not have experience with all those languages, I hope this helped a bit by showing something more than the typical "Hello World" :).
I also took the time to do some simple performance tests, and I will be sharing the results as well.
Happy coding!
PS: In case some are interested, I already described the basic architecture that I'm using for the tests in the article “A Dynamic MVC Development Approach Using Java 6 Scripting, Groovy, and WebLEAF”.
-
*)Why the XML? Because that's what our architecture uses as “lingua franca” between the business logic and the presentation logic, as it allows us to accommodate implementations in PLSQL, Java, Groovy, static XML files for prototyping, etc. I'm sure many of you can think of, and are using, very different architectures without the controversial use of XML, but that's not the topic for the entry.
-
**)There are some HtmlUnit tests implemented that verify that all implementations indeed return an equivalent XML.