Search code examples
dependenciesosgiadaptermodularization

Adapters and modularization without transitive dependencies


I have run into the following situation a couple of times and I cannot find a fully satisfying solution:

I am developing a Java application using OSGi and, following OSGi's best practices, it is highly modularized. Here's an extract of some of my plugins and classes in them:

- com.example.core.db.manager (plugin)
    |- com.example.core.db.manager (package)
         |- DatabaseManager (interface)

- com.example.core.business.objectmanager (plugin)
    |- com.example.core.business.objectmanager (package)
         |- BusinessObjectManager (interface)

- com.example.some.businessobjectmanager.consumer (plugin)
    |- com.example.some.businessobjectmanager.consumer (package)
         |- SomeBusinessObjectManagerConsumer (interface)
            (obviously this is not the real name, but the name is irrelevant)

Where

  • DatabaseManager is a low-level construct that interacts directly with the database.
  • BusinessObjectManager is a high-level construct that acts (among other things) as a sort of adapter for the DatabaseManager.
  • SomeBusinessObjectManagerConsumer consumes BusinessObjectManager and should not know about the underlying database, not even that there is a database. It should not know about the DatabaseManager; rather, it should interact only with the BusinessObjectManager.

So far so good. But now SomeBusinessObjectManagerConsumer needs to update some edges between entities in the database (I use a graph database, meaning my entities (what would normally be rows in a table) are nodes, and the relationships between them are edges). As explained before SomeBusinessObjectManagerConsumer doesn't know anything about the database, but it knows there are some "business objects" (nodes) and that there are links between some of them (edges).

In BusinessObjectManager, I create a method replaceLinks as follows...

UpdatedLinks replaceLinks(BusinessObjectUID from, Set<BusinessObjectUID> to);

...which is supposed to make sure that, by the time it returns, the from business object will only be linked to the to objects, possibly removing previous links and adding new ones. I would like to know about these removals and additions. I create an interface UpdatedLinks in plugin com.example.core.business.objectmanager:

public interface UpdatedLinks {
    Set<BusinessObjectUID> getRemovedLinks();
    Set<BusinessObjectUID> getAddedLinks();
}

But the BusinessObjectManager is not really the one who is going to perform this link replacement and put together the UpdatedLinks return object. Instead, it delegates this to DatabaseManager. So I create an equivalent method in DatabaseManager, and BusinessObjectManager will simply invoke this method.

The problem now is where to place the interface UpdatedLinks: it is needed by DatabaseManager, BusinessObjectManager and SomeBusinessObjectManagerConsumer, but the dependencies between these classes (and their corresponding plugins) goes in this direction:

SomeBusinessObjectManagerConsumer ---depends-on---> BusinessObjectManager ---depends-on---> DatabaseManager

So:

  • I cannot put UpdatedLinks in BusinessObjectManager's plugin because then it is not visible for DatabaseManager.
  • I cannot put it in DatabaseManager's plugin because then it is not visible for SomeBusinessObjectManagerConsumer (remember that SomeBusinessObjectManagerConsumer doesn't know anything about DatabaseManager; because of modularization in OSGi, it can only have access to DatabaseManager if I declare an explicit dependency on it, which I don't want to do).
  • It is too specific to put it in some kind of "utilities" plugin.
  • I do not currently have any plugin where it makes sense to put this interface and that would make it available to all the dependent plugins.

Basically, I would have to create a new plugin just for this interface and I cannot even find any meaningful name for that plugin, considering who is going to depend on it. (But mainly, I resist the idea of creating a plugin just for this interface, which exists just as a means to deliver the results of the replaceLinks method).

I have encountered this situation several times (especially lately using OSGi, because of the modularization) and I can never find a fully satisfying solution. What would/do you do in such a case?


"Meta" disclaimers:

  • The title could be better; I just can't come up with a better one. Feel free to change it.
  • I'm not sure whether stackoverflow is the right StackExchange community for this question so please feel free to recommend others that may be more appropriate. I couldn't find a better one.
  • I tried to explain in detail so that you can picture the situation. I hope I didn't overdo it ;)

Solution

  • I propose to simply create two interfaces for it. One one the DataBaseManager level that is used by BusinessObjectManager and one on the BusinessObjectManager level that is used by SomeBusinessObjectManagerConsumer.

    As you want the bottom and top layers not to be connected you should also not share interfaces between them. Of course you could also create a special api bundle just for this but I think it would not be worth it and hurting the cohesion in api of each layer.