질문의 역사
모놀리식 아키텍처에서 API 테스트는 중간 세션 저장소에 상태를 유지하면서 단일 엔드포인트에 대한 간단한 요청-응답 검증에 의존했습니다. 마이크로서비스로의 전환은 비즈니스 작업이 동기 및 비동기 체인을 통해 여러 서비스에 걸쳐 이루어지는 분산 거래 복잡성을 도입하여 테스터가 네트워크 경계를 넘어 상태를 추적할 수 있도록 요구했습니다. 또한 자동 확장 및 블루-그린 배포와 같은 인프라 변동성을 수용해야 했습니다.
문제
전통적인 API 자동화는 각 서비스 호출을 고립된 거래로 취급하여 부분 실패가 서비스 경계를 넘어 보상 조치를 트리거해야 하는 사가 및 분산 거래를 검증할 수 없습니다. 또한 하드코딩된 서비스 엔드포인트는 동적 스케일링에 대해 테스트를 부서지기 쉽게 만들며, 통제된 결함 주입이 없으면 회로 차단기 구성 및 재시도 정책이 생산 사건 발생까지 검증되지 않아 재앙적인 연쇄 실패로 이어질 수 있습니다.
해결책
정적 구성 대신 실행 시 동적 엔드포인트를 확인하기 위해 Consul 또는 Eureka와 같은 서비스 검색 레지스트리를 활용하는 발레리티 인지 테스트 하니스를 구현합니다. 이 아키텍처는 이벤트 소싱 리스너를 통해 사가 패턴 검증을 구현하여, 서비스 호출 간의 상관 ID를 추적하여 부분 실패 동안 보상 거래가 올바르게 수행되도록 합니다. 또한 Istio와 같은 서비스 메시 제어 평면과 통합하여 지연 및 오류 응답을 주입함으로써 응용 프로그램 코드를 수정하거나 전용 테스트 환경을 요구하지 않고 회로 차단기 검증을 가능하게 합니다.
public class DistributedSagaTest { private DynamicServiceMesh mesh; private SagaEventValidator validator; private FaultInjector faultInjector; @BeforeMethod public void setup() { mesh = new DynamicServiceMesh(ServiceRegistry.consul()); validator = new SagaEventValidator(KafkaConfig.testConsumer()); faultInjector = new IstioFaultInjector(mesh); } @Test public void testOrderSagaWithCircuitBreaker() { String sagaId = UUID.randomUUID().toString(); OrderRequest order = new OrderRequest("SKU-123", 2); // Phase 1: Reserve inventory Response reserve = mesh.post(Service.INVENTORY, "/reserve", order, sagaId); assertEquals(reserve.getStatus(), 201); // Inject payment service latency to trigger circuit breaker faultInjector.addLatency(Service.PAYMENT, 5000, 0.5); // Phase 2: Process payment with resilience validation PaymentResult result = validator.executeWithValidation(sagaId, () -> { return mesh.post(Service.PAYMENT, "/charge", order, sagaId); }); if (result.isCircuitBreakerOpen()) { // Verify compensating transaction releases inventory validator.awaitCompensatingEvent(sagaId, "INVENTORY_RELEASED", Duration.ofSeconds(5)); InventoryStatus status = mesh.get(Service.INVENTORY, "/status/" + order.getSku(), sagaId); assertEquals(status.getReservedQuantity(), 0); } } }
한 금융 기술 회사는 모놀리식 결제 프로세서를 12개의 상호 의존 서비스로 구성된 마이크로서비스 아키텍처로 마이그레이션했습니다. 자동화 팀은 초기에는 속성 파일에 저장된 정적으로 구성된 엔드포인트를 사용해 이러한 서비스를 전통적인 REST 보장 테스트로 테스트하려고 시도했지만 해당 서비스의 IP 주소와 포트 변경으로 인해 첫 주 내에 40%의 테스트 실행이 실패했습니다.
팀은 이 불안을 해결하기 위해 세 가지 뚜렷한 아키텍처 접근 방식을 고려했습니다. 첫 번째 옵션은 모든 서비스가 테스트 실행 중에 연결하는 중앙 집중식 테스트 데이터베이스를 구현하여 공유 상태를 통해 데이터 일관성을 보장하는 것이었습니다. 이는 분산 거래 복잡성을 제거했지만, 서비스 간의 위험한 결합을 초래하고 각 서비스가 자체 데이터 저장소를 유지하는 생산 유사 구성에 대해 테스트하는 원칙을 위반하여 직렬화 오류 및 연결 풀 문제를 감추었습니다. 두 번째 접근 방식은 WireMock과 같은 도구를 사용해 모든 종속 서비스를 포괄적으로 모방하는 것이었으며, 이는 안정성과 빠른 실행을 제공했지만, 실제 서비스 상호작용에서만 나타나는 네트워크 시간 초과, 회로 차단기 잘못 구성 및 이벤트 브로커 지연과 관련된 통합 실패를 감지하지 못했습니다.
선택된 해결책은 Istio를 사용하여 서비스 메쉬 사이드카 패턴을 구현하여 플랫폼의 DNS 레지스트리를 통해 동적 서비스 발견을 용이하게 하고, 주입된 상관 헤더를 통해 분산 거래를 추적하는 사용자 정의 사가 테스트 오케스트레이터와 결합되었습니다. 이 아키텍처는 테스트가 하드코딩된 IP 대신 메쉬 발견을 통해 엔드포인트를 해결할 수 있도록 하였으며, Istio 결함 주입 기능은 응용 프로그램 코드를 수정하지 않고도 재시도 정책 및 회로 차단기의 유효성을 검증할 수 있게 했습니다. 사가 오케스트레이터는 Kafka 주제를 듣는 이벤트 저널을 유지하여 보상 거래 이벤트를 위해, 부분 실패가 분산 원장에서 롤백 시퀀스를 올바르게 트리거했음을 검증했습니다.
구현 후, 이 프레임워크는 매일 500개의 끝-to-끝 거래 흐름을 연속적으로 재배포하는 환경에서 성공적으로 실행하며, 이전 단위 및 계약 테스트에서 놓친 보상 거래 논리의 세 가지 중요한 경합 조건을 식별했습니다. 동적 발견 메커니즘은 환경 관련 테스트 실패를 완전히 제거했으며, 혼돈 공학 통합은 다음 고부하 이벤트 동안 생산에서의 연쇄 실패를 초래할 수 있는 회로 차단기 임계값 구성 오류를 잡아 서식할 수 있었습니다.
어떻게 분산 시스템에서 최종 일관성을 검증하며 임의의 수면 지연을 통해 불안정한 테스트를 도입하지 않나요?
많은 후보자들이 Thread.sleep() 또는 최대 가능 지연에 고정된 암묵적 대기를 사용하라고 제안하지만 이는 실행 속도를 극적으로 늦추고 가변 부하 조건에서는 신뢰할 수 없습니다. 올바른 방법은 비즈니스 이벤트 완료를 기준으로 하는 결정적인 종료 기준에 기반한 지수적 백오프로 적응형 폴링을 구현하여, 데이터베이스 또는 메시지 브로커에서 사가 완료 마커를 확인하는 Awaitility와 같은 라이브러리를 사용합니다. 이는 테스트가 타이밍이 아니라 실제 일관성 경계를 검증하도록 보장합니다.
마이크로서비스에서 소비자 주도 계약 테스트와 끝-to-끝 통합 테스트 간의 근본적인 아키텍처 차이는 무엇이며, 하나를 다른 것으로 대체하면 왜 실패하게 되나요?
후보자들은 자주 이러한 접근 방식을 혼동하며, 계약 테스트만으로 시스템 기능을 보장할 수 있다고 주장하거나, 끝-to-끝 테스트가 모든 시나리오에 대한 충분한 인터페이스 검증을 제공한다고 제안합니다. 소비자 주도 계약 테스트는 Pact와 같은 도구를 사용하여 특정 서비스 쌍 간의 스키마 호환성과 요청-응답 계약을 검증하여 제공자의 변경 사항이 개별 소비자를 중단하지 않도록 보장하지만, 여러 서비스에 걸쳐 발생하는 거래의 emergent behavior를 검증할 수 없습니다. 반면에 끝-to-끝 테스트는 이러한 복잡한 상호작용 패턴과 실패 모드 전파를 검증하지만 느린 피드백을 제공하며 서비스 버전의 모든 조합을 테스트할 수 없습니다. 따라서 올바른 아키텍처는 인터페이스 변경 사항에 대한 기본 빠른 피드백 메커니즘으로 계약 테스트를 사용하고 분산 거래 경계를 목표로 한 선택적 끝-to-끝 시나리오로 보완합니다.
여러 데이터베이스와 메시지 브로커에 걸쳐 분산 거래를 검증할 때 테스트 데이터 격리를 어떻게 처리해야 하나요?
대부분의 후보자들은 정리 스크립트가 포함된 공유 테스트 데이터베이스나 간단한 UUID 무작위화를 제안하지만, 마이크로서비스가 PostgreSQL, MongoDB 및 Kafka 주제를 동시에 여러 데이터 저장소에 기록할 때 단일 비즈니스 거래가 생성된다는 점을 고려하지 않습니다. 적절한 격리는 직접 데이터베이스를 지우는 대신 사가 보상 메커니즘을 통해 스타-와이프 패턴을 구현해야 하며, 이는 테스트가 참조 무결성을 유지하기 위해 프로덕션에서 사용하는 동일한 정리 워크플로를 호출하도록 보장합니다. 또한 시간을 제한한 테스트 컨텍스트를 통해 생성된 모든 데이터를 태그할 수 있도록 테스트 시작 시 주입된 분산 추적 헤더를 사용하여 서비스 간 외래 키 제약을 존중하면서도 이벤트 소싱 추가 전용 저장소를 고려한 정확한 정리 쿼리를 가능하게 해야 합니다.