0

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 ;)
Alix
  • 927
  • 7
  • 21

1 Answers1

1

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.

Christian Schneider
  • 19,420
  • 2
  • 39
  • 64
  • Hi Christian. I thought of that but I assume you're suggesting to have 2 implementations `AImpl` (implements `A`) and `BImpl` (implements `B`)? Then I have to copy the data from an `AImpl` object to a `BImpl` object just to pass the data between the different classes... In this particular case that wouldn't be much effort or too computationally expensive, but there could be cases where this turns out to be too expensive. – Alix Sep 18 '15 at 10:50
  • Yes .. I think it is necessary if you want to achieve a strict layering where each layer should only know about the one below. You have to decide for yourself what is more important less overhead or independent layers. Additional layers always cause overhead. So also question each layer if it is really necessary. – Christian Schneider Sep 18 '15 at 12:41
  • That's a fair point, although I'm still not thrilled about the solution. (Not the solution's fault; there's probably no 100% good way to solve this). I'll leave it till Monday to see if there are any other suggestions and otherwise I'll accept your answer. Thanks! – Alix Sep 18 '15 at 13:46