pconley
pconley

Reputation: 714

How to select an appropriate service implementation?

I've created a service with multiple implementations, like so:

@ProviderType
public interface MyService {
    public void printMessage();
}

@Component
public class Foo implements MyService {
    public void printMessage() {
        System.out.println("foo");
    }
}

@Component
public class Bar implements MyService {
    public void printMessage() {
        System.out.println("bar");
    }
}

The real implementations are obviously a bit more complex (I can't just make a single implementation where printMessage takes an argument!). The consumers of the service need to be able to select an appropriate implementation based on a property - for simplicity, we can assume I'm using the name of the implementation.

The factory method used by the existing non-OSGi implementation is something like this:

public MyService getService(String property) {
    switch (property) {
        case "ham":
            return new Foo();
        case "spam":
            return new Bar();
    }
}

The real implementation is, again, more complicated (it's based on a substring match of the property parameter). The simplest approach (for me) to convert this to OSGi would be to make this factory method into a static method of MyService, returning only the necessary filter string, and to make consumers take care of the fiddly bits with getting a BundleContext, then getting and ungetting the service:

public class MyConsumer {
    private BundleContext context;

    @Activate
    public void activate(BundleContext context) {
        this.context = context;
    }

    public void doStuff() {
        Collection<ServiceReference<MyService>> refs = context.getServiceReferences(MyService.class, MyService.getService("ham"));
        ServiceReference<MyService> service = refs.iterator().next();
        context.getService(service).printMessage();
        context.ungetService(service);
    }

Is there a better way to create a "factory" that returns "services", but doesn't require that the consumers deal with BundleContexts etc.?

I've found a couple approaches that won't quite work:

Upvotes: 1

Views: 990

Answers (1)

Peter Kriens
Peter Kriens

Reputation: 15372

The idea of a service is that the consumer should NOT select because that makes the consumers very un-reusable. It generally makes brittle systems if you move that control into the consumer. The consumer should use whatever is registered.

One anti-pattern is that you try to hide the implementation of the service but then needing a specific implementation in your consumer. If this is your use cases, your implementation is public and just make it its own service type. It could still register as the common MyService type as well of course, you can register under multiple types.

If you have an outside selection criteria for which one to use, e.g. a printer and you want the user to decide, then you just get all and show them to the user. Using the service id you can use the right one.

If you need to configure which service should be used then you should use the @Reference(target="(selection.criterium=foo)"). You can override this selection with Configuration Admin. This is explained in targets. You basically set a property with the name of the reference followed by .target to the required filter.

Upvotes: 2

Related Questions