Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OSGi loose-coupling best practice

I'd like to know what is considered the best practices or patterns for decoupling application code from framework code, specifically regarding OSGi.

I'm going to use the example from the Felix SCR pages

The example service is a Comparator

package sample.service;
import java.util.Comparator;
public class SampleComparator implements Comparator
{
    public int compare( Object o1, Object o2 )
    {
        return o1.equals( o2 ) ? 0 : -1;
    }
}

The code above contains no framework plumbing, it's focused and concise. Making this available to the application, when using OSGi, involves registering it with a service registry. One way, as described on the Felix pages linked, is by using the Service Component Runtime.

// OSGI-INF/sample.xml
<?xml version="1.0" encoding="UTF-8"?>
<component name="sample.component" immediate="true">
  <implementation class="sample.service.SampleComparator" />
  <property name="service.description" value="Sample Comparator Service" />
  <property name="service.vendor" value="Apache Software Foundation" />
  <service>
    <provide interface="java.util.Comparator" />
  </service>
</component>

and

Service-Component: OSGI-INF/sample.xml

All nice and lovely, my service implementation has no coupling at all to OSGI.

Now I want to use the service...

package sample.consumer;
import java.util.Comparator;
public class Consumer {
    public void doCompare(Object o1, Object o2) {
        Comparator c = ...;
    }
}

Using SCR lookup strategy I need to add framework-only methods:

protected void activate(ComponentContext context) {
    Comparator c = ( Comparator ) context.locateService( "sample.component" );
}

Using SCR event strategy I also need to add framework-only methods:

protected void bindComparator(Comparator c) {
    this.c = c;
}

protected void unbindComparator(Comparator c) {
    this.c = null;
}

Neither are terribly onerous, though I think it's probable you'd end up with a fair amount of this type of code duplicated in classes, which makes it more noise to filter.

One possible solution I can see would be to use an OSGi specific class to mediate between the consumer, via more traditional means, and the framework.

package sample.internal;
public class OsgiDependencyInjector {
    private Consumer consumer;
    protected void bindComparator(Comparator c) {
        this.consumer.setComparator(c);
    }

    protected void unbindComparator(Comparator c) {
        this.consumer.setComparator(null);
    }
}

Though I'm not sure how you'd arrange this in the SCR configuration.

There is also org.apache.felix.scr.annotations, though that means it'll all only work if you're building with the maven-scr-plugin. Not so bad really and, AFAICT, they impose no runtime implications.

So, now you've read all that, what do you suggest is the best way of consuming OSGi provided services without 'polluting' application code with framework code?

like image 835
ptomli Avatar asked Feb 09 '11 14:02

ptomli


1 Answers

1) I do not think the bind methods are polluting your code, they are just bean setters (you can also call them setXXX to be more traditional). You will need those for unit testing as well.

2) If you use bnd (which is in maven, ant, bndtools, eclipse plugin, etc) then you can also use the bnd annotations. bnd will then automatically create the (always horrible) xml for you.

package sample.service;
import java.util.Comparator;
import aQute.bnd.annotations.component.*;

@Component
public class SampleComparator implements Comparator {
    public int compare( Object o1, Object o2 ) {
        return o1.equals( o2 ) ? 0 : -1;
    }
}

@Component
class Consumer {
    Comparator comparator;

    public void doCompare( Object o1, Object o2 ) {
      if ( comparator.compare(o1,o2) ) 
        ....
    }

    @Reference
    protected setComparator( Comparator c ) {
       comparator = c;
    }
}

In your manifest, just add:

Service-Component: *

This will be picked up by bnd. So no OSGi code in your domain code. You might be puzzled there is no unset method but the default for bnd is static binding. So the set method is called before you're activated and you're deactivated before the unset would be called. As long as your Consumer object would be a µservice too, you're safe. Look at bndtools, the bnd home page, and my blogs for more info about µservices.

PS. Your sample is invalid code because o1 will answer the both greater than and lesser than o2 if o1 != o2, this is not allowed by the Comparator contract and will make sorts unstable.

like image 196
Peter Kriens Avatar answered Sep 19 '22 06:09

Peter Kriens