Messaging with Spring

01.07.2026 | Muallif: Jaxongir a.k.a | Kategoriya: Spring Boot | 33 daqiqa o'qish

Messaging with Spring quyidagilarni qamrab oladi: Spring Events, @EventListener, ApplicationEventPublisher, @Async bilan eventlar, Spring AMQP / RabbitMQ, Spring Kafka, @KafkaListener, KafkaTemplate, Dead Letter Queue, va retry policies.


1. Messaging nima?

Messaging - application ichida yoki service’lar orasida ma’lumotni xabar/event ko‘rinishida uzatish.

Oddiy chaqiruv:

OrderService -> EmailService.send()

Messaging bilan:

OrderService -> OrderCreatedEvent -> Email listener

Ya’ni OrderService email yuborishni o‘zi bilmaydi. U faqat:

"Order yaratildi"

deb event chiqaradi. Kim eshitadi, nima qiladi - bu boshqa komponentlarning vazifasi.


2. Nega messaging kerak?

Masalan, order yaratildi:

public void createOrder() {
    orderRepository.save(order);

    emailService.sendEmail(order);
    smsService.sendSms(order);
    analyticsService.track(order);
    bonusService.addBonus(order);
}

Bu yomonlashib boradi:

  • OrderService juda ko‘p narsani bilib qoladi;

  • email xato bersa order creationga ta’sir qilishi mumkin;

  • kod tightly coupled bo‘ladi;

  • keyin yangi action qo‘shish qiyinlashadi.

Messaging bilan:

public void createOrder() {
    Order order = orderRepository.save(order);

    eventPublisher.publishEvent(new OrderCreatedEvent(order.getId()));
}

Keyin alohida listenerlar:

OrderCreatedEvent
 ├── Email listener
 ├── SMS listener
 ├── Analytics listener
 └── Bonus listener

Bu cleanroq.


3. Messaging turlari

Spring’da messagingni 3 darajada ko‘rish mumkin:

Tur

Qayerda ishlaydi?

Misol

In-process event

Bitta application ichida

Spring Events

Message broker

App va broker orasida

RabbitMQ

Distributed event streaming

Microservice/event stream

Kafka


4. Spring Events

Spring Events - bitta Spring Boot application ichida event chiqarish va eshitish mexanizmi.

Bu hali RabbitMQ yoki Kafka emas. Bu JVM ichida ishlaydi.

Oqim:

Service -> publishEvent() -> @EventListener

5. Oddiy Spring Event misol

Event class:

public record OrderCreatedEvent(
        Long orderId
) {
}

Service:

@Service
public class OrderService {

    private final OrderRepository orderRepository;
    private final ApplicationEventPublisher eventPublisher;

    public OrderService(
            OrderRepository orderRepository,
            ApplicationEventPublisher eventPublisher
    ) {
        this.orderRepository = orderRepository;
        this.eventPublisher = eventPublisher;
    }

    @Transactional
    public Long createOrder(CreateOrderRequest request) {
        Order order = new Order();
        order.setUserId(request.userId());

        Order saved = orderRepository.save(order);

        eventPublisher.publishEvent(
                new OrderCreatedEvent(saved.getId())
        );

        return saved.getId();
    }
}

Listener:

@Component
public class OrderCreatedListener {

    @EventListener
    public void handle(OrderCreatedEvent event) {
        System.out.println("Order created: " + event.orderId());
    }
}

Natija:

Order created: 15

6. Spring Events synchronous ishlaydi

Default holatda @EventListener synchronous.

Ya’ni:

OrderService.createOrder()
  -> publishEvent()
     -> listener ishlaydi
  -> keyin method davom etadi

Agar listener ichida exception bo‘lsa, asosiy flowga ta’sir qilishi mumkin.

Masalan:

@EventListener
public void handle(OrderCreatedEvent event) {
    throw new RuntimeException("Email failed");
}

Agar bu transaction ichida bo‘lsa, order transaction ham rollback bo‘lishi mumkin.

Shuning uchun eventni qachon va qanday ishlatishni tushunish kerak.


7. @TransactionalEventListener

Transaction bilan ishlaganda bu juda muhim.

Oddiy @EventListener event publish qilingan zahoti ishlaydi. Lekin transaction hali commit bo‘lmagan bo‘lishi mumkin.

