๐Ÿ”„ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์™€ Go: ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜์˜ ๋น„๋ฐ€์„ ํ’€๋‹ค

sangjinsuยท2025๋…„ 4์›” 30์ผ

Microservices and Go: Unlocking the Secrets of Event-Driven Architecture

๐Ÿ“š ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์™€ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜ ์†Œ๊ฐœ

ํ˜„๋Œ€ ์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ์€ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์ค‘์‹ฌ์œผ๋กœ ๋น ๋ฅด๊ฒŒ ๋ณ€ํ™”ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋Š” ํ•˜๋‚˜์˜ ๊ฑฐ๋Œ€ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ž‘๊ณ  ๋…๋ฆฝ์ ์œผ๋กœ ๋ฐฐํฌ ๊ฐ€๋Šฅํ•œ ์„œ๋น„์Šค๋“ค๋กœ ๋ถ„ํ• ํ•˜๋Š” ๋ชจ๋“ˆํ™” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์„œ๋น„์Šค๋“ค์€ ๋น„๋™๊ธฐ์ ์œผ๋กœ ํ†ต์‹ ํ•˜๋ฉฐ, ๊ทธ ๊ธฐ๋ฐ˜์—๋Š” ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜(Event-Driven Architecture, EDA)๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

EDA๋Š” ๋†’์€ ํ™•์žฅ์„ฑ, ์œ ์—ฐ์„ฑ, ๊ทธ๋ฆฌ๊ณ  ์„œ๋น„์Šค ๊ฐ„ ๊ฒฐํ•ฉ๋„ ๊ฐ์†Œ๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.


๐Ÿ’ก ์™œ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์— Go ๋ฅผ ์‚ฌ์šฉํ• ๊นŒ?

โšก ๋น ๋ฅด๊ณ  ๊ฐ€๋ฒผ์›€

Go๋Š” ์ปดํŒŒ์ผ ์–ธ์–ด๋กœ, ๋†’์€ ์„ฑ๋Šฅ๊ณผ ๋‚ฎ์€ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ์ž๋ž‘ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ฆ ๋‚ด์žฅ๋œ ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ

Goroutine๊ณผ Channel์„ ํ™œ์šฉํ•ด ๊ณ ๋ถ€ํ•˜ ์š”์ฒญ๋„ ์†์‰ฝ๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”ฅ ๋ฏธ๋‹ˆ๋ฉ€ํ•˜๊ณ  ํšจ์œจ์ ์ธ ๊ฐœ๋ฐœ

ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ž‘๊ณ  ๋‹จ์ˆœํ•˜์—ฌ, ๊ฐœ๋ฐœ ์†๋„์™€ ๋ฐฐํฌ ์†๋„ ๋ชจ๋‘ ํ–ฅ์ƒ๋ฉ๋‹ˆ๋‹ค.


๐ŸŽฏ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜(EDA)๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜(Event-Driven Architecture, EDA)๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ์„ค๊ณ„ ํŒจํ„ด์ž…๋‹ˆ๋‹ค.

  • ๐Ÿญ Producer(์ƒ์‚ฐ์ž): ์ƒํƒœ ๋ณ€ํ™”๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ด๋ฒคํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿ‘‚ Consumer(์†Œ๋น„์ž): ํ•ด๋‹น ์ด๋ฒคํŠธ๋ฅผ ์ฒญ์ทจ(listen)ํ•˜๊ณ , ๊ทธ์— ๋”ฐ๋ผ ์ ์ ˆํžˆ ๋ฐ˜์‘ํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿ”— Broker(์ค‘๊ฐœ์ž): Kafka, RabbitMQ ๊ฐ™์€ ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค๊ฐ€ ์ด๋ฒคํŠธ ํ๋ฆ„์„ ์ค‘๊ฐœํ•ฉ๋‹ˆ๋‹ค.

๐Ÿงฉ EDA์˜ ํ•ต์‹ฌ ๊ตฌ์„ฑ ์š”์†Œ

  • ๐Ÿญ ์ด๋ฒคํŠธ ํ”„๋กœ๋“€์„œ (Event Producer) ํŠน์ • ๋™์ž‘์ด๋‚˜ ์ƒํƒœ ๋ณ€ํ™”๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์ด๋ฒคํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿ”— ์ด๋ฒคํŠธ ๋ธŒ๋กœ์ปค (Event Broker) ์ด๋ฒคํŠธ๋ฅผ ์†Œ๋น„์ž์—๊ฒŒ ์ „๋‹ฌํ•˜๋Š” ๋ฏธ๋“ค์›จ์–ด ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: Kafka, RabbitMQ)
  • ๐ŸŽฏ ์ด๋ฒคํŠธ ์ปจ์Šˆ๋จธ (Event Consumer) ์ „๋‹ฌ๋ฐ›์€ ์ด๋ฒคํŠธ์— ๋ฐ˜์‘ํ•˜๊ณ  ํ•„์š”ํ•œ ์ž‘์—…์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

โš™๏ธ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์™€ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜์˜ ์ž‘๋™ ๋ฐฉ์‹

โœ… 1. ๋น„๋™๊ธฐ ํ†ต์‹  (Asynchronous Communication)

๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์—์„œ๋Š” ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹ ์„ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜์œผ๋กœ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์ฆ‰, ์ง์ ‘ HTTP ํ˜ธ์ถœ์„ ๋ณด๋‚ด๋Š” ๋Œ€์‹ , ์ด๋ฒคํŠธ๋ฅผ ๋ธŒ๋กœ์ปค์— ๋ฐœํ–‰(publish)ํ•˜๊ณ , ๋‹ค๋ฅธ ์„œ๋น„์Šค๋Š” ํ•„์š”ํ•  ๋•Œ ๊ทธ ์ด๋ฒคํŠธ๋ฅผ ๊ตฌ๋…(consume)ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ˆ ์žฅ์  (Pros)

  • ๐Ÿ”„ ์ƒ์‚ฐ์ž์™€ ์†Œ๋น„์ž์˜ ๊ฒฐํ•ฉ๋„๋ฅผ ๋‚ฎ์ถœ ์ˆ˜ ์žˆ์Œ
  • ๐Ÿ“ฆ ์ˆ˜ํ‰ ํ™•์žฅ(Horizontal Scalability)์— ์œ ๋ฆฌํ•จ
  • ๐Ÿ›ก ์žฅ์• ์— ๊ฐ•ํ•œ ๊ตฌ์กฐ (Fault Tolerance ํ–ฅ์ƒ)

โœ… 2. ๋ฏธ๋“ค์›จ์–ด๋กœ์„œ์˜ ์ด๋ฒคํŠธ ๋ธŒ๋กœ์ปค (Event Broker as Middleware)

์ด๋ฒคํŠธ ๋ธŒ๋กœ์ปค๋Š” ์ƒ์‚ฐ์ž์™€ ์†Œ๋น„์ž ์‚ฌ์ด๋ฅผ ์ค‘๊ฐœํ•˜๋Š” ํ•ต์‹ฌ ๋ฏธ๋“ค์›จ์–ด์ž…๋‹ˆ๋‹ค. ๋Œ€ํ‘œ์ ์ธ ๋ธŒ๋กœ์ปค๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  • ๐Ÿงฑ Kafka (Apache Kafka) ๊ณ ์ฒ˜๋ฆฌ๋Ÿ‰, ๋ถ„์‚ฐ ์•„ํ‚คํ…์ฒ˜, ์žฅ์•  ๋ณต์›๋ ฅ์„ ๊ฐ–์ถ˜ ๊ฐ•๋ ฅํ•œ ๋ฉ”์‹œ์ง• ์‹œ์Šคํ…œ
  • ๐Ÿ“ฌ RabbitMQ ๋‹ค์–‘ํ•œ ํ”„๋กœํ† ์ฝœ์„ ์ง€์›ํ•˜๋ฉฐ, ์‹ ๋ขฐ์„ฑ ๋†’์€ ๋ฉ”์‹œ์ง€ ํ์ž‰ ๊ธฐ๋Šฅ ์ œ๊ณต
  • ๐Ÿš€ NATS ๊ฐ€๋ณ๊ณ  ๋น ๋ฅธ ์„ฑ๋Šฅ์„ ์ง€๋‹Œ ๋ฉ”์‹œ์ง• ์‹œ์Šคํ…œ, ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์— ์ ํ•ฉ

โœ… 3. ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์›Œํฌํ”Œ๋กœ์šฐ (Event-Driven Workflow)

  1. Service A (Producer)๊ฐ€ ์–ด๋–ค ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ ํ›„ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.
  2. ํ•ด๋‹น ์ด๋ฒคํŠธ๋Š” ๋ธŒ๋กœ์ปค(Broker)๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.
  3. Service B (Consumer)๋Š” ์ด ์ด๋ฒคํŠธ๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๊ตฌ๋…(subscribe)ํ•˜๊ณ , ๊ทธ์— ๋”ฐ๋ผ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

// Event Struct
type OrderEvent struct {
    OrderID string `json:"orderId"`
    Status  string `json:"status"`
}

// Publish Event
func publishOrderEvent(producer sarama.SyncProducer, topic string, event OrderEvent) error {
    jsonData, err := json.Marshal(event)
    if err != nil {
        return err
    }
    msg := &sarama.ProducerMessage{
        Topic: topic,
        Value: sarama.ByteEncoder(jsonData),
    }
    _, _, err = producer.SendMessage(msg)
    return err
}

๐Ÿš€ Go๋กœ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๊ตฌ์ถ•ํ•˜๊ธฐ

๐Ÿ› ๏ธ 1๋‹จ๊ณ„: Go ํ”„๋กœ์ ํŠธ ์„ธํŒ…ํ•˜๊ธฐ

# Initialize project
go mod init github.com/example/event-driven-go

# Add required packages
go get -u github.com/Shopify/sarama       # Kafka Client
go get -u github.com/gin-gonic/gin        # HTTP Router

๐Ÿ“ก 2๋‹จ๊ณ„: ์ด๋ฒคํŠธ ํ”„๋กœ๋“€์„œ ์ƒ์„ฑํ•˜๊ธฐ (Event Producer)

์ด์ œ Kafka๋กœ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜๋Š” ํ”„๋กœ๋“€์„œ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ์œ„ํ•ด producer.go ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ , Kafka ๋ธŒ๋กœ์ปค์— ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด๋Š” ๋กœ์ง์„ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ฆ ์˜ˆ์ œ: Kafka์— OrderEvent ๋ฐœํ–‰ํ•˜๊ธฐ (main.go)

package main

import (
    "encoding/json"
    "log"
    "time"
    "github.com/Shopify/sarama"
)

// ์ฃผ๋ฌธ ์ด๋ฒคํŠธ ๊ตฌ์กฐ์ฒด ์ •์˜
type OrderEvent struct {
    OrderID string `json:"orderId"`
    Status  string `json:"status"`
}

func main() {
    brokers := []string{"localhost:9092"}
    topic := "order_events"

    // Kafka ํ”„๋กœ๋“€์„œ ์ƒ์„ฑ
    producer, err := createKafkaProducer(brokers)
    if err != nil {
        log.Fatal("Failed to create Kafka producer:", err)
    }
    defer producer.Close()

    // ์ด๋ฒคํŠธ ์ƒ์„ฑ
    event := OrderEvent{
        OrderID: "123456",
        Status:  "CREATED",
    }

    // Kafka๋กœ ์ด๋ฒคํŠธ ๋ฐœํ–‰
    err = publishOrderEvent(producer, topic, event)
    if err != nil {
        log.Println("Failed to publish event:", err)
    }
}

