🔥 FREE PRO OFFER OnlyLink.click Pro Version is 100% Free of Cost till 31 December, 2026! Claim Free Pro

안무와 오케스트레이션: 마이크로서비스의 분산 워크플로 설계

마이크로서비스 아키텍처의 안무와 오케스트레이션

모놀리식 아키텍처에서는 전자상거래 주문 이행과 같은 복잡한 비즈니스 트랜잭션을 실행하는 것이 간단합니다. 모든 데이터는 단일 관계형 데이터베이스에 상주하므로 개발자는 단일 ACID 트랜잭션 내에서 재고, 결제 및 배송 전반에 걸쳐 여러 데이터베이스 쓰기를 래핑할 수 있습니다. 어느 시점에서든 오류가 발생하면 SQL ROLLBACK이 즉시 시스템 일관성을 복원합니다.

그러나 최신 클라우드 기반 시스템은 각 서비스가 자체 데이터를 소유하고 고유한 API 경계를 노출하는 마이크로서비스 아키텍처를 채택합니다. 이 분산 패러다임에서는 단일 엔드투엔드 비즈니스 운영이 여러 개의 독립적인 마이크로서비스와 데이터베이스 엔진에 걸쳐 있습니다.

2PC(2단계 커밋) 프로토콜은 클라우드 네트워크 전체에서 느리고 차단되며 취약하기 때문에 분산 시스템은 최종 일관성을 유지하면서 워크플로를 비동기식으로 조정해야 합니다.

이로 인해 소프트웨어 설계자는 기본적인 설계 결정을 내리게 됩니다. 분산된 마이크로서비스 워크플로우를 관리하기 위해 안무 또는 오케스트레이션을 사용해야 합니까?

이 포괄적인 가이드에서는 두 가지 아키텍처 패턴을 분석하고, 실제 비유를 탐구하고, 아키텍처 장단점을 분석하고, GoJava의 프로덕션 코드 구현을 자세히 설명하고, 인프라에 적합한 패턴을 선택하기 위한 프레임워크를 구축합니다.


실제 비유: 플래시몹과 심포니 오케스트라

두 패턴에 대한 직관적인 정신 모델을 구축하려면 인간 수행자 그룹이 자신의 행동을 어떻게 조정하는지 고려하십시오.

안무 비유: 플래시몹 댄서 네트워크

플래시몹 루틴을 수행하는 전문 거리 댄서 그룹을 상상해 보세요. 무대 위에 서서 개별 댄서에게 다음 동작을 지시하는 강사가 없습니다. 대신 각 댄서는 중앙 음악 트랙을 듣고 옆 댄서의 움직임에 역동적으로 반응합니다.

  • 댄서 A가 공중제비를 완료하면 댄서 B가 시각적 신호를 인식하고 회전을 시작합니다.
  • 댄서 B가 회전을 마치면 댄서 C가 앞으로 나아갑니다.
  • 주요 특징: 분산형, 반응형, 자율형. 모든 참가자는 중앙의 지시 없이 자신의 책임을 이해합니다.

오케스트레이션 비유: 심포니 오케스트라

이제 70인조 클래식 심포니 오케스트라를 상상해 보세요. 바이올리니스트, 타악기 연주자, 첼리스트는 무대를 가로질러 서로의 손을 보면서 단서를 잡지 않습니다. 대신 모든 사람이 지휘자를 똑바로 쳐다봅니다.

  • 지휘자는 부드러운 현을 연주할 때 바이올린에 신호를 보냅니다.
  • 지휘자는 드럼을 가리키며 타악기를 치는 신호를 보냅니다.
  • 연주자가 템포를 놓치면 지휘자가 템포 조정을 조정하거나 일시 중지 신호를 보냅니다.
  • 주요 특징: 중앙 집중식, 명시적, 명령 기반. 한 명의 리더가 모든 참가자를 지휘합니다.

1. 안무 아키텍처: 분산화 및 이벤트 중심

안무에서 분산 마이크로서비스는 중앙 마스터 코디네이터 없이 반응적으로 통신합니다. 서비스는 내부 상태가 변경될 때마다 비동기 메시지 브로커(예: Apache Kafka, RabbitMQ 또는 AWS EventBridge)에 도메인 이벤트를 게시합니다. 다운스트림 마이크로서비스는 관련 이벤트 주제를 구독하고 다음에 수행할 작업을 독립적으로 결정합니다.