@Transactional
public void createOrder() {
    Order order = orderRepository.save(new Order());

    eventPublisher.publishEvent(new OrderCreatedEvent(order.getId()));

    // transaction hali commit bo‘lmagan
}

Agar listener email yuborsa, lekin keyin transaction rollback bo‘lsa:

Email ketdi: order yaratildi
Lekin DB rollback bo‘ldi: order yo‘q

Bu noto‘g‘ri.

Yaxshiroq:

@Component
public class OrderCreatedListener {

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handle(OrderCreatedEvent event) {
        System.out.println("Transaction commit bo‘lgandan keyin ishlaydi");
    }
}

8. Transaction event phase’lari

Phase

Qachon ishlaydi?

BEFORE_COMMIT

Commitdan oldin

AFTER_COMMIT

Commitdan keyin

AFTER_ROLLBACK

Rollbackdan keyin

AFTER_COMPLETION

Commit yoki rollbackdan keyin

Ko‘p real holatda eng foydali variant:

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)

Masalan:

  • email yuborish;

  • SMS yuborish;

  • cache invalidation;

  • Kafka/RabbitMQ event yuborish;

  • audit notification.


9. @Async bilan Spring Events

Agar listener asosiy requestni ushlab turmasin desak, async qilish mumkin.

Avval async yoqiladi:

@EnableAsync
@SpringBootApplication
public class App {
}

Listener:

@Component
public class OrderNotificationListener {

    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void sendNotification(OrderCreatedEvent event) {
        System.out.println("Send notification for order: " + event.orderId());
    }
}

Endi listener boshqa threadda ishlaydi.


10. @Asyncda ehtiyot bo‘lish kerak

Async listener boshqa threadga o‘tadi. Shuning uchun:

  • transaction context o‘tmaydi;

  • lazy entity bilan ishlamang;

  • SecurityContext ham avtomatik o‘tmasligi mumkin;

  • event ichida entity emas, ID yuborish yaxshi.

Yomon:

public record OrderCreatedEvent(Order order) {
}

Yaxshi:

public record OrderCreatedEvent(Long orderId) {
}

Keyin listener ichida kerak bo‘lsa DBdan qayta o‘qiladi:

@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handle(OrderCreatedEvent event) {
    Order order = orderRepository.findById(event.orderId()).orElseThrow();
}

11. Spring Events qachon yetarli?

Spring Events yaxshi:

Holat

Sabab

Bitta monolith ichida

Broker kerak emas

Domain eventlar

Kodni loosely coupled qiladi

Cache invalidation

Commitdan keyin tozalash mumkin

Email/SMS trigger

Main flowdan ajratadi

Audit/event log

Service kodini tozalaydi

Lekin Spring Events yetarli emas:

Holat

Sabab

Microservice orasida event kerak

JVM ichida qolib ketadi

Retry/DLQ kerak

Spring Eventda built-in broker yo‘q

Event yo‘qolmasligi kerak

App crash bo‘lsa event yo‘qolishi mumkin

High throughput streaming

Kafka kerak bo‘lishi mumkin


12. RabbitMQ nima?

RabbitMQ - message broker.

Application message yuboradi, RabbitMQ uni qabul qiladi va consumerlarga yetkazadi.

Producer -> RabbitMQ -> Consumer

RabbitMQ odatda quyidagilar uchun yaxshi:

  • task queue;

  • email/SMS job;

  • background processing;

  • routing;

  • retry;

  • dead letter queue;

  • command/message delivery.


13. RabbitMQ asosiy tushunchalari

Tushuncha

Ma’nosi

Producer

Message yuboruvchi

Consumer

Message o‘quvchi

Queue

Message saqlanadigan navbat

Exchange

Message qaysi queuega borishini hal qiladi

Routing key

Message yo‘nalishi

Binding

Exchange va queue bog‘lanishi

DLQ

Failed message tushadigan queue


14. RabbitMQ oqimi

Producer
  ↓
Exchange
  ↓ routing key
Queue
  ↓
Consumer

Masalan:

order.created exchange
  routing key: email
  queue: email.queue

15. Spring AMQP dependency

Maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

Config:

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

16. RabbitMQ queue/exchange config

@Configuration
public class RabbitConfig {

    public static final String ORDER_EXCHANGE = "order.exchange";
    public static final String ORDER_CREATED_QUEUE = "order.created.queue";
    public static final String ORDER_CREATED_ROUTING_KEY = "order.created";

