Background: I am developing a messaging framework. This framework will allow:
- sending of messages over a service bus
- subscribing to queues on the message bus
- subscribing to topics on a message bus
We are currently using RabbitMQ, but I know we will be moving to Microsoft Service Bus (on Premise) in the very near future.
I plan to create a set of interfaces and implementations so that when we move to ServiceBus, I simply need to provide a new implementation without amending any of the client code (i.e. publishers or subscribers).
The issue here is that RabbitMQ and ServiceBus are not directly translatable. For example, RabbitMQ relies on Exchanges and Topic Names, whereas ServiceBus is all about Namespaces and Queues. Also, there are no common interfaces between the ServiceBus client and the RabbitMQ client (e.g. both may have an IConnection, but the interface is different - not from a common namespace).
So to my point, I can create an interface as follows:
public interface IMessageReceiver{
void AddSubscription(ISubscription subscriptionDetails)
}
Due to the non-translatable properties of the two technologies, the ServiceBus and RabbitMQ implementations of above interface have different requirements. So my RabbitMq implemetation of IMessageReceiver may look like this:
public void AddSubscription(ISubscription subscriptionDetails){
if(!subscriptionDetails is RabbitMqSubscriptionDetails){
// I have a problem!
}
}
To me, the line above breaks Liskov's rule of substitutability.
I considered flipping this around, so that a Subscription accepts a IMessageConnection, but again the RabbitMq Subscription would require specific properties of a RabbitMQMessageConnection.
So, my questions are:
- Am I correct that this breaks LSP?
- Do we agree that in some cases it is unavoidable, or, am I missing something?
Hopefully, this is clear and on topic!
interface TestInterface<T extends ISubscription>
would clearly communicate which types are accepted, and that there are differences between implementations.interface IMessageReceiver<T extends ISubscription>{void AddSubscription(T subscriptionDetails); }
. An implementation could then look likepublic class RabbitMqMessageReceiver implements IMessageReceiver<RabbitMqSubscriptionDetails> { public void AddSubscription(RabbitMqSubscriptionDetails subscriptionDetails){} }
(in java).