Tom
Tom

Reputation: 95

Does my design violate the Liskov Substitution Principle?

I'm working on a Spring Boot application with the following structure for sending messages

public interface MessageService {
  void send(String message);
}

@Component("SendEmailService")
public class SendEmailService implements MessageService {

  @Override
  public void send(String message) {
    System.out.println("SendEmailService");
  }
}

@Component("SendSmsService")
public class SendSmsService implements MessageService {

  @Override
  public void send(String message) {
    System.out.println("SendSms");
  }
}

The application works well, but now I've got a new requirement. Some messages sent by email need a retry mechanism in case of failure. Here's how I implemented this:

public class ExceptionUtils {

  public static void retryOnException(Runnable runnable, int maxRetries, long timeSeed) {
    // Retry logic here
  }
}

@Component("RetryableSendEmailService")
public class RetryableSendEmailService extends SendEmailService {

  @Override
  public void send(String message) {
    ExceptionUtils.retryOnException(() -> super.send(message), 3, 5000);
  }
}

Idea :

Concern: I'm worried that this might violate the Liskov Substitution Principle (LSP) from the SOLID principles. The principle states:

Objects of a superclass shall be replaceable with objects of its subclasses without breaking the application. This requires subclasses to behave in the same way as their superclasses.

Questions:

  1. Does adding a retry mechanism in RetryableSendEmailService violate LSP since it changes the behavior of send by introducing retries?
  2. How can I refactor this code to adhere more closely to SOLID principles, especially LSP, while still achieving the desired functionality?

Upvotes: 0

Views: 75

Answers (1)

Peter Csala
Peter Csala

Reputation: 22829

To be able to use retry the following criteria group should be met:

  • The potentially introduced observable impact is acceptable
  • The operation can be redone without any irreversible side effect
  • The introduced complexity is negligible compared to the promised reliability

From the LSP persective the first two are important.

The potentially introduced observable impact is acceptable

With retry it might happen that you send out the exact same message multiple times due to some transient issue.

In other words, your at most once semantic (the code without retry) is changed to at least once (with the retry).

The operation can be redone without any irreversible side effect

As it was said in the previous point it might happen that the messaging provider sends out multiple copies of the same message. OR it has a dedicated de-duplication feature which helps to make the send operation act idempotently. You should be aware of this before you apply retry "blindly".

Upvotes: 2

Related Questions