In the LTS webcast on Januari 29th I discussed why modularity is important, and how OSGi can be used to create modular cloud application with Java.
The case for modularity
Modularity is far fr om a new concept. In the sixties the concept of modular architecture was already discussed. Still today, most code bases turn out to become inflexible, monolithic beasts over time. Apparently modularity isn’t easy. But why should we care? What would true modularity bring us, and why is this becoming a more relevant topic recently?
We are building more and more complex software. At the same time we have been pushing strongly to more agile models, wh ere changes to requirements are embraced. This is obviously a good thing, but our code bases must be able to deal with increased complexity and potentially high impact changes continuously as well. If you have worked on the same code base for more than a year or two, you probably recognize that after a while the code starts to become more and more coupled together. Because of that, it becomes harder to make changes to that code base over time. This is going to hurt the agility of the team, and probably your mood as well.
Modularity is the solution to this problem. By splitting a system into many very small pieces, it becomes much easier to maintain, extend or completely replace those pieces. In a modular system, you don’t have think about the consequences of code changes for the whole system. Instead, code changes are limited to individual modules.
OSGi is the only mature modularity framework for Java. OSGi has been around for about ten years, and is being used in many different fields, ranging from embedded systems, to application servers and large scale cloud applications. Of course modularity is first and foremost an architectural principle. It is possible, to some extend, to create a modular application without using OSGi, or any other modular runtime. Build time tools such as Maven can be used to create different modules within a project. Without a modular runtime however, a modular application looses all modularity again after deployment, and you’re back at a flat classpath and all problems associated with it. Because of this, it is very difficult to achieve true modularity based just on build tools.
Modularity with OSGi
In the past OSGi has had the name to be complex and difficult to use. With today’s tools and frameworks this is far from true. Bndtools, an open source plugin for Eclipse, makes OSGi development fast and easy. One of the nicest features during development is the extremely fast codesavetest cycle, as can be seen in the following video: https://vimeo.com/user17212182/review/83374139/1cea4e1d61. Basically you get a coding experience in Java similar to the coding experience in scripting languages such as Ruby and JavaScript, in the way that code changes are immediately visible without waiting for slow builds. Modularity in OSGi is achieved at different levels. At the lowest level OSGi is all about class loading. Only classes and interfaces explicitly exported by a bundle can be loaded (imported) by other bundles. From the other side, each external package that a bundle uses must be explicitly imported. This makes it possible to hide implementation entirely, which is an important step towards modularity. Even more important are μServices. A service oriented architecture decouples parts of a system by using interfaces. Note that a service in OSGi is extremely lightweight, and doesn’t have any runtime overhead. A bundle can register a service to the service registry. Another bundle can lookup services from the registry by it’s interface. This way the only coupling between the components is that interface. You can change or completely replace the implementation without breaking existing code. In theory this isn’t so different from dependency injection with other frameworks, but just by itself DI is not sufficient. Using μServices is really easy, there are several frameworks available that make consuming and registering services trivial. The following code example shows an OSGi service that exposes a RESTful web service using Amdatu, and uses another service as well.
@Path(“products")
@Component
public class ProductsResource {
@ServiceDependency
private volatile ProductService productService;
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("categories")
public List listCategories() {
return productService.listCategories();
}
In traditional enterprise application architecture systems are commonly described as layers; a UI layer, a business layer and data/integration layer (and often more). This is a good start, but we need a much more fine grained level of modularity. As a rule of thumb you can often take one functional area, e.g. “products”, together with a technology layer, and make that bundle. With that example we could have the following bundles: progress.api, progress.rest, progress.mongo and progress.events. Services should do only a single thing, and do that single thing well. Services may use other services and when kept small, they become reusable units within the system.
Services can use other services
Taking it to the cloud
Now that we have seen how to develop an application based on services, it’s time to discuss deployment. OSGi can of course be deployed in a variety of ways. In our deployment scenarios we want to leverage the cloud, first and foremost for auto scaling, but also because the reduced required management. Looking at the current Platform as a Service (PaaS) providers, there is not really an attractive option yet. All providers either use proprietary APIs, use inflexible non-modular deployments or are too limited on what you can and cannot run. The PaaS landscape is starting to look better though, so this might be different in the future, but we’re not quite there yet.
Instead of using an existing PaaS we can choose to roll our own solution based on Infrastructure as a Service. The downside of doing so is more manual setup. The good news is that this can be very easy for OSGi applications. We start our IaaS nodes with an almost standard Linux image. The only additional software installed is a barebones Apache Felix OSGi runtime with the Apache ACE management agent. When the node starts, it starts the OSGi runtime as well, but it doesn’t contain any bundles yet. To provision nodes with our software, we use Apache ACE.
Apache ACE is an open source provisioning server, focussed on deploying OSGi applications. Instead of uploading software releases directly onto our servers, we upload a release to Apache ACE. This basically means uploading the bundle jar files to Apache ACE. Apache ACE will than distribute those bundles to all the servers. It’s even better for incremental releases. When for example a single bundle is changed for a bug fix, we only upload this single bundle to Apache ACE. This single bundle update will than be distributed to all servers. The servers don’t have to shut down, they will install the new bundle while running. Deploying updates like this takes less than a second, bringing server downtime down to almost nothing.
When a new node starts, it will automatically be registered to Apache ACE and receive software. Within a minute a new node can start serving requests. This makes it extremely easy and quick to add new machines to a cluster. Given this setup, we can setup auto scaling and failover. Depending on the cloud provider you are using there are different APIs to configure auto scaling. On Amazon AWS this is called AWS AutoScaling, which offers both web services and a command line tool. Using AWS AutoScaling you can define a cluster and configure how many nodes a cluster should contain. You can also configure all sorts of triggers for when new nodes should be added to the cluster, or when nodes should be shut down. AutoScaling will automatically start and stop nodes, and because nodes automatically receive software from Apache ACE, they will become active without any manual steps. Failover comes for free in this setup. AWS AutoScaling can be configured with a health check. When the health check fails on a node, it can be automatically replaced by a new node.
One of the great things about this setup is the idea of disposable nodes. We never have to maintain our servers, application server or even the IaaS image we are using. Instead, when we have a software update, we simply upload it to Apache ACE. When a node starts to misbehave because of either a software or hardware problem, we simply replace it with a new node. This greatly reduces server maintenance costs and reduces downtime.
Uploading releases to Apache ACE can be completely scripted from a Continuous Integration server. This way we remove all manual steps from the deployment process, which makes deployments fast, easy and low risk.
In a more traditional infrastructure OSGi applications can be deployed to application servers as well. Some application servers offer native support for OSGi, for others you can wrap OSGi in, for example, a Servlet container. Alternatively you could look at Apache Karaf, which is an application server specialized at OSGi.
Learning more
There are plenty of resources available to learn more about OSGi to get you started quickly. First of all there is the book written by me and Bert Ertman: “Building Modular Cloud Apps with OSGi” from O’Reilly. There are also tutorial video’s available on amdatu.org and my blog.