โš™๏ธ ํ”„๋กœ๋“€์„œ ์ƒ์„ฑ ํ•จ์ˆ˜

func createKafkaProducer(brokers []string) (sarama.SyncProducer, error) {
    config := sarama.NewConfig()
    config.Producer.Return.Successes = true
    return sarama.NewSyncProducer(brokers, config)
}

โœ… ๋™๊ธฐ(Sync) ํ”„๋กœ๋“€์„œ๋กœ ์ƒ์„ฑ

โœ… ๋ฉ”์‹œ์ง€ ์ „์†ก ์„ฑ๊ณต ์—ฌ๋ถ€ ํ™•์ธ์„ ์œ„ํ•ดReturn.Successes ์„ค์ •


๐Ÿš€ ์ด๋ฒคํŠธ ๋ฐœํ–‰ ํ•จ์ˆ˜

func publishOrderEvent(producer sarama.SyncProducer, topic string, event OrderEvent) error {
    jsonData, err := json.Marshal(event)
    if err != nil {
        return err
    }

    msg := &sarama.ProducerMessage{
        Topic: topic,
        Value: sarama.ByteEncoder(jsonData),
    }

    _, _, err = producer.SendMessage(msg)
    return err
}

๐Ÿ”น OrderEvent ๊ตฌ์กฐ์ฒด๋ฅผ JSON์œผ๋กœ ์ง๋ ฌํ™”

๐Ÿ”น Kafka ํ† ํ”ฝ์œผ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†ก

๐ŸŽง 3๋‹จ๊ณ„: ์ด๋ฒคํŠธ ์ปจ์Šˆ๋จธ ์ƒ์„ฑํ•˜๊ธฐ (Event Consumer)

์ด๋ฒคํŠธ ์ปจ์Šˆ๋จธ๋Š” Kafka๋กœ๋ถ€ํ„ฐ ์ด๋ฒคํŠธ๋ฅผ ๊ตฌ๋…(consume)ํ•˜๊ณ , ๊ทธ์— ๋”ฐ๋ผ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ“„ consumer.go ์˜ˆ์ œ ์ฝ”๋“œ

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "github.com/Shopify/sarama"
)

type OrderEvent struct {
    OrderID string `json:"orderId"`
    Status  string `json:"status"`
}

func main() {
    brokers := []string{"localhost:9092"}
    topic := "order_events"
    consumeOrderEvents(brokers, topic)
}

๐Ÿ“ก Kafka ์ปจ์Šˆ๋จธ ์ƒ์„ฑ ๋ฐ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ 

func consumeOrderEvents(brokers []string, topic string) {
    consumer, err := sarama.NewConsumer(brokers, nil)
    if err != nil {
        log.Fatal("Error creating Kafka consumer:", err)
    }
    defer consumer.Close()

    partitionConsumer, err := consumer.ConsumePartition(topic, 0, sarama.OffsetNewest)
    if err != nil {
        log.Fatal("Error starting partition consumer:", err)
    }
    defer partitionConsumer.Close()

    for msg := range partitionConsumer.Messages() {
        var event OrderEvent
        err := json.Unmarshal(msg.Value, &event)
        if err != nil {
            log.Println("Error decoding message:", err)
            continue
        }
        processOrderEvent(event)
    }
}

โš™๏ธ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๋กœ์ง

func processOrderEvent(event OrderEvent) {
    fmt.Printf("Processing Order ID: %s, Status: %s\n", event.OrderID, event.Status)
}

๐Ÿง  ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์˜ ๊ณ ๊ธ‰ ๊ฐœ๋…

๐Ÿ“ฆ 1. ์ด๋ฒคํŠธ ์†Œ์‹ฑ (Event Sourcing)

์ด๋ฒคํŠธ ์†Œ์‹ฑ์€ ์‹œ์Šคํ…œ ์ƒํƒœ์˜ ๋ณ€ํ™”๋ฅผ ์ผ์œผํ‚ค๋Š” ์ด๋ฒคํŠธ์˜ ์—ฐ์†์ ์ธ ํ๋ฆ„์„ ์ €์žฅํ•˜๋Š” ์•„ํ‚คํ…์ฒ˜ ํŒจํ„ด์ž…๋‹ˆ๋‹ค.

์ด ๋ฐฉ์‹์—์„œ๋Š” ๋‹จ์ˆœํžˆ ํ˜„์žฌ ์ƒํƒœ๋งŒ์„ ์ €์žฅํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ๋ชจ๋“  ์ƒํƒœ ๋ณ€ํ™”์˜ ๊ธฐ๋ก์„ ์ €์žฅํ•จ์œผ๋กœ์จ

์–ธ์ œ๋“ ์ง€ ์ด๋ฒคํŠธ์˜ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ˜„์žฌ ์ƒํƒœ๋ฅผ ์žฌ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ’พ 2. CQRS (Command Query Responsibility Segregation)

CQRS๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ, ์“ฐ๊ธฐ ์ž‘์—…(Command)๊ณผ ์ฝ๊ธฐ ์ž‘์—…(Query)์„ ์™„์ „ํžˆ ๋ถ„๋ฆฌํ•˜๋Š” ํŒจํ„ด์ž…๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ์—ญํ• ์„ ๋‚˜๋ˆ„๋ฉด ๊ฐ๊ฐ์˜ ๋ชจ๋ธ์ด ๊ณ ์œ ์˜ ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž์ถฐ ์ตœ์ ํ™”๋  ์ˆ˜ ์žˆ์–ด,

๊ฒฐ๊ณผ์ ์œผ๋กœ ์‹œ์Šคํ…œ์˜ ํ™•์žฅ์„ฑ๊ณผ ์„ฑ๋Šฅ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”ฅ 3. ๋ฐ๋“œ ๋ ˆํ„ฐ ํ (Dead Letter Queue, DLQ)

DLQ๋Š” ์ฒ˜๋ฆฌ์— ์‹คํŒจํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ๋”ฐ๋กœ ์ €์žฅํ•˜๋Š” ํ์ž…๋‹ˆ๋‹ค.

์ •์ƒ์ ์ธ ํ๋ฆ„์—์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ•ด๋‹น ๋ฉ”์‹œ์ง€๋ฅผ ๋ถ„๋ฆฌ ๋ณด๊ด€ํ•˜์—ฌ,

์ถ”ํ›„ ์›์ธ์„ ๋ถ„์„ํ•˜๊ณ  ์žฌ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค.

์ด๋Š” ์‹œ์Šคํ…œ์˜ ์‹ ๋ขฐ์„ฑ(reliability)์„ ๋†’์ด๊ณ , ๋””๋ฒ„๊น…์„ ์ˆ˜์›”ํ•˜๊ฒŒ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.


โšก๏ธ Go์—์„œ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์™€ ์ด๋ฒคํŠธ ํ…Œ์ŠคํŠธํ•˜๊ธฐ

๋‹จ์œ„ ํ…Œ์ŠคํŠธ (Unit Testing)

Go์—์„œ๋Š” ์ด๋ฒคํŠธ ๋ฐœํ–‰ ๋กœ์ง์— ๋Œ€ํ•ด ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ์•„๋ž˜์™€ ๊ฐ™์ด Mock Kafka ํ”„๋กœ๋“€์„œ๋ฅผ ์‚ฌ์šฉํ•ด ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

go
์ฝ”๋“œ ๋ณต์‚ฌ
func TestPublishOrderEvent(t *testing.T) {
	mockProducer := mocks.NewSyncProducer(t, nil)
	event := OrderEvent{
		OrderID: "123456",
		Status:  "CREATED",
	}
	err := publishOrderEvent(mockProducer, "test_topic", event)
	assert.NoError(t, err)
}

์ด ํ…Œ์ŠคํŠธ๋Š” publishOrderEvent ํ•จ์ˆ˜๊ฐ€ ์˜ค๋ฅ˜ ์—†์ด ์ •์ƒ์ ์œผ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ (Integration Testing)

์‹ค์ œ Kafka ํ™˜๊ฒฝ์—์„œ ์ด๋ฒคํŠธ ํ๋ฆ„์„ ํ…Œ์ŠคํŠธํ•˜๋ ค๋ฉด Docker๋ฅผ ์‚ฌ์šฉํ•ด Kafka๋ฅผ ์‹คํ–‰์‹œํ‚ค๊ณ ,

์‹ค์ œ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ํ†ตํ•ด ์ „์ฒด ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๊ฐ„ ์ด๋ฒคํŠธ ํ๋ฆ„์„ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๋ฐฉ๋ฒ•์€ ์‹ค์ œ ์šด์˜ ํ™˜๊ฒฝ์— ๊ฐ€๊นŒ์šด ์ƒํ™ฉ์„ ์žฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด ์•ˆ์ •์ ์ธ ๋ฐฐํฌ์— ํฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.


๐Ÿ•น๏ธ Kafka์™€ ํ•จ๊ป˜ Kubernetes์— ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๋ฐฐํฌํ•˜๊ธฐ

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋„์ปค๋ผ์ด์ง• (Dockerize the Application)

์•„๋ž˜๋Š” Go๋กœ ์ž‘์„ฑํ•œ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋ฅผ Docker ์ด๋ฏธ์ง€๋กœ ๋นŒ๋“œํ•˜๊ธฐ ์œ„ํ•œ Dockerfile์ž…๋‹ˆ๋‹ค:

FROM golang:1.18
WORKDIR /app
COPY . .
RUN go mod download
RUN go build -o main .
CMD ["./main"]

์ด ์„ค์ •์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‹จ๊ณ„๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค:

  • Go 1.18 ์ด๋ฏธ์ง€๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ™˜๊ฒฝ์„ ์„ค์ •ํ•˜๊ณ 
  • ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ์˜ ๋ชจ๋“  ์†Œ์Šค๋ฅผ ์ปจํ…Œ์ด๋„ˆ๋กœ ๋ณต์‚ฌํ•œ ๋’ค
  • ํ•„์š”ํ•œ ๋ชจ๋“ˆ์„ ์„ค์น˜ํ•˜๊ณ  main ๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ ๋นŒ๋“œ
  • ์ปจํ…Œ์ด๋„ˆ ์‹œ์ž‘ ์‹œ ํ•ด๋‹น ๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ ์‹คํ–‰

Kubernetes์— ๋ฐฐํฌ (Kubernetes Deployment)

์ด์ œ Docker ์ด๋ฏธ์ง€๊ฐ€ ์ค€๋น„๋˜์—ˆ์œผ๋ฉด, Kubernetes์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ์•„๋ž˜์™€ ๊ฐ™์€ Deployment๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: order-service:v1
          ports:
            - containerPort: 8080

์ด ์„ค์ •์€ ๋‹ค์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค:

  • order-service๋ผ๋Š” ์ด๋ฆ„์˜ Deployment๋ฅผ ์ƒ์„ฑ
  • 3๊ฐœ์˜ ๋ ˆํ”Œ๋ฆฌ์นด๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ๊ณ ๊ฐ€์šฉ์„ฑ ํ™•๋ณด
  • ์ปจํ…Œ์ด๋„ˆ๋Š” order-service:v1 ์ด๋ฏธ์ง€๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์‹คํ–‰
  • 8080 ํฌํŠธ๋ฅผ ์™ธ๋ถ€์— ๋…ธ์ถœ

๐Ÿ”ฅ Go ๊ธฐ๋ฐ˜ ์ด๋ฒคํŠธ ์ค‘์‹ฌ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋ฅผ ์œ„ํ•œ ๋ชจ๋ฒ” ์‚ฌ๋ก€