이벤트 기반 안무 마이크로서비스 아키텍처 다이어그램

안무에 따른 전자상거래 흐름

안무를 사용한 전자상거래 결제 프로세스를 생각해 보세요.

  1. 주문 서비스: HTTP POST 체크아웃 요청을 수신하고 보류 중인 주문을 데이터베이스에 기록하며 OrderCreated 도메인 이벤트를 Kafka 이벤트 버스에 내보냅니다.
  2. 결제 서비스: OrderCreated 주제를 구독합니다. 이벤트를 수신하면 고객의 신용 카드에 요금이 청구되고 PaymentProcessed 이벤트가 발생합니다.
  3. 인벤토리 서비스: PaymentProcessed 주제를 구독합니다. 창고 품목을 예약하고 InventoryReserved 이벤트를 발생시킵니다.
  4. 배송 서비스: InventoryReserved 주제를 구독합니다. 배송 라벨을 생성하고 OrderShipped 이벤트를 발생시킵니다.
  5. 알림 서비스: OrderShipped에 가입하고 고객에게 추적 이메일을 보냅니다.

안무의 장점

  • 높은 자율성 및 느슨한 결합: 서비스는 다운스트림 핸들러의 존재를 알지 못합니다. 주문 서비스는 주문이 생성되었다는 사실만 알고 있습니다. 누가 그 정보를 소비하는지는 상관하지 않습니다.
  • 독립적인 확장성 및 속도: 팀은 마이크로서비스를 독립적으로 구축, 배포 및 확장할 수 있습니다. 새로운 기능(예: 판매 추적 분석 서비스)을 추가하려면 업스트림 코드를 수정하지 않고 기존 이벤트를 구독해야 합니다.
  • SPOF(단일 실패 지점 없음): 중앙 워크플로 조정자가 없기 때문에 관련 없는 서비스의 오류로 인해 전체 실행 엔진이 중단되지 않습니다.
  • 고성능 및 처리량: 이벤트 기반 게시/구독 스트리밍은 동기식 HTTP/gRPC 차단 대기 시간 없이 대규모 이벤트 볼륨을 비동기식으로 처리합니다.

안무의 단점

  • 암시적 워크플로 논리: 단일 코드 위치가 엔드투엔드 비즈니스 프로세스를 정의하지 않습니다. 전체 워크플로를 이해하려면 여러 코드베이스에 걸쳐 이벤트 핸들러를 하나로 묶어야 합니다.
  • 순환 종속성 위험: 마이크로서비스가 신중한 주제 설계 없이 겹치는 주제를 게시하고 구독하는 경우 무한 이벤트 루프로 인해 시스템 메시지 대기열이 중단될 수 있습니다.
  • 복잡한 관찰 가능성 및 분산 추적: 10가지 이벤트 주제에 걸쳐 단일 주문 거래를 추적하려면 강력한 분산 추적 인프라(예: OpenTelemetry, Jaeger, W3C Trace Context)가 필요합니다.
  • 어려운 오류 처리 및 보상: 결제가 성공한 후 Inventory Service가 실패하는 경우 Inventory Service는 InventoryFailed 이벤트를 발생시켜야 합니다. 결제 서비스는 이 이벤트를 수신하고 수동으로 환불 보상을 실행해야 합니다.

2. 오케스트레이션 아키텍처: 중앙 집중식 및 명령 기반

Orchestration에서는 전용 코디네이터 서비스(Saga Orchestrator)가 실행 순서를 명시적으로 지시합니다. Orchestrator는 워크플로 상태 시스템을 보유하고, 명령 요청(gRPC, HTTP REST 또는 전용 명령 대기열을 통해)을 작업자 마이크로서비스에 보내고, 응답을 기다리고, 다음 실행 단계를 결정합니다.

Saga 오케스트레이션 마이크로서비스 아키텍처 다이어그램

