Skip to content
This repository has been archived by the owner on Nov 16, 2021. It is now read-only.

Component "outjection" for declarative services. #41

Open
bjhargrave opened this issue Jun 24, 2019 · 1 comment
Open

Component "outjection" for declarative services. #41

bjhargrave opened this issue Jun 24, 2019 · 1 comment
Labels
publiccomment Public comment

Comments

@bjhargrave
Copy link
Member

Original bug ID: BZ#232
From: Elias N Vasylenko <elias@vasylenko.uk>
Reported version: R7

@bjhargrave
Copy link
Member Author

Comment author: Elias N Vasylenko <elias@vasylenko.uk>

Sometimes we need to publish (possibly interrelated) services according to patterns which are awkward or impossible to express in the current DS model. For example:

  • Register a set of services which expose different "views" of some underlying component, but which can't be implemented on the same instance.

  • For some service "B" which consumes a single instance of another service "A", dynamically register an instance of "B" for every available "A".

  • Register a non-interface-class service, where instances of the class can only be obtained via some API.

Typically this means leaving the safety and simplicity of DS and manually registering and unregistering services, and manually publishing the relevant capability information in the manifest.

I propose to introduce a mechanism for a "master" component to declaratively advertise "subordinate" components which the framework should register on its behalf. I'll describe the design space of the proposal in terms of service annotations rather than the component.xml format as it's a little easier to visualise and discuss.

The means by which a master would advertise/expose subordinates would be via @ Component annotations on fields and methods. The SCR would register those fields, and the results of those methods, as components on behalf of the master. When the master component is activated or deactivated, so are its subordinates.

So in the following example, in which we have the interfaces ThingA and ThingB, we might expect the SCR to register the objects referenced by the fields a and b as services of the appropriate interface.

@ Component
class ThingManager {
  @ Component
  public final ThingA a;
  @ Component
  public final ThingB b;

  @ Activate
  public ThingManager( ... ) {
    a = ... ;
    b = ... ;
  }
}

We can also imagine support for adding "field" and "method" targets to other service annotations so they can be applied to subordinate components.

Optionally we might also want to support exposing collections of subordinate services. In the following example, we might expect the SCR to behave similarly to as above, but to register each element of the collection returned by getThings as a Thing service.

@ Component
class ThingManager {
  private Collection<Thing> things;

  @ Activate
  public void activate( ... ) {
    things = Collections.unmodifiableSet( ... );
  }

  @ Component
  public Collection<Thing> getThings() {
    return things;
  }
}

Here we simply have to trust that the referenced object of things and its contents will not change once activate is complete, as we have no way to verify this, much less to observe changes and react accordingly.

A limitation of exposing collections of subordinate services is that we can't specify different service properties for each element, which also means that the PID needs to be generated to make them unique.

Exposing dynamic subordinates is a little trickier. We might, for example, want to support a pattern where we expose a Derived service for every instance of a Thingy service.

@ Component
class ThingManager {
  private final Map<Thingy, Derived> derivations = new HashMap<>();

  @ Activate
  public ThingManager(/* ... */) {
    // ... init things ...
  }

  @ Reference(cardinality = MULTIPLE, policy = DYNAMIC)
  public void addThingy(Thingy thingy) {
    derivations.put(thingy, new DerivedImpl(thingy));
  }

  public void removeThingy(Thingy thingy) {
    derivations.remove(thingy);
  }

  @ Component
  Collection<Derived> getDerivations() {
    return derivations.values();
  }
}

In this case, the result of getDerivations can change dynamically, and as before the SCR has no way of hooking into the implementation to figure out when results might change. However we can specify that every time a dynamic reference of a master component is changed, or the configuration of a master component is changed, all subordinate components are rechecked and registered/unregistered accordingly. This should capture most use-cases.

So for the above example, that would mean every time SCR calls addThingy or removeThingy it must also call getDerivations and then unregister each Derived that it previously registered on behalf of ThingManager but is now missing, and register each new Derived in the collection which has not previously been registered. We should compare Thingys by identity rather than equality here.

There would probably be various constraints on how the components could be configured.

For example on service scopes:

  • Prototype-scope services should not be allowed to have subordinates, as there would be no way for consumers to bind to both the master and the subordinate.
  • Subordinates which are fields should not have prototype scope as new ones can't be generated on demand.
  • The service scope of a subordinate should not be "wider" than that of its master. For example a bundle-scoped master can expose prototype-scoped subordinates (on methods), as the framework can call the method each time a new instance of the subordinate service is required by a component within the same bundle. But a bundle-scoped master cannot expose singleton-scoped subordinates, as there is no sensible way to select a master within the global singleton scope.

Just a suggestion. It's something that I've felt is missing a few times when working with OSGi, and I'd be happy to give a few less abstract examples of use-cases if it'd help. I think probably every single time I've had to manually register services in a BundleContext, I could have achieved the same thing more easily with a simply declarative system like this.

@bjhargrave bjhargrave added the publiccomment Public comment label May 1, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
publiccomment Public comment
Projects
None yet
Development

No branches or pull requests

1 participant