An Ant Modular Build Environment for Enterprise Applications
Pages: 1, 2
We organize a module's directory structure corresponding to common Java industry conventions for source code management. Although there are different conventions, this is the directory structure used in our build environment:
modulename |-- build/ |-- etc/ |-- src/ |-- test/ |-- build.xml
Here's what each entry means:
build: This directory is special in that it is generated by the module build. All other directories and files listed above are entered into the RCS. The build directory contains all files generated during the build process, from auto-generated XML to compiled Java class files, and finally any distribution artifacts (.war, .jar, .ear, etc.). This makes it very easy to clean a build by just deleting this directory.
etc: This is a directory where all config files that are used by the module during build or run time are stored. Most of the time, you'll find properties files and XML config files in here, such as log4j.properties or struts-config.xml. If there are a lot of files, they're typically organized into subdirectories for the components they relate to; e.g., etc/spring/, etc/struts/, etc/ejb/, etc.
src: This directory is the root of your source file directory tree. There are no other directories in it other than those that directly correspond to a package and/or classpath location. So you'll usually see a com/ or net/ or org/ directory here starting a
org.mydomainpackage structure. It is important to note that only things that have a one-to-one classpath correspondence are saved in this directory (i.e., package directories or .java source files).
test: This directory is for your test classes (e.g. JUnit test cases). The important thing here from an organization perspective is the package structure mirrors exactly that found under the src directory. This makes it very convenient for managing test cases, because you instantly know that the class:
build.xml: This Ant file knows how to do everything needed by this module to build and distribute the artifact for which it is responsible. If this module has any submodules, it also knows how to build those submodules and in which order they should be built. Submodules and build ordering are very important concepts that we'll cover shortly.
A submodule is just a module that is a child of another (parent) module. You might have seen other module-based Ant builds where the hierarchy is flat; i.e., one level deep. Our build structure goes a little further than that: ours is two levels deep.
Continuing with our build and the concept of submodules, you would see a build hierarchy like the following, with the module and submodule directories expanded:
module1/ submodule1.1/ |-- etc/ |-- src/ ... |-- build.xml submodule1.2/ |-- etc/ |-- src/ ... |-- build.xml build.xml module2/ ...
OK, so this looks a little complex. Why would we want to do this?
Well, let's preface the answer with a little background on enterprise applications and the concept of an artifact-driven build.
Enterprise applications are almost always client/server-based. Even if you only deploy a web application, it's usually architected as a client-server MVC application. That is, the web page itself is a client view, but the "server"-side components are usually business POJOs that execute business logic on behalf of the component rendering the web page. Even if they are deployed in a single .war, there is a definite architectural separation between code that is primarily used for rendering a view (client code) versus code that is used for processing business requests (server code). At least, there should be!
The notion of client and sever code becomes more obvious in a more traditional client/server application where there is a standalone client GUI communicating with a server-side business object via sockets.
It would be very clean and elegant if we only needed to deploy client code to the client application and server code to the application server. Both tiers also probably share common code, so it would be nice to send common .jars to both client and server. This is the cleanest way to deploy code and manage dependencies between tiers. Our build environment has the ability to create artifacts exactly as desired.
Next we will look at how submodules help us achieve an artifact-driven build.
Hierarchy and Build Artifacts
The deployment scenario just described surfaces a desire for an
artifact-driven build: each module or submodule in the build
environment should be responsible for creating an artifact that
will be deployed to the client or server or both. This is easily
done in our build environment by further breaking down the modules
in our sample application into
server submodules. The
parent-child relationship and delegation of build responsibilities
is what makes this build hierarchical as well.
Using our sample application's
admin module, lets
see what the hierarchy looks like in an expanded directory
appname/ |-- admin/ |-- common/ |-- etc/ |-- src/ |-- test/ |-- build.xml |-- client/ |-- etc/ |-- src/ |-- test/ |-- build.xml |-- server/ |-- etc/ |-- src/ |-- test/ |-- build.xml |-- build.xml ...
Each submodule's contents are structured as defined before, but there's a noticeable difference.
admin module does not have the typical module contents. It
just has submodules and a build.xml, and it doesn't
produce any artifacts itself. Instead it calls build targets in the
common/build.xml, server/build.xml, and
client/build.xml files via the template technique described earlier.
So if you wanted to build the
admin module, you just change into
the admin directory and run Ant:
> cd admin/ > ant
This command uses the admin build.xml file, which in
turn builds the
client submodules. After each submodule is built,
there will be three resulting artifacts:
The common and server .jars can then be deployed to the server (e.g., in an .ear file), and the common and client .jars can be deployed to the client (e.g., in a .war's WEB-INF/lib directory).
What is the purpose of each submodule? Well, they help organize code into cleanly managed subsets of functionality that will be deployed in different tiers of the application. Here's what the above three submodules typically contain:
common: All code that is common to both client and server tiers for the module. This typically means business POJO interfaces, utility classes, etc.
server: Class implementations only needed on the server tier. These are generally implementations of business POJO interfaces, DAO implementations for EIS access, etc.
client: Class implementations only needed on the client tier, such as Swing GUI objects, EJB remote interfaces, etc.
This kind of granularity of submodules and their respective deployment artifacts benefits you in four substantial ways:
- Download times: You can ensure that standalone client
applications such as applets and Java Web Start
applications receive the smallest subset of .jars required to run.
This ensures the fastest possible download times of an application
or applet being run for the first time.
- Dependency management: Via an Ant
<path>entry in the submodule's build.xml file, you can list exactly which other module and/or submodules are allowed as dependencies by the current submodule. This eliminates any lazy or accidental use of APIs that a developer is not supposed to use or won't be supported during runtime.
- Dependency ordering: Because the parent module determines
build order for submodules, you can rest assured that the
clientcode you write can depend upon
commoncode, but not
commoncode cannot be written that is dependent upon
clientcode. If you do these things, your build will break, and you'll instantly be alerted that you accidentally used classes that you shouldn't have. This may sound like a small or nit-picky issue, but this problem quickly rears its head in complex projects or those where the developers have different levels of experience and may not be aware of dependency management.
- Just as you can with modules, you can build just a single
submodule by entering in its directory and running
Modules and submodules may look complicated. They probably look like overkill to you at this point. But trust me from experience, they greatly simplify how you manage source code and dependencies, and how Ant builds your product. The structure defined here really does make product-feature and source-code management easier in a team environment. It takes a lot of the guess work out of figuring out how to do all of the organization yourself, and once set up, is pretty transparent. If you're starting a new client/server project, give it a shot. You'll spend more time working on your application, and less time worrying about configuration management.
Special thanks to Jeremy Haile of Transdyn Controls for his valuable input and review of this article.
- Sample code for this article
- Ant (see also O'Reilly CodeZoo: Ant)
- JUnit (see also O'Reilly CodeZoo: JUnit)
Return to ONJava.com.