โœ… ๋ฉฑ๋“ฑ์„ฑ(Idempotency)

์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๋กœ์ง์€ ๊ฐ™์€ ์ด๋ฒคํŠธ๊ฐ€ ์—ฌ๋Ÿฌ ๋ฒˆ ์ฒ˜๋ฆฌ๋˜๋”๋ผ๋„ ๋ถ€์ž‘์šฉ ์—†์ด ๋™์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ํ†ตํ•ด ์žฌ์‹œ๋„ ์‹œ์—๋„ ์•ˆ์ „ํ•œ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.

โœ… ์Šคํ‚ค๋งˆ ์ง„ํ™”(Schema Evolution)

์ƒ์‚ฐ์ž(Producer)์™€ ์†Œ๋น„์ž(Consumer) ๊ฐ„์˜ ํ˜ธํ™˜์„ฑ์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด,

์ด๋ฒคํŠธ ๊ตฌ์กฐ์— ๋ฒ„์ „ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๊ฑฐ๋‚˜, backward compatibility๋ฅผ ๊ณ ๋ คํ•œ ์„ค๊ณ„๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

โœ… ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ๋กœ๊น…(Monitoring & Logging)

์ด๋ฒคํŠธ ํ๋ฆ„์„ ์ •ํ™•ํžˆ ์ถ”์ ํ•˜๊ณ  ๋ณ‘๋ชฉ ํ˜„์ƒ์„ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๋„๋ก,

์ค‘๊ฐ„ ๋‹จ๊ณ„์—์„œ์˜ ๋กœ๊น…๊ณผ ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘์€ ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค.

์˜ˆ: Prometheus + Grafana, OpenTelemetry ๋“ฑ ์‚ฌ์šฉ

โœ… ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ํŒจํ„ด(Circuit Breaker Pattern)

ํ•˜๋‚˜์˜ ์„œ๋น„์Šค ์žฅ์• ๊ฐ€ ์—ฐ์‡„์ ์ธ ์žฅ์• ๋กœ ํผ์ง€๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด,

์ผ์‹œ์ ์œผ๋กœ ํ˜ธ์ถœ์„ ์ฐจ๋‹จํ•˜๊ณ , ์‹œ์Šคํ…œ์„ ๋ณดํ˜ธํ•˜๋Š” ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ํŒจํ„ด์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ: goresilience, sony/gobreaker ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ™œ์šฉ ๊ฐ€๋Šฅ


๐ŸŽ‰ ๊ฒฐ๋ก 

Go ์–ธ์–ด๋กœ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋ฅผ ๊ตฌ์ถ•ํ•˜๋ฉด์„œ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜(Event-Driven Architecture, EDA)๋ฅผ ๊ฒฐํ•ฉํ•˜๋ฉด,

๋†’์€ ํ™•์žฅ์„ฑ๊ณผ ์žฅ์• ์— ๊ฐ•ํ•œ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Kafka, RabbitMQ, NATS ๊ฐ™์€ ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค๋Š” ์„œ๋น„์Šค ๊ฐ„์˜ ๊ฒฐํ•ฉ๋„๋ฅผ ๋‚ฎ์ถ”๋ฉด์„œ๋„ ์›ํ™œํ•œ ํ†ต์‹ ์„ ๋ณด์žฅํ•ด ์ค๋‹ˆ๋‹ค.

โœ… ์ฃผ์š” ์ด์ 

  • ๐Ÿ“ˆ ๋†’์€ ํ™•์žฅ์„ฑ (High Scalability)
  • ๐Ÿ›ก ์žฅ์•  ๋ณต์›๋ ฅ (Resilience to Failures)
  • ๐Ÿ”— ๋А์Šจํ•œ ๊ฒฐํ•ฉ ๊ตฌ์กฐ (Loose Coupling of Services)