    @Bean
    public TopicExchange orderExchange() {
        return new TopicExchange(ORDER_EXCHANGE);
    }

    @Bean
    public Queue orderCreatedQueue() {
        return QueueBuilder
                .durable(ORDER_CREATED_QUEUE)
                .build();
    }

    @Bean
    public Binding orderCreatedBinding() {
        return BindingBuilder
                .bind(orderCreatedQueue())
                .to(orderExchange())
                .with(ORDER_CREATED_ROUTING_KEY);
    }
}

Bu yerda:

order.exchange -> order.created -> order.created.queue

17. RabbitMQ message yuborish

DTO:

public record OrderCreatedMessage(
        Long orderId,
        Long userId
) {
}

Producer:

@Service
public class OrderMessageProducer {

    private final RabbitTemplate rabbitTemplate;

    public OrderMessageProducer(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    public void sendOrderCreated(OrderCreatedMessage message) {
        rabbitTemplate.convertAndSend(
                RabbitConfig.ORDER_EXCHANGE,
                RabbitConfig.ORDER_CREATED_ROUTING_KEY,
                message
        );
    }
}

Service:

@Transactional
public Long createOrder(CreateOrderRequest request) {
    Order order = orderRepository.save(new Order(request.userId()));

    eventPublisher.publishEvent(
            new OrderCreatedEvent(order.getId(), order.getUserId())
    );

    return order.getId();
}

Commitdan keyin RabbitMQga yuborish:

@Component
public class OrderEventListener {

    private final OrderMessageProducer producer;

    public OrderEventListener(OrderMessageProducer producer) {
        this.producer = producer;
    }

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void onOrderCreated(OrderCreatedEvent event) {
        producer.sendOrderCreated(
                new OrderCreatedMessage(event.orderId(), event.userId())
        );
    }
}

18. RabbitMQ message qabul qilish

@Component
public class OrderCreatedConsumer {

    @RabbitListener(queues = RabbitConfig.ORDER_CREATED_QUEUE)
    public void consume(OrderCreatedMessage message) {
        System.out.println("Received order: " + message.orderId());
    }
}

Bu consumer order.created.queuedan message oladi.


19. RabbitMQ JSON converter

Default serialization bilan muammo bo‘lmasligi uchun JSON converter berish yaxshi:

@Configuration
public class RabbitJsonConfig {

    @Bean
    public MessageConverter jsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }
}

Endi RabbitTemplate va listener JSON bilan ishlaydi.


20. RabbitMQ retry

Agar consumer xato bersa, message qayta ishlanishi kerak bo‘lishi mumkin.

Config:

spring:
  rabbitmq:
    listener:
      simple:
        retry:
          enabled: true
          initial-interval: 2s
          max-attempts: 3
          multiplier: 2
          max-interval: 10s

Ma’nosi:

1-urinish
2 sekund kutish
2-urinish
4 sekund kutish
3-urinish
xato bo‘lsa -> reject/DLQ

21. Dead Letter Queue nima?

DLQ - Dead Letter Queue.

Agar message bir necha marta qayta ishlansa ham xato bersa, uni yo‘qotib yubormaslik kerak. DLQga yuboramiz.

order.created.queue
  ↓ error after retries
order.created.dlq

DLQ kerak:

  • failed message’ni keyin tekshirish;

  • bug fixdan keyin qayta ishlash;

  • monitoring/alert qilish;

  • data yo‘qolmasligi uchun.


22. RabbitMQ DLQ config

@Configuration
public class RabbitDlqConfig {

    public static final String ORDER_EXCHANGE = "order.exchange";
    public static final String ORDER_CREATED_QUEUE = "order.created.queue";
    public static final String ORDER_CREATED_DLQ = "order.created.dlq";
    public static final String DLX = "order.dlx";

    @Bean
    public DirectExchange deadLetterExchange() {
        return new DirectExchange(DLX);
    }

    @Bean
    public Queue orderCreatedQueue() {
        return QueueBuilder
                .durable(ORDER_CREATED_QUEUE)
                .deadLetterExchange(DLX)
                .deadLetterRoutingKey("order.created.failed")
                .build();
    }

    @Bean
    public Queue orderCreatedDlq() {
        return QueueBuilder
                .durable(ORDER_CREATED_DLQ)
                .build();
    }

