Antecedentes: estou desenvolvendo uma estrutura de mensagens. Essa estrutura permitirá:
- envio de mensagens através de um barramento de serviço
- assinando filas no barramento de mensagens
- assinando tópicos em um barramento de mensagens
Atualmente, estamos usando o RabbitMQ, mas sei que iremos mudar para o Microsoft Service Bus (no local) em um futuro muito próximo.
Eu pretendo criar um conjunto de interfaces e implementações para que, quando mudarmos para o ServiceBus, eu simplesmente precise fornecer uma nova implementação sem alterar nenhum código do cliente (ou seja, editores ou assinantes).
O problema aqui é que o RabbitMQ e o ServiceBus não são diretamente traduzíveis. Por exemplo, o RabbitMQ depende de trocas e nomes de tópicos, enquanto o ServiceBus é sobre espaços para nome e filas. Além disso, não há interfaces comuns entre o cliente ServiceBus e o cliente RabbitMQ (por exemplo, ambos podem ter um IConnection, mas a interface é diferente - não de um espaço para nome comum).
Então, ao meu ponto de vista, posso criar uma interface da seguinte maneira:
public interface IMessageReceiver{
void AddSubscription(ISubscription subscriptionDetails)
}
Devido às propriedades não traduzíveis das duas tecnologias, as implementações ServiceBus e RabbitMQ da interface acima têm requisitos diferentes. Portanto, minha implementação do IMessageReceiver RabbitMq pode ficar assim:
public void AddSubscription(ISubscription subscriptionDetails){
if(!subscriptionDetails is RabbitMqSubscriptionDetails){
// I have a problem!
}
}
Para mim, a linha acima quebra a regra de substituibilidade de Liskov.
Eu considerei inverter isso, para que uma Assinatura aceite um IMessageConnection, mas novamente a Assinatura RabbitMq exigiria propriedades específicas de um RabbitMQMessageConnection.
Então, minhas perguntas são:
- Estou correto que isso quebra o LSP?
- Concordamos que, em alguns casos, é inevitável ou estou perdendo alguma coisa?
Felizmente, isso é claro e sobre o assunto!
interface IMessageReceiver<T extends ISubscription>{void AddSubscription(T subscriptionDetails); }
. Uma implementação pode parecer public class RabbitMqMessageReceiver implements IMessageReceiver<RabbitMqSubscriptionDetails> { public void AddSubscription(RabbitMqSubscriptionDetails subscriptionDetails){} }
(em java).
interface TestInterface<T extends ISubscription>
comunicaria claramente quais tipos são aceitos e que existem diferenças entre implementações.