๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

์ด๋ฒคํŠธ ์ฃผ๋„ํ˜• ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜: Kafka๋กœ ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์„ค๊ณ„ํ•˜๊ธฐ

okrestart 2024. 10. 23.

 

์ด๋ฒคํŠธ ์ฃผ๋„ํ˜• ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜ ๋Š” ํ˜„๋Œ€ ์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ์—์„œ ์ค‘์š”ํ•œ ํŒจ๋Ÿฌ๋‹ค์ž„์ž…๋‹ˆ๋‹ค. ์ด ์•„ํ‚คํ…์ฒ˜๋Š” ์„œ๋น„์Šค ๊ฐ„์˜ ๊ฒฐํ•ฉ๋„๋ฅผ ๋‚ฎ์ถ”๊ณ , ์‹œ์Šคํ…œ ํ™•์žฅ์„ฑ์„ ๊ทน๋Œ€ํ™”ํ•˜๋ฉฐ, ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•ด ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” Kafka ๋ฅผ ํ™œ์šฉํ•œ ์ด๋ฒคํŠธ ์ฃผ๋„ํ˜• ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์„ค๊ณ„, ์ด๋ฒคํŠธ ์†Œ์‹ฑ๊ณผ CQRS ํŒจํ„ด ์ ์šฉ, ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹  ์ฒ˜๋ฆฌ , ๊ทธ๋ฆฌ๊ณ  Kafka๋กœ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋ฅผ ํ™•์žฅํ•˜๋Š” ๋ฐฉ๋ฒ• ์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

 


 

 

1. Kafka๋ฅผ ํ™œ์šฉํ•œ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์„ค๊ณ„

Kafka๋Š” ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค ๋กœ์„œ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜์—์„œ ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ์„œ๋น„์Šค ๊ฐ„ ๋น„๋™๊ธฐ ํ†ต์‹  ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋ฉฐ, ๋ฐ์ดํ„ฐ๋ฅผ ํŒŒํ‹ฐ์…˜ ์œผ๋กœ ๋ถ„ํ• ํ•˜์—ฌ ๋Œ€๊ทœ๋ชจ ํŠธ๋ž˜ํ”ฝ ์ฒ˜๋ฆฌ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

Kafka ๊ธฐ๋ฐ˜ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์˜ˆ์‹œ

// ์ด๋ฒคํŠธ ํ”„๋กœ๋“€์„œ ์˜ˆ์‹œ
public class OrderService {

    @Autowired
    private KafkaTemplate<String, OrderEvent> kafkaTemplate;

    public void placeOrder(Order order) {
        // ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ๋กœ์ง
        OrderEvent event = new OrderEvent(order.getId(), "ORDER_PLACED");
        kafkaTemplate.send("order_events", event);
    }
}
// ์ด๋ฒคํŠธ ์ปจ์Šˆ๋จธ ์˜ˆ์‹œ
@KafkaListener(topics = "order_events", groupId = "payment_group")
public void handleOrderEvent(OrderEvent event) {
    if ("ORDER_PLACED".equals(event.getStatus())) {
        // ๊ฒฐ์ œ ์ฒ˜๋ฆฌ ๋กœ์ง
        processPayment(event.getOrderId());
    }
}

์„ค๋ช…:

  • KafkaTemplate ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฃผ๋ฌธ ์ด๋ฒคํŠธ๋ฅผ order_events ํ† ํ”ฝ์— ์ „์†กํ•ฉ๋‹ˆ๋‹ค.
  • @KafkaListener ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒฐ์ œ ์„œ๋น„์Šค๊ฐ€ ํ•ด๋‹น ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์‹ ํ•˜๊ณ  ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

 


 

2. ์ด๋ฒคํŠธ ์†Œ์‹ฑ๊ณผ CQRS ํŒจํ„ด ์ ์šฉ

์ด๋ฒคํŠธ ์†Œ์‹ฑ(Event Sourcing) ์€ ์‹œ์Šคํ…œ ์ƒํƒœ๋ฅผ ์ด๋ฒคํŠธ์˜ ์—ฐ์† ์œผ๋กœ ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. CQRS(Command Query Responsibility Segregation) ๋Š” ์“ฐ๊ธฐ ์ž‘์—… ๊ณผ ์ฝ๊ธฐ ์ž‘์—… ์„ ๋ถ„๋ฆฌํ•˜์—ฌ ์‹œ์Šคํ…œ์˜ ํ™•์žฅ์„ฑ๊ณผ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ์ด ๋‘ ํŒจํ„ด์€ Kafka๋ฅผ ํ™œ์šฉํ•œ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์—์„œ ํŠนํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฒคํŠธ ์†Œ์‹ฑ ์˜ˆ์‹œ

public class OrderEventStore {

    @Autowired
    private KafkaTemplate<String, OrderEvent> kafkaTemplate;

    public void saveEvent(OrderEvent event) {
        kafkaTemplate.send("order_event_store", event);
    }