    @Bean
    public Binding orderCreatedDlqBinding() {
        return BindingBuilder
                .bind(orderCreatedDlq())
                .to(deadLetterExchange())
                .with("order.created.failed");
    }
}

Agar message reject qilinsa, u order.created.dlqga tushadi.


23. Kafka nima?

Kafka - distributed event streaming platform.

RabbitMQ ko‘proq queue/message broker sifatida ishlatiladi. Kafka esa eventlarni log ko‘rinishida saqlaydi.

Kafka oqimi:

Producer -> Topic partition -> Consumer group

Kafka odatda yaxshi:

  • event streaming;

  • analytics;

  • audit eventlar;

  • microservice integration;

  • high throughput;

  • event replay;

  • real-time data pipeline.


24. Kafka asosiy tushunchalari

Tushuncha

Ma’nosi

Topic

Eventlar nomlangan oqimi

Partition

Topic ichidagi bo‘lim

Offset

Consumer qayergacha o‘qiganini bildiradi

Producer

Event yuboruvchi

Consumer

Event o‘quvchi

Consumer group

Birga ishlaydigan consumerlar guruhi

Broker

Kafka server

Retention

Eventlar qancha vaqt saqlanishi


25. Kafka va RabbitMQ farqi qisqa

Mezон

RabbitMQ

Kafka

Model

Queue/message broker

Distributed log/event stream

Message saqlash

Odatda consume bo‘lgach yo‘qoladi

Retention bo‘yicha saqlanadi

Replay

Qiyinroq

Kuchli

Routing

Juda kuchli

Oddiyroq

Throughput

Yaxshi

Juda yuqori

Use case

Task queue, routing, job

Event stream, analytics, microservices

Consumer

Queue’dan message oladi

Offset bo‘yicha o‘qiydi

Oddiy qoida:

Background job / murakkab routing -> RabbitMQ
Event stream / replay / high throughput -> Kafka

26. Spring Kafka dependency

Maven:

<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>

Spring Boot bilan odatda config:

spring:
  kafka:
    bootstrap-servers: localhost:9092
    consumer:
      group-id: order-service
      auto-offset-reset: earliest
    producer:
      acks: all

27. Kafka message DTO

public record OrderCreatedEventMessage(
        Long orderId,
        Long userId,
        Instant createdAt
) {
}

JSON serializer config:

spring:
  kafka:
    producer:
      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
    consumer:
      value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      properties:
        spring.json.trusted.packages: "com.example"

28. KafkaTemplate bilan event yuborish

@Service
public class OrderKafkaProducer {

    private final KafkaTemplate<String, OrderCreatedEventMessage> kafkaTemplate;

    public OrderKafkaProducer(
            KafkaTemplate<String, OrderCreatedEventMessage> kafkaTemplate
    ) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void send(OrderCreatedEventMessage message) {
        kafkaTemplate.send(
                "order.created",
                message.orderId().toString(),
                message
        );
    }
}

Bu yerda key sifatida orderId ishlatildi.

Nega key muhim?

Bir xil key -> odatda bir xil partition

Bu order bo‘yicha event tartibini saqlashga yordam beradi.


29. @KafkaListener

@Component
public class OrderCreatedKafkaConsumer {

    @KafkaListener(
            topics = "order.created",
            groupId = "notification-service"
    )
    public void consume(OrderCreatedEventMessage message) {
        System.out.println("Kafka event: " + message.orderId());
    }
}

Agar consumer group bir xil bo‘lsa:

groupId = notification-service

message group ichidagi bitta consumer instancega boradi.

Agar boshqa service boshqa group bilan o‘qisa:

groupId = analytics-service

u ham o‘sha eventni alohida oladi.


30. Kafka consumer group tushunchasi

Topic:

order.created

Consumer groups:

notification-service
analytics-service
bonus-service

Har bir group eventlarni mustaqil o‘qiydi.

order.created event
 ├── notification-service oladi
 ├── analytics-service oladi
 └── bonus-service oladi

Lekin bitta group ichida bir event odatda bitta consumer instancega beriladi.


31. Kafka retry va DLT

Kafka’da failed message uchun retry va DLT ishlatiladi.

Spring Kafka’da:

