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 extendsSendEmailService
. - 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 itssubclasses without breaking the application. This requires subclassesto behave in the same way as their superclasses.
Questions:
- Does adding a retry mechanism in
RetryableSendEmailService
violateLSP 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 desiredfunctionality?