    public List<OrderEvent> getOrderEvents(String orderId) {
        // Kafka์—์„œ ์ด๋ฒคํŠธ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋กœ์ง (์ƒ๋žต)
        return fetchOrderEventsFromKafka(orderId);
    }
}

CQRS ํŒจํ„ด ์ ์šฉ ์˜ˆ์‹œ

// Command: ์ฃผ๋ฌธ ์ƒ์„ฑ ์ฒ˜๋ฆฌ
public class OrderCommandService {
    public void createOrder(Order order) {
        // ์ฃผ๋ฌธ ์ƒ์„ฑ ๋กœ์ง
        orderRepository.save(order);
        eventStore.saveEvent(new OrderEvent(order.getId(), "ORDER_CREATED"));
    }
}

// Query: ์ฃผ๋ฌธ ์กฐํšŒ ์ฒ˜๋ฆฌ
public class OrderQueryService {
    public Order getOrderDetails(String orderId) {
        return orderRepository.findById(orderId);
    }
}

์„ค๋ช…:

  • ์ด๋ฒคํŠธ ์†Œ์‹ฑ ์€ Kafka๋ฅผ ํ†ตํ•ด ๋ชจ๋“  ์ด๋ฒคํŠธ๋ฅผ ์ €์žฅํ•˜์—ฌ ์ฃผ๋ฌธ ์ƒํƒœ๋ฅผ ๋ณต์›ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
  • CQRS ํŒจํ„ด ์€ ๋ช…๋ น(Command)๊ณผ ์กฐํšŒ(Query)๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ์„ ์œ ์ง€ํ•˜๋ฉด์„œ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•ฉ๋‹ˆ๋‹ค.

 


 

3. ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹  ์ฒ˜๋ฆฌ

๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ ์€ ๋‹ค์–‘ํ•œ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Kafka ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋น„๋™๊ธฐ ๋ฐฉ์‹์œผ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ฃผ๊ณ ๋ฐ›์•„ ์„œ๋น„์Šค ๊ฐ„ ๊ฒฐํ•ฉ๋„๋ฅผ ๋‚ฎ์ถœ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, Kafka๋Š” ๋Œ€์šฉ๋Ÿ‰ ํŠธ๋ž˜ํ”ฝ ์ฒ˜๋ฆฌ์— ์ ํ•ฉํ•˜๋ฏ€๋กœ, ์—ฌ๋Ÿฌ ์„œ๋น„์Šค๊ฐ€ ๋™์‹œ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹  ์ฒ˜๋ฆฌ ์˜ˆ์‹œ

@KafkaListener(topics = "inventory_events", groupId = "order_group")
public void handleInventoryUpdate(InventoryEvent event) {
    if ("INVENTORY_UPDATED".equals(event.getStatus())) {
        // ์žฌ๊ณ  ์—…๋ฐ์ดํŠธ ํ›„ ์ฃผ๋ฌธ ์ƒํƒœ ๋ณ€๊ฒฝ ๋กœ์ง
        updateOrderStatus(event.getOrderId(), "READY_FOR_SHIPMENT");
    }
}

์„ค๋ช…:

  • Kafka๋ฅผ ํ†ตํ•ด ์žฌ๊ณ  ์„œ๋น„์Šค ์™€ ์ฃผ๋ฌธ ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์žฌ๊ณ ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๋ฉด Kafka ์ด๋ฒคํŠธ๋ฅผ ํ†ตํ•ด ์ฃผ๋ฌธ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 


 

4. Kafka๋กœ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ํ™•์žฅํ•˜๊ธฐ

Kafka๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์˜ ํ™•์žฅ์„ฑ ์„ ๊ทน๋Œ€ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์„œ๋น„์Šค๋Š” ๋…๋ฆฝ์ ์œผ๋กœ ์Šค์ผ€์ผ ์—…/๋‹ค์šด ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ฐ ์„œ๋น„์Šค๋Š” ํ•„์š”์— ๋”ฐ๋ผ ๊ฐœ๋ณ„์ ์œผ๋กœ ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Kafka๋กœ ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์˜ˆ์‹œ

@Configuration
public class KafkaConfig {
    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConcurrency(3); // ๋™์‹œ์— 3๊ฐœ์˜ ์Šค๋ ˆ๋“œ๋กœ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
        return factory;
    }
}

์„ค๋ช…:

  • ConcurrentKafkaListenerContainerFactory ๋ฅผ ํ†ตํ•ด ๋™์‹œ์„ฑ ์„ ์„ค์ •ํ•˜๊ณ , ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ์—์„œ ๋™์‹œ์— ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋„๋ก ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 


 

์ด๋ฒคํŠธ ์ฃผ๋„ํ˜• ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ํŠธ๋ Œ๋“œ ๋ฐ ํ†ต๊ณ„