@RetryableTopic(
        attempts = "3",
        backoff = @Backoff(delay = 2000, multiplier = 2),
        dltTopicSuffix = ".dlt"
)
@KafkaListener(topics = "order.created", groupId = "notification-service")
public void consume(OrderCreatedEventMessage message) {
    if (message.orderId() == null) {
        throw new IllegalArgumentException("orderId required");
    }

    System.out.println("Process order: " + message.orderId());
}

Agar 3 marta xato bo‘lsa:

order.created.dlt

topicga tushadi.

DLT listener:

@DltHandler
public void handleDlt(OrderCreatedEventMessage message) {
    System.out.println("DLT message: " + message);
}

32. Idempotency nima?

Messagingda eng muhim tushunchalardan biri - idempotency.

Message bir martadan ko‘proq kelishi mumkin.

OrderCreatedEvent(orderId=10)
OrderCreatedEvent(orderId=10)

Consumer ikki marta ishlasa, muammo chiqishi mumkin:

bonus ikki marta berildi
email ikki marta ketdi
payment ikki marta qilindi

Shuning uchun consumer idempotent bo‘lishi kerak.


33. Idempotent consumer misol

Eventda unique eventId bo‘lsin:

public record OrderCreatedMessage(
        UUID eventId,
        Long orderId,
        Long userId
) {
}

Processed event table:

@Entity
public class ProcessedEvent {

    @Id
    private UUID eventId;

    private LocalDateTime processedAt;
}

Consumer:

@Transactional
public void consume(OrderCreatedMessage message) {
    if (processedEventRepository.existsById(message.eventId())) {
        return;
    }

    bonusService.addBonus(message.userId());

    processedEventRepository.save(
            new ProcessedEvent(message.eventId(), LocalDateTime.now())
    );
}

Endi bir xil event qayta kelsa, ikkinchi marta ishlamaydi.


34. Messaging va transaction muammosi

Eng katta muammo:

DBga order saqlandi
Kafka/RabbitMQga event yuborish kerak

Agar:

DB commit bo‘ldi
Kafka send xato bo‘ldi

event yo‘qoladi.

Aksincha:

Kafka event ketdi
DB rollback bo‘ldi

noto‘g‘ri event ketdi.

Buni oddiy @Transactional bilan to‘liq hal qilish qiyin.


35. Transactional Outbox Pattern

Production microservice’da eng yaxshi patternlardan biri - Transactional Outbox.

Oqim:

Bitta DB transaction ichida:
1. Order save
2. OutboxEvent save

Alohida worker:
3. OutboxEvent o‘qiydi
4. Kafka/RabbitMQga yuboradi
5. SENT deb belgilaydi

Service:

@Transactional
public Long createOrder(CreateOrderRequest request) {
    Order order = orderRepository.save(new Order(request.userId()));

    OutboxEvent event = new OutboxEvent(
            UUID.randomUUID(),
            "ORDER_CREATED",
            order.getId().toString(),
            """
            {"orderId": %d, "userId": %d}
            """.formatted(order.getId(), order.getUserId())
    );

    outboxRepository.save(event);

    return order.getId();
}

Bu kafolatlaydi:

Order va event record birga commit bo‘ladi

Keyin worker eventni brokerga yuboradi.


36. Outbox entity

@Entity
public class OutboxEvent {

    @Id
    private UUID id;

    private String type;

    private String aggregateId;

    @Column(columnDefinition = "TEXT")
    private String payload;

    @Enumerated(EnumType.STRING)
    private OutboxStatus status;

    private LocalDateTime createdAt;

    private LocalDateTime sentAt;
}

Status:

public enum OutboxStatus {
    NEW,
    SENT,
    FAILED
}

Worker:

@Scheduled(fixedDelay = 5000)
@Transactional
public void publishOutboxEvents() {
    List<OutboxEvent> events =
            outboxRepository.findTop100ByStatusOrderByCreatedAt(
                    OutboxStatus.NEW
            );

    for (OutboxEvent event : events) {
        kafkaTemplate.send("order.events", event.getAggregateId(), event.getPayload());

        event.markAsSent();
    }
}

Bu sodda variant. Katta sistemalarda Debezium CDC bilan outbox ishlatiladi.


37. Message ordering

Messagingda tartib muhim bo‘lishi mumkin.

Masalan:

ORDER_CREATED
ORDER_PAID
ORDER_CANCELLED

Agar tartib buzilsa, consumer noto‘g‘ri ishlaydi.

