sms-messaging
Spring Boot microservices system for SMS dispatch. Provider abstraction via interface, random load-balanced factory selection, CRUD message persistence, and scheduled code generation.
sms-messaging-microservices simulates a production SMS dispatch system split across two services: a central Server and an SmsProvider. The Server exposes CRUD endpoints for messages and a provider selection layer that routes outbound SMS to one of two provider implementations (MTC and Alfa). Providers are interchangeable behind an ISmsProviderService interface, and the factory that selects between them is independent of both — adding a new provider means implementing one interface and registering it in the factory, nothing else. A scheduled job generates random 6-digit codes at a configured interval. The API is documented via Swagger UI.
Provider Abstraction
The core design decision is that the Server never knows which SMS provider it is calling. ISmsProviderService defines a single method: sendSms(SmsProviderRequest) returning Mono<String>. MtcSmsProviderService and AlfaSmsProviderService each implement this interface and handle the HTTP communication with their respective external APIs via WebClient.
SmsProviderFactory is a Spring service that holds references to both implementations. It selects one at runtime using a random integer. The Server injects the factory, calls getSmsProvider(), and calls sendSms on whatever comes back. The factory is the only place in the codebase that names a concrete provider class.
public interface ISmsProviderService {
Mono<String> sendSms(SmsProviderRequest request);
}
@Service
public class SmsProviderFactory {
private final MtcSmsProviderService mtcProvider;
private final AlfaSmsProviderService alfaProvider;
private final Random random = new Random();
public ISmsProviderService getSmsProvider() {
return switch (random.nextInt(3)) {
case 0 -> mtcProvider;
case 1 -> alfaProvider;
case 2 -> throw new BadRequestException("Provider error");
default -> null;
};
}
}Message Service
MessageService handles the full lifecycle of an SMS message record: create, read, update, delete, and paginated list. Each write operation runs inside a @Transactional boundary. Reads use the repository directly without an explicit transaction. findById delegates to orElseThrow with a ResourceNotFoundException, so the controller layer never receives an empty Optional.
The paginated getAllMessages(Pageable) passes the Spring Data Pageable directly to the repository's findAll — no manual offset/limit arithmetic. The controller constructs the Pageable from request parameters and passes it down.
@Service
public class MessageService {
@Transactional
public Message sendMessage(MessageRequest request) {
Message message = Message.builder()
.message(request.getMessage())
.phoneNumber(request.getPhoneNumber())
.build();
return messagesRepository.save(message);
}
public Message getMessageById(Long id) {
return messagesRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Message not found!"));
}
public Page<Message> getAllMessages(Pageable pageable) {
return messagesRepository.findAll(pageable);
}
}