์ด๋ฒคํŠธ ์ฃผ๋„ํ˜• ์•„ํ‚คํ…์ฒ˜๋Š” ๋Œ€๊ทœ๋ชจ ์‹œ์Šคํ…œ ์—์„œ ํŠนํžˆ ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค. Gartner ์˜ ์—ฐ๊ตฌ์— ๋”ฐ๋ฅด๋ฉด, ์ด๋ฒคํŠธ ์ฃผ๋„ํ˜• ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ฑ„ํƒํ•œ ๊ธฐ์—…๋“ค์€ ์„œ๋น„์Šค ์ค‘๋‹จ ์‹œ๊ฐ„ ์„ 40% ์ด์ƒ ๊ฐ์†Œ ์‹œ์ผฐ์œผ๋ฉฐ, ์„œ๋น„์Šค ๊ฐ„์˜ ์˜์กด์„ฑ ์„ ์ค„์—ฌ ๊ฐœ๋ฐœ ์†๋„ ๋ฅผ ํ‰๊ท ์ ์œผ๋กœ 30% ํ–ฅ์ƒ์‹œ์ผฐ์Šต๋‹ˆ๋‹ค.

 


 

์ž์ฃผ ๋ฌป๋Š” ์งˆ๋ฌธ (FAQ)

Q1. Kafka๋ฅผ ์‚ฌ์šฉํ•œ ์ด๋ฒคํŠธ ์ฃผ๋„ํ˜• ์•„ํ‚คํ…์ฒ˜์˜ ์žฅ์ ์€ ๋ฌด์—‡์ธ๊ฐ€์š”?
A1. Kafka ๋Š” ์„œ๋น„์Šค ๊ฐ„ ๋น„๋™๊ธฐ ํ†ต์‹  ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋ฉฐ, ๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์„œ๋น„์Šค ๊ฐ„ ๊ฒฐํ•ฉ๋„ ๋ฅผ ๋‚ฎ์ถ”๊ณ , ํ™•์žฅ์„ฑ ์„ ๊ทน๋Œ€ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Q2. ์ด๋ฒคํŠธ ์†Œ์‹ฑ๊ณผ CQRS ํŒจํ„ด์€ ์–ด๋–ป๊ฒŒ ์ ์šฉํ•˜๋‚˜์š”?
A2. ์ด๋ฒคํŠธ ์†Œ์‹ฑ ์€ ์ƒํƒœ๋ฅผ ์ด๋ฒคํŠธ์˜ ์—ฐ์†์œผ๋กœ ์ €์žฅํ•˜๋ฉฐ, CQRS ๋Š” ์“ฐ๊ธฐ์™€ ์กฐํšŒ ์ž‘์—…์„ ๋ถ„๋ฆฌํ•˜์—ฌ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•˜๋Š” ํŒจํ„ด์ž…๋‹ˆ๋‹ค. Kafka๋Š” ์ด๋Ÿฌํ•œ ํŒจํ„ด์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐ ์ ํ•ฉํ•œ ๋ฉ”์‹œ์ง• ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค.

Q3. Kafka๋กœ ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ ์„ ์ฒ˜๋ฆฌํ•˜๋ฉด ์–ด๋–ค ์ด์ ์ด ์žˆ๋‚˜์š”?
A3. Kafka๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ ์„ ๋น„๋™๊ธฐ ๋ฐฉ์‹ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด ์„ฑ๋Šฅ๊ณผ ํ™•์žฅ์„ฑ์ด ๋†’์•„์ง‘๋‹ˆ๋‹ค. ๋˜ํ•œ, ํŠธ๋ž˜ํ”ฝ์ด ๋งŽ์€ ์‹œ์Šคํ…œ์—์„œ๋„ ์•ˆ์ •์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Q4. Kafka๋ฅผ ์‚ฌ์šฉํ•œ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์—์„œ ํ™•์žฅ์€ ์–ด๋–ป๊ฒŒ ์ด๋ฃจ์–ด์ง€๋‚˜์š”?
A4. Kafka๋Š” ํŒŒํ‹ฐ์…”๋‹ ๊ณผ ๋ณต์ œ ๋ฅผ ํ†ตํ•ด ๋Œ€๊ทœ๋ชจ ํŠธ๋ž˜ํ”ฝ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์„œ๋น„์Šค๋Š” ๋…๋ฆฝ์ ์œผ๋กœ ์Šค์ผ€์ผ ํ•  ์ˆ˜ ์žˆ์–ด ์‹œ์Šคํ…œ ์ „๋ฐ˜์˜ ํ™•์žฅ์„ฑ์„ ํฌ๊ฒŒ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Q5. ์ด๋ฒคํŠธ ์ฃผ๋„ํ˜• ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์—์„œ ์žฅ์•  ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ธ๊ฐ€์š”?
A5. Kafka๋Š” ๋‚ด์žฅ๋œ ์žฅ์•  ๋ณต๊ตฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜ ์„ ์ œ๊ณตํ•˜์—ฌ ์ด๋ฒคํŠธ ์†์‹ค ์—†์ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ณต๊ตฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ๊ฐ ์„œ๋น„์Šค๋Š” ๋…๋ฆฝ์ ์œผ๋กœ ์žฅ์•  ๋ณต๊ตฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์–ด, ์‹œ์Šคํ…œ ์ „๋ฐ˜์˜ ์‹ ๋ขฐ์„ฑ ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋Œ“๊ธ€