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 :
- I created a new class
RetryableSendEmailService
that extends
SendEmailService
. - If a component needs retry logic, it can inject
RetryableSendEmailService
. If not, it can injectSendEmailService
.
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:
- Does adding a retry mechanism in
RetryableSendEmailService
violate
LSP since it changes the behavior of send by introducing retries? - How can I refactor this code to adhere more closely to
SOLID
principles, especiallyLSP
, while still achieving the desired
functionality?