오케스트레이션에 따른 전자상거래 흐름

  1. 주문 서비스/Saga Orchestrator: 결제 요청을 수신하고 ORDER_PENDING 상태에서 OrderSagaCoordinator 워크플로 인스턴스를 인스턴스화합니다.
  2. 1단계(결제 명령): Orchestrator가 PaymentService.ExecutePayment()을(를) 호출합니다. 결제 서비스가 결제를 처리하고 SUCCESS을(를) 반환합니다.
  3. 2단계(인벤토리 명령): Orchestrator는 SUCCESS을 수신하고 InventoryService.ReserveStock()을 호출합니다. Inventory Service는 재고를 예약하고 SUCCESS을(를) 반환합니다.
  4. 3단계(배송 명령): Orchestrator는 ShippingService.CreateShipment()을 호출합니다. 배송 서비스는 추적 세부 정보를 반환합니다.
  5. 4단계(완료): Orchestrator는 상태 저장소의 주문 상태를 ORDER_COMPLETED로 업데이트합니다.

2단계에서 InventoryService.ReserveStock()이 실패하면 Orchestrator는 롤백 명령을 순차적으로 실행합니다.

  • 1단계를 실행 취소하려면 PaymentService.RefundPayment()을 호출합니다.
  • Saga 상태를 ORDER_CANCELLED로 업데이트합니다.

오케스트레이션의 장점

  • 명시적 및 중앙 집중식 워크플로 가시성: 전체 비즈니스 프로세스는 단일 상태 머신 정의 또는 워크플로 DSL(예: 임시 워크플로 정의)에서 명확하게 표시됩니다.
  • 간소화된 오류 관리: 단계가 실패하면 Orchestrator는 간접 이벤트 체인에 의존하지 않고 이전에 완료된 모든 단계에 대해 보상 트랜잭션을 직접 호출합니다.
  • 순환 종속성 방지: 작업자 서비스는 서로 직접 호출하는 대신 Orchestrator와 앞뒤로 통신합니다.
  • 더 쉬워진 테스트 및 감사: 단위 테스트에서 서비스 응답을 모의하여 워크플로 상태 전환을 결정적으로 테스트할 수 있습니다.

오케스트레이션의 단점

  • 과도한 중앙 집중화(“God Service”)의 위험: 개발자가 도메인 비즈니스 로직을 Orchestrator에 푸시하면 작업자 마이크로서비스가 “멍청한 CRUD 서비스"로 전환되어 모놀리식 코어를 다시 생성할 위험이 있습니다.
  • 더 긴밀한 API 결합: Orchestrator는 모든 참여자 마이크로서비스의 API 계약 및 엔드포인트를 명시적으로 인식해야 합니다.
  • 잠재적 확장성 병목 현상: 중앙 Orchestrator는 모든 활성 트랜잭션의 상태 지속성을 처리합니다. 높은 처리량 시스템에는 수평으로 확장 가능한 상태 엔진 백엔드가 필요합니다.

3. 종합적인 아키텍처 비교

안무와 오케스트레이션을 나란히 평가하려면 주요 운영 특성을 고려하세요.

안무와 오케스트레이션 아키텍처 비교 매트릭스
차원 안무(이벤트 중심) 오케스트레이션(명령 기반)
커뮤니케이션 스타일 비동기 Pub/Sub(Event 브로드캐스트) 지점간/RPC(Command + 응답)
서비스 커플링 매우 낮음(서비스는 도메인 이벤트만 알고 있음) 중간(Orchestrator는 작업자 API를 알고 있음)
상태 관리 서비스 데이터베이스에 분산 Orchestrator 상태 엔진 내부의 중앙 집중식
워크플로 가시성 암시적(핸들러에 걸쳐 확산) 명시적(중앙 집중식 상태 기계 코드)
실패 복구 복합(보상 이벤트의 연속) 간단함(Orchestrator가 롤백을 관리함)
분산 추적 모든 주제에 걸쳐 상관 관계 ID가 필요합니다 Orchestrator 로그를 통한 단순화된 추적
이상적인 팀 규모 자율적인 팀을 갖춘 대규모 엔지니어링 조직 복잡한 기업 흐름을 관리하는 중/대규모 팀
가장 적합한 대상 높은 처리량, 간단한 선형 워크플로우 무거운 비즈니스 규칙이 포함된 복잡한 다중 지점 워크플로우

4. 하이브리드 접근 방식: 매크로 안무 + 마이크로 오케스트레이션

현대 엔터프라이즈 아키텍처에서는 전부 아니면 전무의 선택을 강요하는 경우가 거의 없습니다. 대신, 주요 엔지니어링 팀은 하이브리드 아키텍처를 사용합니다.

  • 매크로 수준(안무): 높은 수준의 제한된 컨텍스트(예: 판매 제한 컨텍스트, 공급망 제한 컨텍스트, 고객 지원)는 Kafka 또는 NATS를 통해 이벤트 중심 안무를 사용하여 통신합니다.
  • 마이크로 수준(오케스트레이션): 특정 제한된 컨텍스트(예: 다중 게이트웨이 재시도, 사기 검증 및 원장 항목을 처리하는 결제 제한된 컨텍스트 내부) 내에서 로컬 오케스트레이터는 세분화된 서비스 실행을 조정합니다.
                  [EVENT BROKER: KAFKA]
             /              |              \
   (OrderCreated)     (PaymentSuccess)   (StockReserved)
           /                |                \
  [Order Domain]   [Payment Domain]   [Inventory Domain]
         |                  |                 |
  (Local Saga        (Local Saga       (Local Saga
  Orchestrator)      Orchestrator)     Orchestrator)

이 하이브리드 패턴은 개별 서비스 팀 내에서 오케스트레이션의 상태 가시성을 유지하면서 도메인 경계를 넘어 안무를 느슨하게 결합합니다.


5. 생산 코드 예제

Go 및 **Java(Spring Boot)**를 사용하여 프로덕션 환경에서 두 패턴을 모두 구현하는 방법을 살펴보겠습니다.

Go 구현: 안무 이벤트 소비자와 Saga Orchestrator

1. Go에서의 안무(Kafka 이벤트 소비자)

안무에서 Inventory Service는 Kafka의 PaymentProcessedEvent을 반응적으로 수신합니다.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

	"github.com/segmentio/kafka-go"
)

type PaymentProcessedEvent struct {
	OrderID string  `json:"order_id"`
	Amount  float64 `json:"amount"`
	Status  string  `json:"status"`
}

type InventoryReservedEvent struct {
	OrderID string `json:"order_id"`
	Status  string `json:"status"`
}

func main() {
	reader := kafka.NewReader(kafka.ReaderConfig{
		Brokers: []string{"localhost:9092"},
		Topic:   "payment-events",
		GroupID: "inventory-service-group",
	})
	defer reader.Close()

	writer := kafka.NewWriter(kafka.WriterConfig{
		Brokers: []string{"localhost:9092"},
		Topic:   "inventory-events",
	})
	defer writer.Close()

	fmt.Println("Inventory Service listening for payment events...")

	for {
		msg, err := reader.ReadMessage(context.Background())
		if err != nil {
			log.Fatalf("Error reading message: %v", err)
		}

		var event PaymentProcessedEvent
		if err := json.Unmarshal(msg.Value, &event); err != nil {
			log.Printf("Invalid message payload: %v", err)
			continue
		}

		if event.Status == "SUCCESS" {
			log.Printf("[Choreography] Reserved stock for Order: %s", event.OrderID)
			
			// Publish downstream domain event reactively
			resEvent := InventoryReservedEvent{
				OrderID: event.OrderID,
				Status:  "RESERVED",
			}
			payload, _ := json.Marshal(resEvent)
			
			err = writer.WriteMessages(context.Background(), kafka.Message{
				Key:   []byte(event.OrderID),
				Value: payload,
			})
			if err != nil {
				log.Printf("Failed to publish inventory event: %v", err)
			}
		}
	}
}

2. Go의 오케스트레이션(중앙 상태 머신 코디네이터)

오케스트레이션에서 명시적 상태 시스템은 단계를 실행하고 보상을 처리합니다.

package main

import (
	"context"
	"errors"
	"fmt"
	"log"
)

type OrderSagaOrchestrator struct {
	paymentClient *PaymentClient
	stockClient   *StockClient
}

func NewOrderSagaOrchestrator(p *PaymentClient, s *StockClient) *OrderSagaOrchestrator {
	return &OrderSagaOrchestrator{paymentClient: p, stockClient: s}
}