Kafka’da ordering odatda partition ichida kafolatlanadi.

Shuning uchun key muhim:

kafkaTemplate.send("order.events", orderId.toString(), message);

Bir xil orderId bir xil partitionga tushadi va tartib saqlanishi osonroq bo‘ladi.

RabbitMQ’da ham queue ichida tartib bor, lekin retry, requeue, multiple consumer bo‘lsa tartib murakkablashadi.


38. At-least-once delivery

Ko‘p messaging systemlarda default amaliy kafolat:

At-least-once delivery

Ya’ni message kamida bir marta yetkaziladi. Lekin ba’zan ikki marta ham kelishi mumkin.

Shuning uchun:

Consumer idempotent bo‘lishi shart

Bu juda muhim production qoidasi.


39. Poison message

Poison message - doim xato beradigan message.

Masalan:

{
  "orderId": null
}

Consumer har safar xato beradi.

Agar retry cheksiz bo‘lsa:

message -> error -> retry -> error -> retry -> ...

Queue/topic tiqilib qolishi mumkin.

Yechim:

  • max retry;

  • DLQ/DLT;

  • validation;

  • alert;

  • manual replay.


40. Messagingda monitoring

Productionda quyidagilar kuzatiladi:

Metric

Nima uchun kerak

Queue depth

Queue’da qancha message kutyapti

Consumer lag

Kafka consumer qanchalik ortda

Retry count

Xatolar ko‘payyaptimi

DLQ size

Failed message bor-yo‘qligi

Processing time

Consumer sekinmi

Error rate

Xato foizi

Broker availability

RabbitMQ/Kafka ishlayaptimi

Kafka’da ayniqsa consumer lag muhim:

Producer tez yozmoqda
Consumer sekin o‘qimoqda
Lag oshib boradi

41. Messagingda keng tarqalgan xatolar

Xato

Oqibat

Entityni messagega solish

Lazy/loading/schema muammo

Idempotency yo‘q

Duplicate processing

Retry cheksiz

Poison message systemani tiqadi

DLQ yo‘q

Failed message yo‘qoladi yoki loop qiladi

Transactiondan oldin event yuborish

DB rollback bo‘lsa noto‘g‘ri event

Broker sendni DB transaction ichida noto‘g‘ri boshqarish

Inconsistency

Message versioning yo‘q

Consumer eski/yangi formatni tushunmaydi

Monitoring yo‘q

Queue tiqilib qolganini kech bilasiz


42. Message versioning

Vaqt o‘tishi bilan message formati o‘zgaradi.

Oldin:

{
  "orderId": 10,
  "userId": 5
}

Keyin:

{
  "eventVersion": 2,
  "orderId": 10,
  "userId": 5,
  "totalAmount": 150000
}

Yaxshi amaliyot:

public record OrderCreatedMessage(
        int eventVersion,
        UUID eventId,
        Long orderId,
        Long userId,
        BigDecimal totalAmount,
        Instant occurredAt
) {
}

Consumer eski fieldlar yo‘q bo‘lsa ham yiqilib tushmasligi kerak.


43. Real architecture misol

Order service:

POST /orders
  ↓
OrderService.create()
  ↓
DB: orders insert
DB: outbox_events insert
  ↓
commit

Outbox publisher:

outbox_events NEW
  ↓
Kafka: order.created
  ↓
outbox_events SENT

Consumers:

notification-service -> email/sms
analytics-service -> reporting
bonus-service -> user bonus
warehouse-service -> stock reservation

Bu architecture’da OrderService boshqa servicelarni bevosita bilmaydi.


44. Spring Events vs RabbitMQ vs Kafka

Mezon

Spring Events

RabbitMQ

Kafka

Scope

Bitta app ichida

Service/app orasida

Distributed event stream

Persistence

Yo‘q

Queue’da saqlaydi

Log retention

Retry/DLQ

Qo‘lda

Kuchli

Kuchli

Throughput

App ichida

Yaxshi

Juda yuqori

Replay

Yo‘q

Cheklangan

Kuchli

Routing

Oddiy

Juda kuchli

Topic/partition

Use case

Monolith ichki event

Background job, routing

Event streaming, microservices


45. Qaysi birini tanlash kerak?

Spring Events tanlang

Agar:

Bitta Spring Boot app ichida decoupling kerak

Masalan:

  • order created -> cache clear;

  • user registered -> welcome email trigger;

  • product updated -> local listener.