๐Ÿงญ ์š”์•ฝ: Go์™€ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค

  • ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜(EDA)๋Š” ์„œ๋น„์Šค ๊ฐ„ ๋น„๋™๊ธฐ ํ†ต์‹ ์„ ํ†ตํ•ด ํ™•์žฅ์„ฑ, ์œ ์—ฐ์„ฑ, ์žฅ์•  ๋ณต์›๋ ฅ์„ ๋†’์ธ๋‹ค.
  • Go๋Š” ๋น ๋ฅธ ์‹คํ–‰ ์†๋„์™€ ๋‚ด์žฅ ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ(goroutine)๋กœ ์ด๋ฒคํŠธ ์ค‘์‹ฌ ์‹œ์Šคํ…œ์— ์ ํ•ฉํ•˜๋‹ค.
  • Kafka, RabbitMQ, NATS ๋“ฑ์€ ์„œ๋น„์Šค ๊ฐ„ ๊ฒฐํ•ฉ๋„๋ฅผ ๋‚ฎ์ถ”๊ณ , ์ด๋ฒคํŠธ ํ๋ฆ„์„ ์•ˆ์ •์ ์œผ๋กœ ์ „๋‹ฌํ•˜๋Š” ์ค‘๊ฐœ์ž ์—ญํ• ์„ ํ•œ๋‹ค.
  • EDA์—์„œ๋Š” ๋ฉฑ๋“ฑ์„ฑ, ์Šคํ‚ค๋งˆ ํ˜ธํ™˜์„ฑ, ๋กœ๊น…/๋ชจ๋‹ˆํ„ฐ๋ง, ์žฅ์•  ๋Œ€์‘ ํŒจํ„ด ๋“ฑ์˜ ์šด์˜ ์ „๋žต์ด ํ•„์ˆ˜์ด๋‹ค.

๐Ÿ“š ์ถ”๊ฐ€๋กœ ๊ณต๋ถ€ํ•˜๋ฉด ์ข‹์€ ๊ฐœ๋…๋“ค

  1. Event Sourcing
    • ์ƒํƒœ๋ฅผ ์ง์ ‘ ์ €์žฅํ•˜์ง€ ์•Š๊ณ , ์ƒํƒœ ๋ณ€ํ™” ์ด๋ฒคํŠธ์˜ ์ˆœ์„œ๋ฅผ ์ €์žฅํ•ด ํ˜„์žฌ ์ƒํƒœ๋ฅผ ์žฌ๊ตฌ์„ฑํ•˜๋Š” ๋ฐฉ์‹.
    • ๋ณต์›์„ฑ๊ณผ ๊ฐ์‚ฌ(audit) ๊ธฐ๋Šฅ์— ์œ ๋ฆฌํ•จ.
  2. CQRS (Command Query Responsibility Segregation)
    • *์“ฐ๊ธฐ(Command)์™€ ์ฝ๊ธฐ(Query)**๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐ ํ™•์žฅ์„ฑ ํ™•๋ณด์— ๋„์›€.
  3. Dead Letter Queue (DLQ)
    • ์ฒ˜๋ฆฌ ์‹คํŒจํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณ„๋„ ํ์— ์ €์žฅํ•ด ์žฅ์•  ๋ถ„์„๊ณผ ์žฌ์ฒ˜๋ฆฌ๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ์ „๋žต.
  4. ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ํŒจํ„ด (Circuit Breaker)
    • ์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ•œ ์„œ๋น„์Šค๋กœ์˜ ํ˜ธ์ถœ์„ ์ผ์‹œ์ ์œผ๋กœ ์ฐจ๋‹จํ•˜์—ฌ ์‹œ์Šคํ…œ ์ „์ฒด์˜ ์—ฐ์‡„ ์žฅ์• ๋ฅผ ์˜ˆ๋ฐฉํ•˜๋Š” ํŒจํ„ด.
profile
๊ฐœ๋ฐœ์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๋Š”, ์ด์Šˆ๋ฅผ ์ค„์ด๋Š” ํ™˜๊ฒฝ์„ ๋งŒ๋“ค๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€