func (o *OrderSagaOrchestrator) ExecuteSaga(ctx context.Context, orderID string, amount float64) error {
	log.Printf("[Orchestrator] Starting Saga execution for Order ID: %s", orderID)

	// Step 1: Charge Payment
	if err := o.paymentClient.Charge(ctx, orderID, amount); err != nil {
		log.Printf("[Orchestrator] Payment failed for Order %s: %v", orderID, err)
		return err
	}
	log.Printf("[Orchestrator] Step 1 Complete: Payment Charged")

	// Step 2: Reserve Inventory
	if err := o.stockClient.Reserve(ctx, orderID); err != nil {
		log.Printf("[Orchestrator] Inventory reservation failed: %v. Initiating Compensation...", err)
		
		// Compensation Step: Refund Payment
		if refundErr := o.paymentClient.Refund(ctx, orderID, amount); refundErr != nil {
			log.Printf("[CRITICAL] Compensation failed! Manual intervention required for Order %s", orderID)
		}
		return errors.New("saga aborted: inventory unavailable")
	}

	log.Printf("[Orchestrator] Saga Completed Successfully for Order ID: %s", orderID)
	return nil
}

type PaymentClient struct{}
func (p *PaymentClient) Charge(ctx context.Context, id string, amt float64) error { return nil }
func (p *PaymentClient) Refund(ctx context.Context, id string, amt float64) error { return nil }

type StockClient struct{}
func (s *StockClient) Reserve(ctx context.Context, id string) error { return errors.New("out of stock") }

func main() {
	saga := NewOrderSagaOrchestrator(&PaymentClient{}, &StockClient{})
	_ = saga.ExecuteSaga(context.Background(), "ORD-9982", 149.99)
}

Java(스프링 부트) 구현

1. Java 안무 (Spring Cloud Stream / Kafka Listener)

package com.ghaznix.microservices.choreography;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.function.Function;

public record PaymentProcessedEvent(String orderId, String status, double amount) {}
public record InventoryReservedEvent(String orderId, String status) {}

@Configuration
public class InventoryChoreographyProcessor {

    @Bean
    public Function<PaymentProcessedEvent, InventoryReservedEvent> processPaymentEvent() {
        return paymentEvent -> {
            System.out.println("[Choreography Java] Processing payment event for order: " + paymentEvent.orderId());

            if ("SUCCESS".equals(paymentEvent.status())) {
                // Reserve stock in database...
                System.out.println("[Choreography Java] Reserved inventory for: " + paymentEvent.orderId());
                return new InventoryReservedEvent(paymentEvent.orderId(), "SUCCESS");
            } else {
                return new InventoryReservedEvent(paymentEvent.orderId(), "FAILED");
            }
        };
    }
}

2. Java의 오케스트레이션(선언적 상태 코디네이터)

package com.ghaznix.microservices.orchestration;

import org.springframework.stereotype.Service;

@Service
public class OrderSagaOrchestratorService {

    private final PaymentServiceClient paymentClient;
    private final InventoryServiceClient inventoryClient;
    private final ShippingServiceClient shippingClient;

    public OrderSagaOrchestratorService(PaymentServiceClient p, InventoryServiceClient i, ShippingServiceClient s) {
        this.paymentClient = p;
        this.inventoryClient = i;
        this.shippingClient = s;
    }

    public boolean processCheckoutSaga(String orderId, double totalAmount) {
        System.out.println("[Orchestrator Java] Initiating Saga Workflow for Order: " + orderId);

        // Step 1: Execute Payment
        boolean paymentSuccess = paymentClient.processPayment(orderId, totalAmount);
        if (!paymentSuccess) {
            System.err.println("[Orchestrator Java] Step 1 Failed: Aborting Saga.");
            return false;
        }

        // Step 2: Reserve Inventory
        boolean inventorySuccess = inventoryClient.reserveStock(orderId);
        if (!inventorySuccess) {
            System.err.println("[Orchestrator Java] Step 2 Failed: Triggering Compensation.");
            paymentClient.refundPayment(orderId, totalAmount);
            return false;
        }

        // Step 3: Trigger Shipping
        boolean shippingSuccess = shippingClient.createShipment(orderId);
        if (!shippingSuccess) {
            System.err.println("[Orchestrator Java] Step 3 Failed: Compensating Step 2 & Step 1.");
            inventoryClient.releaseStock(orderId);
            paymentClient.refundPayment(orderId, totalAmount);
            return false;
        }

        System.out.println("[Orchestrator Java] Saga Executed Successfully.");
        return true;
    }
}

