Friday, February 16, 2007

Configuration Considered Harmful

Half a year ago I downloaded my first Java 2 Enterprise Edition, also known as J2EE, the version of Java you use to build websites. It uses a very complex build system named Java Blueprints, spanning 26 files and 2000 lines of XML and settings. Coming from Symbian background, I though that the build process really is incomprehensibly complex: In Symbian, the build process uses ~100 or so perl modules, and you are not supposed to modify them. To give you an idea of how much 2000 lines is, note that this applet, which packs some real functionality, takes ~1500 lines.

In fact the J2EE build process is really simple. First, you build a jar file with a specific directory structure. Then you deploy it to some application server. Knowing this is useful when you switch from one application server to another (in my case, from Sun's server to Tomcat). Therefore, it's really stupid to use a huge build system.

Complex systems are OK when they really work and make things easier. Anyone who has converted a medium-sized application from manual building to Make or Ant knows this. However, when they become complex enough without being nicely encapsulated, they introduce an extra moving part. Useless moving parts merely make things more difficult.

Java is famous for giving good and accurate error reports in the form of exceptions and stack traces. The compiler checks the code extensively before you can execute it. There is also a standard way to produce good documentation for Java modules that are delivered to third parties (the Javadoc tool). Configuration systems tend to be exactly opposite: They use custom formats, often XML, which are often poorly documented. Their error annoucements leave you wondering what the hell went wrong, if they give any in the first place. Often, you can't be sure whether the error is in the configuration files or in the code.

Next, I'll present a truly minimal hello world J2EE application. Here is the list of files:



build.xml - the Ant build file
src\HelloServlet.java - the source code
web\WEB-INF\web.xml - the deployment descriptor



Here is the source code. When you type the address of the web service (usually http://localhost/hello or something similar), the program calls the get or post method of this servlet. The request object contains the things that the user sent to you: for example, if the user filled a form and pressed submit, the contents of the form are in the request. The response represents the HTML page that you return to the user.



/* Minimal Hello World servlet */
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloServlet extends HttpServlet {

public void doGet(
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
doPost( request, response );
}

public void doPost(
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
response.setContentType("text/html; charset=UTF-8");

PrintWriter out = response.getWriter();
out.println( "<html>" );
out.println( "<head><title>Helo World</title></head>" );
out.println( "<body><h1>Helo World</h1></body>" );
out.println( "</html>" );
}
}



The deployment descriptor tells that the application should be deployed at "hello" subpage (http://localhost/hello) and that the HelloServlet generates the page (there may be several servlets).



<?xml version="1.0" encoding="UTF-8"?>

<web-app>
<display-name>hello</display-name>

<servlet>
<display-name>HelloServlet</display-name>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>HelloServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

</web-app>



The Ant build file build.xml is still somewhat more complex than needed, but a far cry from 2000 lines. There's nothing worth reading happening there, it is here merely for completeness. It has been simplified from the spring tutorial build file, which strikes a very good balance between simplicity and functionality.



<?xml version="1.0"?>

<project name="flash1" basedir="." default="build">

<property name="src.dir" value="src"/>
<property name="web.dir" value="web"/>
<property name="build.dir" value="${web.dir}/WEB-INF/classes"/>
<property name="name" value="hello"/>
<property name="tomcat.dir" value="/tools/tomcat5.5"/>
<property name="deploy.path" value="${tomcat.dir}/webapps"/>

<path id="master-classpath">
<fileset dir="${web.dir}/WEB-INF/lib">
<include name="*.jar"/>
</fileset>
<!-- We need the servlet API classes: -->
<!-- for Tomcat 4.1 use servlet.jar -->
<!-- for Tomcat 5.0 use servlet-api.jar -->
<!-- for Other app server - check the docs -->
<fileset dir="${tomcat.dir}/common/lib">
<include name="*.jar"/>
</fileset>
<pathelement path="${build.dir}"/>
</path>

<target name="usage">
<echo message=""/>
<echo message="${name} build file"/>
<echo message="-----------------------------------"/>
<echo message=""/>
<echo message="Available targets are:"/>
<echo message=""/>
<echo message="build --> Build the application"/>
<echo message="deploywar --> Deploy application as a WAR file"/>
<echo message=""/>
</target>

<target name="build" description="Compile main source tree java files">
<mkdir dir="${build.dir}"/>
<javac destdir="${build.dir}"
target="1.5"
debug="true"
deprecation="false"
optimize="false"
failonerror="true"
encoding="UTF8">
<src path="${src.dir}"/>
<classpath refid="master-classpath"/>
</javac>
</target>

<target name="deploywar" depends="build" description="Deploy application as a WAR file">
<war destfile="${name}.war"
webxml="${web.dir}/WEB-INF/web.xml">
<fileset dir="${web.dir}">
<include name="**/*.*"/>
</fileset>
</war>
<copy todir="${deploy.path}" preservelastmodified="true">
<fileset dir=".">
<include name="*.war"/>
</fileset>
</copy>
</target>

</project>



This example gives us another case about configuration and its discontents: The deployment descriptor. When you do traditional graphical user interfaces with Swing, you create the elements by code. Imagine that instead of writing an XML deployment descriptor you would configure the application by code, applet style, demolishing the second big instance of configuration in J2EE. What would it look like?



import javax.servlet.*;

public class HelloApp extends Weblet {

public void init() {
setName( "hello" );
addServlet( "HelloServlet", HelloServlet.class );
mapServlet( "HelloServlet", "/" );
}
}



In what way is this inferior to web.xml?

No comments: