마이크로 서비스 데이터베이스 시드


10

모델을 제어하는 ​​서비스 A (CMS) (제품, ID, 제목, 가격) 및 지정된 모델을 표시해야하는 서비스 B (배송) 및 C (이메일) 필드 만 가정해야합니다. 이벤트 소싱 접근 방식으로 해당 서비스에서 특정 모델 정보를 동기화하는 방법 제품 카탈로그가 거의 변경 되지 않지만 변경 사항은 거의 없으며 배송 및 전자 메일 데이터에 매우 자주 액세스 할 수있는 관리자가 있다고 가정합니다 (예 : 기능 : B : display titles of products the order contained및 C :) display content of email about shipping that is going to be sent. 각 서비스에는 자체 DB가 있습니다.

해결책 1

이벤트 내 제품에 대한 모든 필수 정보를 전송하십시오. 이는 다음과 같은 구조를 의미합니다 order_placed.

{
    order_id: [guid],
    product: {
        id: [guid],
        title: 'Foo',
        price: 1000
    }
}

서비스 B 및 C 제품 정보는 테이블의 productJSON 속성에 저장됩니다.orders

따라서 필요한 정보를 표시하기 위해 이벤트에서 검색된 데이터 만 사용됩니다

문제점 : B와 C에 제시해야 할 다른 정보에 따라 이벤트시 데이터 양이 늘어날 수 있습니다. B와 C는 제품에 대해 동일한 정보를 요구하지 않을 수 있지만, 이벤트를 둘로 분리하지 않는 한 이벤트에 둘 다 포함해야합니다. 주어진 이벤트에 지정된 데이터가 없으면 코드에서 사용할 수 없습니다 . B 및 C의 기존 주문에 대해 지정된 제품에 색상 옵션을 추가 하면 이벤트를 업데이트 한 다음 다시 실행하지 않으면 주어진 제품이 무색이됩니다. .

해결책 2

이벤트 내에서 제품의 안내 만 전송-이는 다음과 같은 구조를 의미합니다 order_placed.

{
    order_id: [guid],
    product_id: [guid]
}

서비스 B 및 C 제품 정보는 테이블의 product_id속성에 저장됩니다.orders

A/product/[guid]엔드 포인트에 대한 API 호출을 수행하여 필요할 때 서비스 B 및 C가 제품 정보를 검색 합니다.

문제 : 이것은 B와 C를 A에 항상 의존하게 만듭니다 (항상). 제품 스키마가 A에서 변경되면 해당 제품에 의존하는 모든 서비스에 대해 변경을 수행해야합니다 (갑자기)

해결책 3

이벤트 내에서 제품의 안내 만 발송-order_placed에 대한 다음 구조를 의미합니다.

{
    order_id: [guid],
    product_id: [guid]
}

서비스 B 및 C 제품 정보는 products테이블에 저장됩니다 . 아직 거기 product_idorders테이블 만 복제 거기에 productsA, B와 C 사이의 데이터; B와 C는 A와는 다른 제품 정보를 포함 할 수 있습니다

제품 정보는 서비스 B 및 C가 작성 될 때 시드되며 A/product엔드 포인트 를 호출 하거나 (모든 제품의 필수 정보를 표시 함) A에 대한 직접 DB 액세스를 수행하고 제공된 필수 제품 정보를 복사하여 제품에 대한 정보가 변경 될 때마다 업데이트 됩니다. 서비스.

문제점 : 이것은 B와 C를 A에 의존하게 만듭니다 (씨앗 때). 제품 스키마가 A에서 변경되는 경우 제품에 의존하는 모든 서비스 (시딩시)에서 변경을 수행해야합니다.


내 이해에서 올바른 접근법은 솔루션 1을 사용하고 특정 논리마다 이벤트 기록을 업데이트하는 것입니다 (제품 카탈로그가 변경되지 않고 표시 할 색상을 추가하려는 경우 기록을 안전하게 업데이트하여 현재 상태를 얻을 수 있음) 제품 카탈로그 및 이벤트 내에서 누락 된 데이터 채우기) 또는 지정된 데이터가 존재하지 않음 (제품 카탈로그가 변경되어 표시 할 색상을 추가하려는 경우 해당 시점에서 과거에 제공된 제품이 있는지 확실하지 않음) 색상이 있었는지 여부-이전 카탈로그의 모든 제품이 검은 색이며 이벤트 또는 코드를 업데이트하여 제공한다고 가정 할 수 있습니다)


