На каких технологиях был реализован модуль уведомлений
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Модуль уведомлений: технологии и реализация
Это вопрос о конкретной системе (видимо, интервьюируемого проекта). Я дам стандартный ответ на уровне expert'а, который покрывает современные подходы и лучшие практики.
Типовая архитектура системы уведомлений
Полнофункциональный модуль уведомлений обычно состоит из:
┌─────────────────────────────────────────┐
│ APPLICATION LAYER │
│ (Сервис отправки уведомлений) │
└────────────────────┬────────────────────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
┌────────────┐ ┌────────┐ ┌─────────┐
│ Message │ │ Routing│ │ Template│
│ Broker │ │ Engine │ │ Engine │
│ (RabbitMQ)│ │ │ │ │
└────────────┘ └────────┘ └─────────┘
│
┌────┴──────┬──────────┬──────────┐
▼ ▼ ▼ ▼
Email Push Notif SMS Webhook
Handler Handler Handler Handler
Основные компоненты
1. Message Broker
Обычно используются:
// ✅ RabbitMQ — популярный выбор
// Достоинства:
// - Надёжная доставка (acknowledgements)
// - Маршрутизация (exchanges, queues)
// - Персистентность
RabbitTemplate template = rabbitTemplate(connectionFactory);
template.convertAndSend(
"notifications.exchange",
"email.routing.key",
emailNotification
);
// ✅ Apache Kafka — для больших объёмов
// Достоинства:
// - Горизонтальная масштабируемость
// - Наличие истории (retention)
// - Высокий throughput
kafkaTemplate.send(
"notifications-topic",
emailNotification
);
// ✅ Redis Streams — для быстрого перетоков
redisTemplate.opsForStream().add(
"notifications-stream",
Map.of("id", notification.getId())
);
2. Сервис уведомлений (Application Layer)
@Service
public class NotificationService {
private final NotificationRepository notificationRepo;
private final MessageBroker messageBroker;
private final NotificationTemplateEngine templateEngine;
@Transactional
public void sendNotification(NotificationRequest request) {
// 1. Сохраняем в БД (для истории и retry)
Notification notification = new Notification();
notification.setUserId(request.getUserId());
notification.setType(request.getType());
notification.setStatus(NotificationStatus.PENDING);
notification.setCreatedAt(Instant.now(ZoneId.of("UTC")));
Notification saved = notificationRepo.save(notification);
// 2. Отправляем в message broker
messageBroker.publish(
"notifications.exchange",
request.getType().getRoutingKey(),
new NotificationEvent(saved)
);
}
@Transactional
public void markAsSent(UUID notificationId) {
Notification notification = notificationRepo.findById(notificationId)
.orElseThrow();
notification.setStatus(NotificationStatus.SENT);
notification.setSentAt(Instant.now(ZoneId.of("UTC")));
notificationRepo.save(notification);
}
}
3. Template Engine
// ✅ FreeMarker — для шаблонов
@Service
public class TemplateEngine {
private final Configuration freemarkerConfig;
public String render(String templateName, Map<String, Object> data)
throws IOException, TemplateException {
Template template = freemarkerConfig.getTemplate(templateName);
StringWriter output = new StringWriter();
template.process(data, output);
return output.toString();
}
}
// Шаблон: templates/email_welcome.ftl
// <html>
// <body>
// <h1>Привет, ${userName}!</h1>
// <p>Код подтверждения: ${confirmationCode}</p>
// </body>
// </html>
// ✅ Thymeleaf — альтернатива
@Autowired
private ThymeleafTemplateEngine thymeleafEngine;
4. Email Handler
@Service
public class EmailNotificationHandler {
private final JavaMailSender mailSender;
private final TemplateEngine templateEngine;
@RabbitListener(queues = "email-notifications-queue")
public void handleEmailNotification(NotificationEvent event) {
try {
String htmlContent = templateEngine.render(
"email_" + event.getType().toString().toLowerCase() + ".ftl",
event.getContext()
);
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(event.getRecipient());
message.setSubject(event.getSubject());
message.setText(htmlContent);
mailSender.send(message);
// Обновляем статус
notificationService.markAsSent(event.getId());
} catch (Exception e) {
handleError(event, e);
}
}
}
// Или с Spring Mail Mime Message (для HTML)
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(
mimeMessage,
true, // multipart
"UTF-8"
);
helper.setTo(event.getRecipient());
helper.setSubject(event.getSubject());
helper.setText(htmlContent, true); // true = HTML
mailSender.send(mimeMessage);
5. Push Notification Handler (Firebase)
@Service
public class PushNotificationHandler {
private final FirebaseMessaging firebaseMessaging;
@RabbitListener(queues = "push-notifications-queue")
public void handlePushNotification(NotificationEvent event) {
Notification notification = Notification.builder()
.setTitle(event.getTitle())
.setBody(event.getBody())
.putData("notification_id", event.getId().toString())
.putData("click_action", event.getClickAction())
.build();
// Отправляем конкретному девайсу по токену
String deviceToken = event.getDeviceToken();
try {
String response = firebaseMessaging.send(
Message.builder()
.setToken(deviceToken)
.setNotification(notification)
.build()
);
logger.info("Push sent: " + response);
notificationService.markAsSent(event.getId());
} catch (FirebaseMessagingException e) {
// Токен невалиден, удаляем
if (e.getMessagingErrorCode() == MessagingErrorCode.THIRD_PARTY_AUTH_ERROR) {
userService.removeDeviceToken(event.getUserId());
}
}
}
}
6. SMS Handler (Twilio)
@Service
public class SmsNotificationHandler {
private final TwilioRestClient twilio;
@RabbitListener(queues = "sms-notifications-queue")
public void handleSmsNotification(NotificationEvent event) {
try {
Message message = Message.creator(
new PhoneNumber("+" + event.getPhoneNumber()), // To
new PhoneNumber("+1234567890"), // From
event.getMessage()
)
.create();
logger.info("SMS sent: " + message.getSid());
notificationService.markAsSent(event.getId());
} catch (TwilioRestException e) {
logger.error("SMS failed: " + e.getErrorMessage());
handleRetry(event);
}
}
}
7. Webhook Handler (для интеграций)
@Service
public class WebhookNotificationHandler {
private final RestTemplate restTemplate;
@RabbitListener(queues = "webhook-notifications-queue")
public void handleWebhookNotification(NotificationEvent event) {
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("X-Webhook-Secret", generateSignature(event));
HttpEntity<WebhookPayload> request = new HttpEntity<>(
new WebhookPayload(event),
headers
);
ResponseEntity<String> response = restTemplate.postForEntity(
event.getWebhookUrl(),
request,
String.class
);
if (response.getStatusCode().is2xxSuccessful()) {
notificationService.markAsSent(event.getId());
} else {
handleRetry(event);
}
} catch (Exception e) {
handleError(event, e);
}
}
}
Retry механизм
@Service
public class NotificationRetryService {
// Переотправляем не доставленные уведомления
@Scheduled(fixedRate = 60000) // Каждую минуту
public void retryFailedNotifications() {
List<Notification> failed = notificationRepo
.findByStatusAndCreatedAtAfter(
NotificationStatus.FAILED,
Instant.now(ZoneId.of("UTC")).minus(Duration.ofHours(24))
);
for (Notification notification : failed) {
if (notification.getRetryCount() < 3) {
notification.setRetryCount(notification.getRetryCount() + 1);
messageBroker.republish(notification);
} else {
notification.setStatus(NotificationStatus.PERMANENTLY_FAILED);
notificationRepo.save(notification);
}
}
}
}
Мониторинг и логирование
@Aspect
@Service
public class NotificationMonitoring {
@Around("@annotation(Monitored)")
public Object monitor(ProceedingJoinPoint point) throws Throwable {
long start = System.currentTimeMillis();
String method = point.getSignature().getName();
try {
Object result = point.proceed();
long duration = System.currentTimeMillis() - start;
logger.info("Notification {} succeeded in {}ms", method, duration);
metrics.recordSuccess(method, duration);
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - start;
logger.error("Notification {} failed after {}ms: {}",
method, duration, e.getMessage());
metrics.recordFailure(method, duration, e);
throw e;
}
}
}
Конфигурация RabbitMQ
@Configuration
public class NotificationQueueConfig {
// Exchanges
@Bean
public TopicExchange notificationsExchange() {
return new TopicExchange("notifications.exchange", true, false);
}
// Queues
@Bean
public Queue emailQueue() {
return new Queue("email-notifications-queue", true);
}
@Bean
public Queue pushQueue() {
return new Queue("push-notifications-queue", true);
}
// Bindings
@Bean
public Binding emailBinding(Queue emailQueue, TopicExchange exchange) {
return BindingBuilder.bind(emailQueue)
.to(exchange)
.with("email.*");
}
}
Типовая стек технологий
| Слой | Технология | Причина |
|---|---|---|
| Message Broker | RabbitMQ / Kafka | Надёжность, масштабируемость |
| Application | Spring Boot | Enterprise стандарт |
| Templates | FreeMarker / Thymeleaf | Гибкость, переиспользование |
| Spring Mail / SendGrid / AWS SES | Надёжность, масштабируемость | |
| Push | Firebase / OneSignal | Кросс-платформа |
| SMS | Twilio / Nexmo | Глобальное покрытие |
| Database | PostgreSQL | История уведомлений |
| Async | @Async / CompletableFuture | Неблокирующая обработка |
| Scheduling | @Scheduled / Quartz | Периодические проверки |
| Monitoring | Prometheus / Grafana | Видимость |
Практические выводы
✅ Message Broker — основа (асинхронность) ✅ Разные каналы — Email, Push, SMS, Webhook ✅ Template Engine — для гибких сообщений ✅ Retry механизм — для надёжности ✅ Мониторинг — для видимости ✅ Persistence — для истории и отладки
Правильный ответ на интервью: "Я бы реализовал модуль уведомлений с использованием RabbitMQ как message broker для асинхронной обработки, Spring Boot для приложения, FreeMarker для шаблонов, и отдельные обработчики для Email (Spring Mail), Push (Firebase), SMS (Twilio) и Webhook. Всё с retry механизмом, сохранением истории в PostgreSQL и мониторингом через Prometheus."