RabbitMQ tanlang

Agar:

Task queue, retry, DLQ, routing kerak

Masalan:

  • email queue;

  • SMS queue;

  • image processing;

  • invoice generation;

  • background jobs.

Kafka tanlang

Agar:

Event stream, replay, ko‘p consumer group, high throughput kerak

Masalan:

  • order events;

  • payment events;

  • analytics pipeline;

  • audit log;

  • microservice integration;

  • CDC/outbox events.


46. Best practice

1. Message ichiga entity solmang

Yomon:

public record OrderCreatedMessage(Order order) {
}

Yaxshi:

public record OrderCreatedMessage(
        UUID eventId,
        Long orderId,
        Long userId,
        Instant occurredAt
) {
}

2. Har message’da eventId bo‘lsin

Duplicate processingdan himoya qiladi.

UUID eventId

3. Consumer idempotent bo‘lsin

Message ikki marta kelsa ham, natija buzilmasin.


4. Retry limit va DLQ shart

Cheksiz retry productionda xavfli.


5. Transaction commitdan keyin event yuboring

Monolith ichida:

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)

Microservice’da:

Transactional Outbox

6. Message schema/versioningni oldindan o‘ylang

Consumerlar har doim bir vaqtda deploy bo‘lmaydi.


47. Interview savollar

Savol 1: Spring Events nima?

Javob:

Spring Events bitta Spring application ichida event publish qilish va listenerlar orqali qabul qilish mexanizmi. ApplicationEventPublisher event chiqaradi, @EventListener yoki @TransactionalEventListener uni eshitadi.


Savol 2: @EventListener va @TransactionalEventListener farqi nima?

Javob:

@EventListener event publish qilingan zahoti ishlaydi. @TransactionalEventListener esa transaction phase bilan bog‘lanadi, masalan AFTER_COMMIT orqali faqat DB commit bo‘lgandan keyin ishlaydi.


Savol 3: RabbitMQ qachon ishlatiladi?

Javob:

RabbitMQ background job, task queue, murakkab routing, retry va DLQ kerak bo‘lgan joylarda yaxshi. Masalan email, SMS, invoice generation yoki image processing queue.


Savol 4: Kafka qachon ishlatiladi?

Javob:

Kafka high throughput event streaming, event replay, ko‘p consumer group, analytics pipeline va microservice integration uchun yaxshi. Kafka eventlarni retention bo‘yicha saqlaydi va consumerlar offset orqali o‘qiydi.


Savol 5: DLQ/DLT nima?

Javob:

Dead Letter Queue yoki Dead Letter Topic - retrylardan keyin ham ishlanmagan failed message tushadigan joy. U message yo‘qolmasligi, keyin tahlil qilish va qayta ishlash uchun kerak.


Savol 6: Idempotent consumer nima?

Javob:

Idempotent consumer bir xil message ikki yoki undan ko‘p marta kelsa ham natijani bir marta bajarilgandek saqlaydi. Masalan eventIdni processed table’da saqlab, duplicate eventlarni o‘tkazib yuboradi.


Savol 7: Transactional Outbox nima?

Javob:

Transactional Outbox - DB update va event yaratishni bitta transaction ichida bajarish patterni. Service asosiy entity bilan birga outbox tablega event yozadi, keyin alohida worker yoki CDC uni Kafka/RabbitMQga yuboradi. Bu DB commit va event publish orasidagi inconsistency muammosini kamaytiradi.


48. Qisqa xulosa

Mavzu

Asosiy fikr

Spring Events

Bitta app ichida event-based decoupling

ApplicationEventPublisher

Event chiqaradi

@EventListener

Eventni darhol eshitadi

@TransactionalEventListener

Transaction phase bo‘yicha ishlaydi

@Async

Listenerni boshqa threadda ishlatadi

RabbitMQ

Queue, routing, retry, DLQ uchun kuchli

Kafka

Event stream, replay, consumer group, high throughput

DLQ/DLT

Failed message’larni saqlash

Idempotency

Duplicate message’dan himoya

Outbox

DB transaction va event publish consistency uchun

Eng muhim gap:

Messaging - service’larni bo‘sh bog‘lash uchun kuchli vosita. Lekin retry, DLQ, idempotency va transaction consistency bo‘lmasa, productionda katta muammo chiqaradi.