Filed under: Automation, Continuous integration, — Tags: Gradle, Jenkins, Maven, TeamCity, deb, rpm — Thomas Sundberg — 2015-02-16
A Continuous Integration, CI, server builds a system every time a change has been detected in the version control system. This is a very common practice and something good. We are able to catch many silly mistakes early. The question is, what should the CI server build? Which artifact should the build produce?
The answer obviously depends. It depends on how you plan to use the artifacts later. If you build a Java system it is very easy to say that the artifacts should be a jar, a war or an ear file. This seems to be very natural for most Java developers.
Who can do anything useful with a jar? Actually nobody. Not unless you make it executable,
anyway. This is not very common. It is possible, and often desirable, to bundle everything needed into a jar that
you later can invoke with java -jar my-project.jar
The same goes for a war or an ear. A war file could bundle its own servlet container. An example of a project that
uses this technique is Jenkins. Jenkins is distributed as a war file that can
be deployed into a servlet container. Jenkins can be also started standalone and use its embedded Jetty, which is a Servlet engine and HTTP server. This
means that you can start Jenkins with java -jar jenkins.war
Problems could arise with more complicated systems that should run as a service on a host. A standalone, executable
jar is not possible to start as a service on any operating system I know of. You will at least need a shell script
on Linux to be able to start it from /etc/init.d
This makes it more complicated to install.
If your target environment is Windows, then you need black magic or lots of money to be able to run the application as a service. This makes Windows system very impractical so lets not consider them for running any serious services. I know, I have worked as a CM for two years with a large web shop that is hosted in Windows. Believe me, it is complicated.
A good deliverable from the build is something that can be installed natively. An example could be an rpm package that can be installed on a Linux system. An rpm includes whatever is needed. In the case of an executable jar, at least the jar and a script so it can be started as a service.
If you have access to an rpm package, can you include more stuff and therefore simplify the installation? The answer is most certainly yes. An rpm is just a set of files that includes a way to install and uninstall them. Suppose that you are building a service that is supposed to run on top of a JBoss or Wildfly. What should the rpm package contain to make the installation simple?
I think you should bundle both your application, an ear file and possible some start and stop scripts, as well as the application server. This means that you can install it on any system that has a JVM installed.
This will make the installation simple for any system administrator. It will also make the installation simple on all test systems you want to install it on, before it is installed into production.
Why stop with bundling the application and the application server? You could also bundle the JVM. This would mean that you, the developer, have full control over the runtime environment. The application is built using a well known application server and it is executed on a well know Java version.
The installation will now be trivial for the system administrators. There is no need to install a JVM on the Linux installation that should host the service. And then install the application.
All you actually need is a Linux distribution that can handle the rpm you are about to install and install it.
This also has the benefit that whenever you want to upgrade a component, say the JBoss version, all you have to do is bundle your application with the new version. The combination will now be developed and tested as a whole and you should not find any funny things in production due to different application server versions. The same goes for the JVM. If you bundle the JVM, the risk of finding production problems due to a different version is small. You have developed and tested the entire application with the same JVM version.
The next logical step here is to build a container or an image for a virtual server as the artefact from the CI build. This is perhaps even better compared to building an easy-to-deploy package. Not all of my customers' operations departments are able to cope with that, so I haven't even tried to push the idea yet. But it is a tempting idea to provide something that simplifies the life even further for the operations guys that tend to be the receivers of the applications I help organizations to build.
Delivering artifacts from a Java project that isn't a jar, war or ear file is definitely more complicated. The most common build system, Maven and Gradle, produces jar files out of the box. But the process can easily be automated. It will simplify the rest of the handling of the application. Therefore, I think you should deliver installable packages instead of pure Java artifacts. You know your target environment. You know if you will install something on a Windows box and therefore use Chocolatey, you know if it is an rpm or deb package that is needed. If you don't, build all three and make them available.
The operations guys are your friends and they deserve to be treated well. Give them what they are used to and need. Not what you are able to build easily. If you are deploying the system, make sure you simplify that part of your job as much as possible. Don't settle for what the build system can build for you easily.
I would like to thank Malin Ekholm, Adrian Bolboaca and Alexandru Bolboaca for proofreading.