Best Practices in the WSO2 Carbon Platform

 

This post discusses best practices when programming with the WSO2 Carbon platform, which is the base for all WSO2 products.

Here are the main points discussed in this post:

  1. Do not hardcode compile time, run time dependencies and re-use properties in the root pom.
  2. Use OSGI Declarative Services
  3. Do not copy paste code, re-use code through OSGI, Util methods, etc.
  4. Understand Multi tenancy and design for Multi Tenancy
  5. Write tests for your code

 

These points are discussed in detail below giving reasons and a HOWTO for each point. Hope you find the details useful as this is a long (and probably boring) read.

  1. Do not hardcode compile time, run time dependencies and re-use properties in the root pom.
Ex:
       <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>1.0.0.wso2v1</version>
        </dependency>
Why do this?
This should be avoided as it threatens the stability of the build. If two versions of the same jar comes into a product, it can cause OSGI related errors, that can take some time to identify and fix.
How to avoid this?
Make sure the root pom (components/pom.xml or platform/pom.xml) has a dependency version defined, and use that.
Ex: in platform/pom.xml
<orbit.version.json>2.0.0.wso2v1</orbit.version.json>
If it is not defined, please define it in the root pom and use this version.
 
 
 
2. Use OSGI declarative services
Do not get service references in the activate method.
ServiceReference serviceReference = componentContext.getBundleContext().getServiceReference(Foo.class.getName());
        if(serviceReference != null){
            Foo = (Foo) componentContext.getBundleContext().getService(serviceReference);
        }
Why do this?
(Quoting, Pradeep here) This can lead to erroneous situations and you will have to check whether services are available in a while loop to make it work properly. And it becomes complicated when two or more service references.
Why bother to do all this when the DS framework handles all this for you.
How to avoid this?
Use a DS reference in your service component.
Ex:
 * @scr.reference name=”foo.comp”
 * interface=”org.wso2.carbon.component.Foo”
 * cardinality=”1..1″ policy=”dynamic” bind=”setFoo”  unbind=”unsetFoo”
protected void setFoo(Foo foo) {
// do whatever ex:
        manager.setFoo(foo);
}
protected void unsetFoo(Foo foo) {
// make sure lose the reference
        manager.setFoo(null);
}
3. Do not copy paste code, re-use code through OSGI, Util methods, etc.
 
If you need functionality provided by other components, don’t copy paste code. Find another way to re-use the code. If you need some common thing done most probably, there exists a util method to that, or an osgi method. If not add or create one.
Why do this?
 
It might take some effort and discipline but later on you (or someone else) will have to write less code. If changes happen to the original code, you will have to fix the copy pasted code as well (which is often missed).
How to do this?
 
i. Use util methods/ constants
Easily said with an example. Ex: To split domain name from user name use, MultitenantUtils.getTenantDomain(username);
Same applies for constants. Ex: MultitenantConstants.SUPER_TENANT_DOMAIN
ii. Use and expose OSGI services
You can simply expose any class you want by registering an OSGI service,
ex: In the bundle activator,
context.getBundleContext().
                        registerService(Foo.class.getName(), new Foo(), null);
Get a reference and re-use as pointed in point 2.
4. Understand Multi tenancy and design for Multi Tenancy
 
I feel that some folks don’t understand what multi tenancy (MT) means.  It is an important aspect of the platform and it should not be an after thought, but a part of the design.
Why do this?
 
Making code work for multiple tenants needs some careful design. It may not be straight forward for some cases. So thinking about it after a release or when you want to make the code work for MT may require some heavy refactoring. Now with the products and services merged, multi tenancy should not be separate at all.
How to do this?
 
This is an extensive topic so I will not go into details.
Using AxisConfigurationContextObserver, Tenant aware registries are some easy ways provided by the platform. If you are depending on a non-MT dependency, you will have to figure out how to make it work in the MT case. You can always get help from other folks who have done MT aware stuff.
 
5. Write tests for your code
Make sure you write tests for your code and gain a good % of code coverage. Folks will not know whether changes will break functionality or not until it is too late.
Why do this?
 
The reasons are obvious and have been stated by many. But to re-iterate, this makes the code base extremely stable. Other folks can change your code to fix bugs or do enhancements without worrying about breaking functionality or actually breaking functionality.
How to do this?
 

I personally prefer unit tests. But we have an integration test framework and as well as a system test framework (Clarity). Make sure you have tests to address to cover most functionality, if not all functionality. Features should not be considered complete, without test coverage.

 

If you find improvements on the points spoken, please do leave a comment and I will incorporate it into the post.

Running FindBugs/Code Inspections = Saving countless dev hours

I wanted to write this post after learning so much from running FindBugs and Idea code inspections on my code over a period of time.

There was once a simple code I wrote after seeing some performance gaps, to introduce some local caching. So I used a simple HashMap implementation, which really couldn’t go wrong. Or so I thought. We put this up in the running system after some quick initial tests, and then it started to give all sorts of problems. A culprit was a piece of code like this:

private Resource getSubscriptionResource(Registry registry, String endpointUUIDPath) throws RegistryException {
 Resource subscriptionEndpoint = null;
 if (registryResourceCache.containsKey(endpointUUIDPath)) {
 subscriptionEndpoint = registryResourceCache.get(subscriptionEndpoint);
 } else {
 subscriptionEndpoint = registry.get(endpointUUIDPath);
 registryResourceCache.put(endpointUUIDPath, subscriptionEndpoint);
 }
 return subscriptionEndpoint;
 }

After this piece of code was running in the system, it started to give all sorts of errors (obviously) and I had to start by reverting the patch, going through the code to figuring out the problem, involving some proper QA to verify the functionality, and finally, applying the patch to the running system. This cost us maybe about 4 to 5 hours of dev/QA time. Of course, the blunder is obvious now, but not at that time.

If I took 5 minutes and ran Idea code inspections on my class over my code, it would show me this:

This simple inspection would have saved me so much of my time and my colleagues’ time as well.

FindBugs is a tool which can analyze your Java code and show you probable bugs, bad practices, performance bottlenecks and anti-patterns that looks perfectly fine to the naked eye. Many of us feel uneasy when fixing these reported issues as they seem unnecessary and tend to mess up our code. But those very little issues come back to you as  nasty bugs that bite you right where it hurts later on. One of the lessons I learnt from running FindBugs on my code is that double checked locking can introduce some really weird behavior in multi-core/ multi-threaded systems.

Here is what I saw:

It even provided me a great link, that expanded my knowledge about this subject (http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html). Now I know how to use it properly, and also to avoid using it if at all.

I learnt these lessons the hard way. You don’t have to. Use FindBugs or Idea inspections or any other code analysis tool before you actually commit your code. It will surely save tons of your time, while give you an amazing knowledge of better coding practices.