ES / CQRS 동시성 처리


20

최근에 직장에서 적용해야하므로 CQRS / ES로 뛰어 들기 시작했습니다. 많은 문제를 해결할 것이기 때문에 우리의 경우에는 매우 유망한 것으로 보입니다.

ES / CQRS 앱이 간단한 은행 사용 사례 (돈을 인출)에 상황에 맞는 것처럼 보이는 방법에 대한 대략적인 이해를 스케치했습니다.

ES / CQRS

요약하자면, A 개인이 돈을 인출하면 :

  • 명령이 내려졌다
  • 검증 / 검증을위한 명령이 전달됨
  • 유효성 검증에 성공하면 이벤트가 이벤트 저장소로 푸시됩니다.
  • 애그리 게이터는 이벤트를 대기열에서 제거하여 집계에 수정 사항을 적용합니다.

내가 이해 한 바에 따르면, 이벤트 로그는 사실의 원천입니다. FACTS의 로그이므로 이벤트 로그를 투영 할 수 있습니다.


자,이 위대한 계획에서 이해하지 못하는 것은이 경우에 일어나는 일입니다.

  • 규칙 : 잔액은 음수 일 수 없습니다
  • 사람 A의 잔액은 100e입니다
  • 개인 A가 100e의 철회 명령을 발행 함
  • 유효성 검사가 통과되고 100e 이벤트의 MoneyWithdrewEvent가 생성됩니다.
  • 그 동안 개인 A는 100e의 다른 철회 명령을 발행합니다.
  • 첫 번째 MoneyWithdrewEvent가 집계되지 않았으므로 집계에 대한 유효성 검사 (아직 업데이트되지 않은)에 대한 유효성 검사가 이루어지기 때문에 유효성 검사가 통과됩니다.
  • MoneyWithdrew100e의 이벤트가 다른 시간에 방출됩니다

==> 잔액이 -100e에 일치하지 않는 상태이며 로그에 MoneyWithdrewEvent가 2 개 있습니다.

이 문제를 해결하기위한 몇 가지 전략이 있습니다.

  • a) 이벤트 버전에서 이벤트와 함께 집계 버전 ID를 넣어 수정시 버전이 일치하지 않으면 아무 일도 일어나지 않습니다.
  • b) 검증 계층이 어떻게 든 하나를 생성해야 함을 암시하는 일부 잠금 전략을 사용하십시오.

전략과 관련된 질문 :

  • a)이 경우, 이벤트 로그는 더 이상 진실의 원천이 아니며 처리 방법은 무엇입니까? 또한 인출을 허용하는 것은 완전히 잘못된 반면 클라이언트로 돌아 왔습니다.이 경우 잠금을 사용하는 것이 더 낫습니까?
  • b) 잠금 == 교착 상태, 모범 사례에 대한 통찰력이 있습니까?

전반적으로, 동시성을 처리하는 방법에 대한 이해가 정확합니까?

참고 : 나는 같은 짧은 시간에 돈을 두 번 인출하는 것이 불가능하다는 것을 이해하지만 세부 사항을 잃지 않기 위해 간단한 예를 들었습니다.


7 단계까지 기다리지 않고 4 단계에서 집계를 업데이트하지 않는 이유는 무엇입니까?
Erik Eidt

따라서이 경우 이벤트 저장소는 응용 프로그램을 시작할 때 집계 / 기타 투영을 다시 작성하기 위해 읽을 수있는 로그 일 뿐입니 까?
Louis F.

답변:


19

ES / CQRS 앱이 간단한 은행 사용 사례 (돈을 인출)에 상황에 맞는 것처럼 보이는 방법에 대한 대략적인 이해를 스케치했습니다.

이것은 이벤트 소스 응용 프로그램의 완벽한 예입니다. 시작하자.

명령이 처리되거나 재 시도 될 때마다 (인내심을 가지고) 다음 단계가 수행됩니다.

  1. 명령이 명령 핸들러, 즉의 서비스에 도달합니다 Application layer.
  2. 명령 핸들러 Aggregate는 저장소를 식별하여 저장소에서로드합니다 (이 경우로드는 인스턴스 를 new호출 Aggregate하고이 집계에서 이전에 생성 된 모든 이벤트를 가져 와서 집계 자체에 다시 적용하여 수행됨) . 나중에 사용; 이벤트가 적용된 후 집계는 최종 상태에 있습니다 (즉, 현재 계정 잔액은 숫자로 계산 됨)
  3. 명령 핸들러는에 적절한 방법을 호출 Aggregate처럼, Account::withdrawMoney(100)그리고 굴복 이벤트, 즉 수집 MoneyWithdrewEvent(AccountId, 100); 계좌에 돈이 충분하지 않으면 (잔액 <100) 예외가 발생하고 모든 것이 중단됩니다. 그렇지 않으면 다음 단계가 수행됩니다.
  4. 명령 핸들러는 Aggregate저장소 를 유지하려고 시도 합니다 (이 경우 저장소는 Event Store). 그것은에 새 이벤트를 추가하여이를 Event stream경우에만 경우 version의가 Aggregate여전히이 때 있던 일입니다 Aggregate로드합니다. 버전이 동일하지 않으면 명령이 재 시도 됩니다. 1 단계로 이동하십시오 . 이 version동일하면 이벤트가에 추가되고 Event stream클라이언트에 Success상태 가 제공됩니다 .

이 버전 확인을 낙관적 잠금 이라고 하며 일반적인 잠금 메커니즘입니다. 다른 메커니즘 중 하나는 현재 쓰기가 완료 될 때까지 다른 쓰기가 차단 될 때 비관적 잠금 입니다.