6. 인기 있는 산업 툴링 환경

선택한 아키텍처 방향에 따라 오픈 소스 및 클라우드 에코시스템은 전용 인프라 엔진을 제공합니다.

안무 생태계

  • 메시지 스트리밍: Apache Kafka, Apache Pulsar, RabbitMQ, NATS JetStream.
  • 클라우드 이벤트 라우터: AWS EventBridge, Azure Event Grid, Google Cloud Eventarc.
  • 스키마 레지스트리: 합류 스키마 레지스트리(Avro/Protobuf 거버넌스용).

오케스트레이션 생태계

  • 워크플로 코드 엔진: Temporal.io(Go/Java/TypeScript 내구성 실행 엔진), Cadence.
  • 클라우드 관리 코디네이터: AWS Step Functions, Azure Logic Apps, GCP 워크플로.
  • BPMN 및 엔터프라이즈 엔진: Camunda 8(Zeebe), Netflix Conductor.

7. 의사결정 매트릭스: 선택 방법

마이크로서비스 플랫폼에 대한 안무와 오케스트레이션 중에서 결정할 때 다음 결정 규칙 매트릭스를 사용하십시오.

                              [Start: System Architecture Assessment]
                                                |
                              Is the workflow complex with >4 steps 
                              or strict business auditing rules?
                                           /         \
                                     (YES)             (NO)
                                     /                   \
               [Choose: Saga Orchestration]    Does the system require 
               (e.g., Temporal / Camunda)      ultra-high event streaming velocity?
                                                   /              \
                                             (YES)                  (NO)
                                             /                        \
                            [Choose: Event Choreography]      [Choose: Simple Choreography]
                            (e.g., Apache Kafka / NATS)       (e.g., RabbitMQ Pub/Sub)

다음과 같은 경우 안무를 선택하세요.

  1. 워크플로는 2~4개의 간단한 선형 단계로 구성됩니다.
  2. 높은 이벤트 스트리밍 처리량과 밀리초 미만의 전송 대기 시간이 최우선 사항입니다.
  3. 엔지니어링 팀은 독립적으로 서비스를 구축하고 배포하는 자율 도메인 팀으로 구성됩니다.
  4. 이미 강력한 분산 추적 및 APM 도구(OpenTelemetry, Datadog)를 보유하고 있습니다.

다음과 같은 경우 오케스트레이션을 선택하세요.

  1. 귀하의 비즈니스 프로세스에는 복잡한 상태 전환, 다중 분기 조건부 논리 또는 일시적인 지연(예: “고객 승인을 위해 3일 대기”)이 포함됩니다.
  2. 규정 준수 및 감사 요구 사항에 따라 모든 거래의 정확한 상태에 대한 중앙 집중식 로그가 필요합니다.
  3. 맞춤형 이벤트 체이닝 로직을 작성하지 않고도 실패한 단계에 대한 강력하고 자동화된 보상 롤백이 필요합니다.
  4. 귀하는 기업 금융 거래(예: 은행 업무, 보험 청구 처리)를 관리하고 있습니다.

결론

안무도 오케스트레이션도 보편적으로 우월하지 않습니다. 안무는 암시적 워크플로 가시성과 복잡한 분산 추적을 희생하면서 느슨한 결합, 이벤트 처리량 및 서비스 자율성을 극대화합니다. 조정은 더 긴밀한 API 결합 및 조정자 인프라 관리를 희생하면서 명시적인 상태 관리, 중앙 집중식 감사 기능, 결정적 오류 복구를 제공합니다.

두 패턴의 장점을 이해하고 적절한 경우 마이크로 오케스트레이션을 통한 하이브리드 매크로 구성을 활용함으로써 클라우드 환경 전반에서 분산 트랜잭션을 원활하게 처리하는 탄력적이고 확장 가능한 마이크로서비스 아키텍처를 구축할 수 있습니다.

Ghaznix Ecosystem Products

Empower Your Digital Presence & Workflows

Explore top-tier tools built by Ghaznix to streamline your links, surveys, and brand growth.