관련하여 updating event history-이벤트 소싱 이벤트 기록은 진실의 원천이며 절대 변경해서는 안되며 앞으로 나아갑니다. 이벤트가 변경되면 이벤트 버전 관리 또는 유사한 솔루션을 사용할 수 있지만 특정 시점까지 이벤트를 재생할 때 데이터 상태는 해당 시점의 상태 여야합니다.
아니오

질의를위한 데이터 (스키마 등) 및 추가 / 제거되는 필드 등을 저장하는 것과 관련하여 우리는 그 당시 JSON으로 데이터를 저장하는 cosmosDB를 사용했습니다. 버전 관리가 필요한 유일한 것은 이벤트 및 / 또는 명령입니다. 또한 클라이언트 (웹, 모바일 등)의 쿼리에 응답하는 데이터가 포함 된 엔드 포인트 계약 및 가치 개체를 업데이트해야합니다. 필드가없는 오래된 데이터는 기본값이나 비어 있습니다. 이는 비즈니스에 적합하지만 이벤트 기록은 그대로 유지되며 앞으로 나아갑니다.
아니오

@Nope의 updating event history의미 : 모든 이벤트를 통해 한 스트림 (v1)에서 다른 스트림 (v2)으로 복사하여 일관된 이벤트 스키마를 유지하십시오.
eithed

또한, 상거래 / 전자 상거래 영역에서 가격이 자주 변경되는 경우 명시된대로 가격을 캡처 할 수 있습니다. 사용자에게 표시되는 가격은 실제 주문이 캡처 될 때 다를 수 있습니다. 문제를 해결하는 방법에는 여러 가지가 있지만 고려해야 할 방법입니다.
CPerson

@CPerson yup-가격은 이벤트 자체에서 전달되는 속성 중 하나 일 수 있습니다. 반면에, 이미지의 URL은 (의 의도를 나타내는 이벤트 내에서 존재할 수 display image at the point when purchase was made) 또는 (의 의도를 나타내는 수 없습니다 display current image as it within catalog)
eithed

답변:


3

솔루션 # 3은 실제로 올바른 아이디어에 가깝습니다.

이것을 생각하는 방법 : B와 C는 각각 필요한 데이터의 "로컬"복사본을 캐싱 합니다. B (및 C)에서 처리 된 메시지는 로컬로 캐시 된 정보를 사용합니다. 마찬가지로 로컬로 캐시 된 정보를 사용하여 보고서가 생성됩니다.

데이터는 안정적인 API를 통해 소스에서 캐시로 복제됩니다. B와 C는 동일한 API를 사용할 필요조차 없습니다. 필요에 맞는 페치 프로토콜을 사용합니다. 실제로 공급자와 소비자를 제한하는 계약 (프로토콜 및 메시지 스키마)을 정의합니다. 그러면 해당 계약의 모든 소비자를 모든 공급 업체에 연결할 수 있습니다. 이전 버전과 호환되지 않는 변경에는 새로운 계약이 필요합니다.

서비스는 필요에 따라 적절한 캐시 무효화 전략을 선택합니다. 이는 정기적으로 소스에서 변경 사항을 가져 오거나 상황이 변경되었거나 "요청시"알림에 응답하여 캐시를 통한 읽기로 작동하여 저장된 데이터 사본으로 폴백하는 것을 의미합니다. 소스를 사용할 수 없습니다.

이는 A를 일시적으로 사용할 수 없을 때 B와 C가 계속 비즈니스 가치를 제공 할 수 있다는 의미에서 "자율"을 제공합니다.

권장 자료 : 외부 자료, 내부 자료 , Pat Helland 2005.


예, 여기에 작성한 내용에 완전히 동의하며 솔루션 3은 내가 적용 한 goto 솔루션이지만 이벤트를 재생하는 경우 이벤트 소싱 접근 방식이 아닙니다. 제품의 현재 상태를 사용하십시오. 이벤트 시점의 상태를 사용하려고합니다. 물론 이것은 비즈니스 요구 사항에 따라 괜찮을 수도 있습니다. 그러나 우리는뿐만 아니라 그 소싱 이벤트를 필요 카탈로그에 대한 변경 사항을 추적, 종속을 얼마나 많은 데이터를 유지하려면, 우리는 더 나은 솔루션 1. 다시 떨어지는 수도
eithed

1
나는 당신이 솔루션 # 3을 가지고 있다고 생각합니다. 카탈로그와의 일관성을 재생해야하는 경우 이벤트 소스도 마찬가지입니다. 다시 시작했을 때만 재생할 수 있습니다. 시작했을 가능성이 높습니다. 일단 가동되면 새로운 이벤트 만 검토하면되므로 데이터 양은 실제로 문제가되지 않습니다. 그러나 체크 포인트를 사용하는 옵션 (필요한 경우), 즉 " 이벤트 1,000 의 상태 가 여기에 있습니다 "가 있으므로 이제 전체 히스토리 대신 이벤트 1,001- 현재로만 재생하면됩니다. .
Mike B.

2

Computer Science 에는 두 가지 어려운 점이 있으며 그 중 하나는 캐시 무효화입니다.

솔루션 2는 절대적으로 기본 위치이며 다음 시나리오 중 하나에 해당하는 경우 일반적으로 캐싱 구현을 고려해야합니다.

  1. 서비스 A에 대한 API 호출로 인해 성능 문제가 발생했습니다.
  2. 서비스 A가 다운되고 데이터를 검색 할 수없는 비용은 비즈니스에 중요합니다.

성능 문제는 실제로 주요 드라이버입니다. 서비스 A의 가용성을 높이는 것과 같이 캐싱을 포함하지 않는 # 2를 해결하는 방법에는 여러 가지가 있습니다.

캐싱은 시스템에 상당한 복잡성을 추가하고 추론하기 어려운 엣지 케이스와 복제하기 매우 어려운 버그를 생성 할 수 있습니다. 또한 새로운 데이터가 존재하는 경우 오래된 데이터를 제공 할 위험을 완화해야합니다. 예를 들어 "서비스 A가 다운되었습니다. 나중에 다시 시도하십시오"라는 메시지를 표시하는 것보다 비즈니스 관점에서 훨씬 더 나쁠 있습니다.

에서 이 우수한 기사 우디 다한하여 :

이러한 종속성은 신발 끈을 묶어 천천히 개발 속도를 늦추어 시스템의 한 부분을 변경하면 다른 부분이 깨지는 코드베이스의 안정성을 저하시킵니다. 천 번의 컷으로 인한 사망은 느리기 때문에 아무도 우리가 어떤 큰 결정을 내려서 모든 일이 그렇게 나 빠지게했는지 정확히 확신하지 못합니다.

또한 제품 데이터에 대한 특정 시점 쿼리가 필요한 경우 데이터가 제품 데이터베이스에 저장되는 방식 (예 : 시작 / 종료 날짜)으로 처리되어야하며 API에 명확하게 노출되어야합니다 (유효 날짜는 데이터를 쿼리하기위한 API 호출에 대한 입력이어야합니다.


1
@SavvasKleanthous "네트워크는 신뢰할 수있다"는 분산 컴퓨팅의 오류 중 하나입니다. 그러나 그 오류에 대한 응답이 "다른 모든 서비스의 모든 서비스에서 모든 비트의 데이터를 캐시"해서는 안됩니다 (나는 약간 쌍곡 적이라는 것을 알고 있습니다). 서비스를 사용할 수 없을 것으로 예상하고이를 오류 조건으로 처리하십시오. 다운되는 서비스 A가 비즈니스에 큰 영향을 미치는 드문 상황 인 경우 다른 옵션을 신중하게 고려하십시오.
Phil Sandler

1
@SavvasKleanthous는 (내 대답에서 언급했듯이) 많은 경우 오래된 데이터를 반환 하는 것이 오류를 던지는 것보다 훨씬 나쁠 수 있다고 생각 합니다.
Phil Sandler

1
@eithed 나는이 의견을 언급했다 : "그러나 만약 우리가 카탈로그에 대한 변경 사항을 추적하고 싶다면 이벤트 소싱도 필요하다". 어쨌든 제품 서비스는 다운 스트림 서비스가 아니라 시간이 지남에 따라 변경 사항을 추적해야합니다.
Phil Sandler

1
또한 관찰 한 데이터를 저장하면 캐싱과 일부 유사하지만 동일한 문제가 발생하지 않습니다. 보다 구체적으로 무효화는 필요하지 않다. 데이터가 발생하면 새로운 버전의 데이터를 얻습니다. 당신이 경험하는 것은 지연된 일관성입니다. 그러나 웹 요청을 사용하더라도 불일치의 창이 있습니다 (작지만).
Savvas Kleanthous

1
@SavvasKleanthous 어쨌든 내 주요 요점은 아직 존재하지 않는 문제, 특히 자체 문제와 위험을 초래하는 솔루션을 해결하려고 시도하지 않는 것입니다. 옵션 2가 가장 간단한 솔루션이며 비즈니스 요구 사항을 충족하지 않을 때까지 기본적으로 선택해야합니다 . 작동 할 수있는 가장 간단한 솔루션을 선택하는 것이 "정말 나쁘다"고 생각한다면, 우리는 동의하지 않는다고 생각합니다.
Phil Sandler

2

한 솔루션이 다른 솔루션보다 낫다고 말하기는 매우 어렵습니다. 솔루션 # 2와 # 3 중 하나를 선택하는 것은 다른 요소 (캐시 기간, 일관성 허용 범위 등)에 따라 다릅니다.

내 2 센트 :

캐시 무효화는 어려울 수 있지만 문제 설명에서는 제품 카탈로그가 거의 변경되지 않는다고 언급합니다. 이 사실은 제품 데이터를 캐싱에 적합한 후보로 만듭니다.

솔루션 # 1 (NOK)

  • 여러 시스템에 걸쳐 데이터가 복제 됨

솔루션 # 2 (확인)

  • 강력한 일관성 제공
  • 제품 서비스의 가용성이 높고 우수한 성능을 제공하는 경우에만 작동
  • 이메일 서비스가 많은 제품과 함께 요약을 준비하면 전체 응답 시간이 더 길어질 수 있습니다

솔루션 # 3 (복잡하지만 선호)

  • 제품 정보를 검색하기 위해 직접 DB 액세스 대신 API 접근 방식 선호
  • 탄력적-제품 서비스가 다운 되어도 소비 서비스는 영향을받지 않습니다
  • 소비 애플리케이션 (배송 및 이메일 서비스)은 이벤트가 게시 된 직후 제품 세부 정보를 검색합니다. 몇 밀리 초 내에 제품 서비스가 중단 될 가능성은 매우 먼 곳입니다.

1

일반적으로 말해서, 두 서비스 사이의 시간적 결합으로 인해 옵션 2에 대해 강력히 권장합니다 (이러한 서비스 간의 통신이 매우 안정적이며 자주 발생하지 않는 한). 시간적 커플 링은 설명하는 것으로 this makes B and C dependant upon A (at all times), A가 B 또는 C에서 작동 중지되거나 도달 할 수없는 경우 B 및 C가 기능을 수행 할 수 없음을 의미합니다.

개인적으로 옵션 1과 3 모두 유효한 옵션 인 상황이 있다고 생각합니다.

A와 B & C 사이의 통신이 너무 높거나 이벤트에 들어가는 데 필요한 데이터의 양이 문제가 될 정도로 충분히 큰 경우, 네트워크 부담이 훨씬 적기 때문에 옵션 3이 가장 좋습니다. 메시지 크기가 줄어들면 작업 대기 시간이 줄어 듭니다. 여기에서 고려해야 할 다른 사항은 다음과 같습니다.

  1. 계약의 안정성 : 메시지 계약이 A를 떠나는 경우가 자주 바뀌면 메시지에 많은 속성을 넣으면 소비자가 많이 변경됩니다. 그러나이 경우 나는 이것이 큰 문제가 아니라고 생각합니다.
    1. 시스템 A가 CMS라고 언급했습니다. 이것은 당신이 안정적인 도메인에서 일하고 있다는 것을 의미하며, 나는 당신이 자주 변경되는 것을 믿지 않습니다.
    2. B와 C는 배송 및 이메일이며 A로부터 데이터를 받고 있기 때문에 변경하지 않고 추가 변경이 발생한다고 생각합니다. 재 작업없이 발견 할 때마다 안전하게 추가 할 수 있습니다.
  2. 커플 링 : 커플 링이 거의 없거나 전혀 없습니다. 통신은 메시지를 통해 이루어지기 때문에 데이터를 시드하는 동안 짧은 시간 서비스 이외의 서비스와 해당 작업의 계약 간에는 커플 링이 없습니다 (이는 피할 수 있거나 피해야하는 커플 링이 아님)

옵션 1은 내가 무시하는 것이 아닙니다. 동일한 양의 커플 링이 있지만 개발 측면에서는 수행하기 쉽고 (특별한 조치가 필요하지 않음) 도메인의 안정성으로 인해 이미 언급 한 것처럼 자주 변경되지 않습니다.

내가 제안하는 또 다른 옵션은 시작하는 동안 프로세스를 실행하지 않고 제품 카탈로그에 변경이있을 때 B와 C에서 "ProductAdded 및"ProductDetailsChanged "이벤트를 관찰하는 3의 약간의 변형입니다. 이렇게하면 배포 속도가 빨라지고 문제가있는 경우 문제 / 버그를 쉽게 수정할 수 있습니다.


2020-03-03 수정

통합 접근법을 결정할 때 특정 우선 순위가 있습니다.

  1. 일관성 비용은 얼마입니까? A에서 변경된 것들과 B & C에 반영되는 것들 사이에 몇 밀리 초의 불일치를 허용 할 수 있습니까?
  2. 특정 시점 쿼리 (시간 쿼리라고도 함)가 필요합니까?
  3. 데이터에 대한 진실의 근원이 있습니까? 그것들을 소유하고 업스트림으로 간주되는 서비스?
  4. 소유자 / 단일 진실의 근원이 있다면 그 것이 안정적입니까? 아니면 잦은 변경이있을 것으로 예상됩니까?

불일치 비용이 높으면 (기본적으로 A의 제품 데이터는 가능한 빨리 B와 C에 캐시 된 제품과 일관성이 있어야 함), 사용 불가를 수용하고 웹과 같은 동기식 요청을 할 필요가 없습니다. / rest request)를 B & C에서 A로 가져 와서 데이터를 가져옵니다. 알아 두세요! 이것은 여전히 ​​트랜잭션 일관성을 의미하지는 않지만 불일치의 창을 최소화합니다. 절대적으로 즉시 일관성을 유지해야한다면 서비스 경계를 ​​바꿔야합니다. 그러나, 나는 매우 강하게이 문제가되지 않습니다 생각합니다. 경험상 실제로 회사가 몇 초간의 불일치를 받아 들일 수 없기 때문에 동기식 요청을 할 필요조차 없습니다.

특정 시점 쿼리가 필요한 경우 (귀하의 질문에 눈치 채지 못하여 위의 내용을 포함하지 않았을 수 있음) 다운 스트림 서비스에서 유지 관리 비용이 너무 높습니다 (복제해야합니다) 의사 결정을 명확하게하는 모든 다운 스트림 서비스의 내부 이벤트 프로젝션 로직 당시 상태로 투영하고 반환합니다. 나는 이것이 옵션 2 일 수 있다고 생각하지만 (정확하게 이해한다면?) 시간적 결합이 겹친 사건과 투영 논리의 유지 보수 비용보다 낫지 만 비용은 비싸다.

특정 시점이 필요하지 않고 데이터의 명확한 단일 소유자가없는 경우 (초기 답변에서 귀하의 질문에 근거하여 가정 한 경우) 매우 합리적인 패턴이 표현을 유지하는 것입니다 각 서비스에서 제품의 개별. 제품 데이터를 업데이트 할 때 각각 웹 요청을 병렬 처리하여 A, B 및 C를 병렬로 업데이트하거나 A, B 및 C 각각에 여러 명령을 보내는 명령 API가 있습니다. 작업을 수행하기위한 데이터의 로컬 버전. A, B 및 C의 데이터가 다를 수 있고 제품의 "전체"가 세 데이터 모두의 구성 일 수 있으므로 이는 위의 옵션 중 어느 것도 아닙니다 (옵션 3에 가깝게 만들 수는 있지만) 소스.

A와 서비스 B 및 C의 통합을 위해 도메인 / 내부 이벤트 (또는 이벤트 소싱을 A에서 스토리지 패턴으로 저장 한 이벤트)를 사용하는 데 사용할 수 있기 때문에 진실의 출처가 안정적인 계약인지를 아는 것이 유용합니다. 계약이 안정적인 경우 도메인 이벤트를 통해 통합 할 수 있습니다. 그러나 변경이 자주 발생하거나 메시지 계약이 전송에 문제를 일으킬 정도로 큰 경우에는 추가적인 문제가 있습니다.

안정적인 소유자가 있고 계약이 안정적 일 것으로 예상되는 경우 가장 좋은 옵션은 옵션 1입니다. 주문에는 필요한 모든 정보가 포함되며 B와 C는 해당 이벤트의 데이터를 사용하여 기능을 수행합니다.

옵션 3에 따라 계약이 변경되거나 자주 중단되는 경우 제품 데이터를 가져 오기 위해 웹 요청으로 돌아가는 것이 실제로 더 나은 옵션입니다. 여러 버전을 유지 관리하는 것이 훨씬 쉽기 때문입니다. 따라서 B는 v3 제품을 요청합니다.


예, 동의합니다. 동안 ProductAdded이나하는 ProductDetailsChanged제품 카탈로그를 추적하는 것은 우리가 재생되는 경우 이벤트에, 어떤 방법으로 데이터베이스간에 동기화 된 데이터를 유지하기 위해 필요한 변화의 복잡성을 추가하고 우리는 과거로부터 액세스 카탈로그 데이터가 필요합니다.
eithed

@ eithed 나는 몇 가지 가정을 확장하기 위해 대답을 업데이트했습니다.
Savvas Kleanthous
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.