이 용어 Event stream는 동일한 집계에서 생성 된 모든 이벤트에 대한 추상화입니다.

Event store이는 최종 상태뿐만 아니라 집계에 대한 모든 변경 사항이 저장되는 또 다른 종류의 지속성 이라는 것을 이해해야합니다 .

a)이 경우, 이벤트 로그는 더 이상 진실의 원천이 아니며 처리 방법은 무엇입니까? 또한 인출을 허용하는 것은 완전히 잘못된 반면 클라이언트로 돌아 왔습니다.이 경우 잠금을 사용하는 것이 더 낫습니까?

이벤트 저장소는 항상 진실의 원천입니다.

b) 잠금 == 교착 상태, 모범 사례에 대한 통찰력이 있습니까?

낙관적 잠금을 사용하면 잠금이없고 명령 재 시도 만 가능합니다.

어쨌든, 잠금! = 교착 상태


2
Aggregate모든 이벤트를 적용하지는 않지만 Aggregate이전 시점까지 의 스냅 샷을 유지하고 해당 시점 이후에 발생한 이벤트 만 적용 하는로드의로드와 관련하여 일부 최적화 가 있습니다 .
Constantin Galbenu

Ok 나는 이벤트 저장소 == 이벤트 버스 (kafka를 염두에두고 있음) 때문에 많은 혼란을 겪습니다. 따라서 많은 이벤트를 다시 읽어야하므로 집계를 재구성하는 데 비용이 많이들 수 있습니다. 의 스냅 샷이있는 Aggregate경우 스냅 샷을 언제 업데이트해야합니까? 스냅 샷 저장소가 이벤트 저장소와 동일하거나 이벤트 버스에서 파생 된 구체화 된 뷰입니까?
Louis F.

스냅 샷 생성에 대한 몇 가지 전략이 있습니다. 하나는 n 개의 이벤트마다 스냅 샷을 만드는 것입니다. 동일한 커밋에서 동일한 장소 / 지속성 / 데이터베이스에 이벤트와 함께 스냅 샷을 저장해야합니다. 아이디어는 스냅 샷이 집계 버전과 밀접한 관련이 있다는 것입니다.
Constantin Galbenu

좋아, 나는 이것을 처리하는 방법에 대한 명확한 비전을 가지고 있다고 생각합니다. 마지막 질문, 결국 이벤트 버스의 역할은 무엇입니까? 집계가 동기식으로 업데이트되는 경우
Louis F.

1
예, RabbitMQ 또는 이벤트를 읽기 모델에 비동기식으로 보내려는 채널을 이벤트 저장소에 유지 한 후에 만 ​​사용할 수 있습니다. 실제로 이벤트가 지속 된 후에는 이벤트 유효성 검사가 수행되지 않습니다. 이벤트는 발생한 사실을 나타냅니다. 읽기 모델은 어떤 일이 일어난 것을 좋아할 수도 있고 싫어할 수도 있지만 기록을 변경할 수는 없습니다.
Constantin Galbenu

1

ES / CQRS 앱이 간단한 은행 사용 사례 (돈을 인출)에 상황에 맞는 것처럼 보이는 방법에 대한 대략적인 이해를 스케치했습니다.

닫기. 문제는 "집계"를 업데이트하기위한 논리가 이상한 장소에 있다는 것입니다.

보다 일반적인 구현은 명령 핸들러가 메모리에 유지하는 데이터 모델과 이벤트 저장소의 이벤트 스트림이 동기화 된 상태로 유지되는 것입니다.

설명하기 쉬운 예는 명령 핸들러가 이벤트 저장소에 동기 쓰기를 작성하고 이벤트 저장소에 대한 연결이 쓰기 성공을 표시하는 경우 모델의 로컬 사본을 업데이트하는 경우입니다.

명령 핸들러가 내부 모델이 상점의 모델과 일치하지 않기 때문에 이벤트 저장소와 재 동기화해야하는 경우, 상점에서 히스토리를로드하고 자체 내부 상태를 다시 빌드하여이를 수행합니다.

다시 말해, 화살표 2 & 3 (있는 경우)은 일반적으로 집계 저장소가 아닌 이벤트 저장소에 연결됩니다.

이벤트 버전에서 이벤트와 함께 집계 버전 ID를 넣습니다. 수정시 버전이 일치하지 않으면 아무 일도 일어나지 않습니다.

이벤트 스트림의 스트림에 추가 하는 것이 아니라 일반적으로 스트림 의 특정 위치에 PUT 을 적용합니다. 해당 작업이 저장소 상태와 호환되지 않으면 쓰기가 실패하고 서비스는 적절한 실패 모드 (클라이언트 실패, 재시도, 병합 ...)를 선택할 수 있습니다. dem 등원 쓰기를 사용하면 분산 메시징의 여러 문제가 해결되지만 i 등원 쓰기를 지원하는 저장소가 있어야합니다.


흠, 나는 이벤트 저장소 구성 요소를 잘못 이해했다고 생각합니다. 나는 모든 것이 그것을 통과하고 스트리밍되어야한다고 생각했습니다. 이벤트 저장소가 kafka이고 읽기 전용 인 경우 어떻게합니까? 2 단계와 3 단계에서 모든 메시지를 다시 살펴볼 여유가 없습니다. 전반적으로 나의 비전은 이것과 일치하는 것 같습니다 : medium.com/technology-learning/…
Louis F.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.