public CheckOutHttpClient(IOptions <CheckOutSettings> options) { _checkOutSettings = options.Value; }
public CheckOutStateMachine(CheckOutSettings settings, ILogger <CheckOutStateMachine> logger) { _logger = logger; Event(() => BookCheckedOut, x => x.CorrelateById(m => m.Message.CheckOutId)); Event(() => RenewCheckOut, x => { x.CorrelateById(m => m.Message.CheckOutId); // // 기존에 CheckOutId 에 해당하는 Saga가 없는 상태에서 RenewCheckOut 이 수신되면, // // Saga를 생성하는 대신, OnMissingInstance() 로 특별한 처리를 할 수 있다 // // 아래의 경우 *_error queue 로 빠진다. (그 과정에서 Retry Policy 에 따라 재시도도 이루어지게 할 수 있다) // x.OnMissingInstance(m => m.Fault()); // 사용자 경험을 좋게(~ Error가 발생되었다는 로그, 큐가 생성하지 않고) // 명시적으로 어떤 특별한 작업(예: 불완전한 상태에 대한 기술을 하는 메시지를 전송) 을 수행. x.OnMissingInstance(m => m.ExecuteAsync(async context => { // Publish, Respond, .... await context.RespondAsync <CheckOutNotFound>(new { CheckOutId = context.Message.CheckOutId }); })); }); Event(() => BookAddedToMemberCollection, x => x.CorrelateBy((saga, context) => saga.BookId == context.Message.BookId) ); Event(() => AddBookToMemberCollectionFaulted, // context.Message.Message.xxx 가 좀 이상해 보이지만, 첫번째는 Fault Message, 두번째는 이 Fault Message가 가지는 원본 메시지. x => x.CorrelateBy((saga, context) => saga.BookId == context.Message.Message.BookId) ); InstanceState(saga => saga.CurrentState); Initially( When(BookCheckedOut) .Then(context => { context.Instance.BookId = context.Data.BookId; context.Instance.CheckOutDate = context.Data.Timestamp; context.Instance.MemberId = context.Data.MemberId; context.Instance.DueDate = context.Instance.CheckOutDate + settings.DefaultCheckOutDuration; }) // DI Container 가 지원되도록 NotifyMemberActivity 를 생성/사용하려면. 아래처럼 하면 된다. .Activity(x => x.OfInstanceType <NotifyMemberActivity>()) // 다른 Consumer에게 작업요청.. .PublishAsync(x => x.Init <AddBookToMemberCollection>(new { MemberId = x.Instance.MemberId, BookId = x.Instance.BookId })) .TransitionTo(CheckedOut) ); During(CheckedOut, When(RenewCheckOut) .Then(context => { // 현재 시각을 기준으로 기한을 갱신한다. // --> 시간 관련된 것은 테스트 하기 어렵다. 따라서 Quartz 같은 애들이 제공하는 SystemTime 처럼 Mock이 용이한 것을 쓰는게 좋을듯. var now = SystemTime.UtcNow().DateTime; context.Instance.DueDate = now + settings.DefaultCheckOutDuration; }) // 꼭 `.IfElse()` 를 쓰지 않아도 되지만, 이렇게 하면 상태기계 시각화 모듈에 분기조건이 표시된다. .IfElse( context => context.Instance.DueDate > context.Instance.CheckOutDate + settings.CheckOutDurationLimit, // `ifLimit`, `otherwise` 처럼 plain english 를 쓴다. ifLimited => ifLimited .Then(context => { // 최대 허용 납기를 넘기지 않게 DueDate 를 조정한다. context.Instance.DueDate = context.Instance.CheckOutDate + settings.CheckOutDurationLimit; }) // 그리고... 적절한 응답 메시지를 보낸다. .RespondAsync(context => context.Init <CheckOutDurationLimitReached>(new { CheckOutId = context.Instance.CorrelationId, DueDate = context.Instance.DueDate })), otherwise => otherwise // DueDate 가 갱신되었으므로 ... 사용자에게 알린다음... .Activity(x => x.OfInstanceType <NotifyMemberActivity>()) // 응답메시지를 한번 날려보자. .RespondAsync(context => context.Init <CheckOutRenewed>(new { CheckOutId = context.Instance.CorrelationId, // = context.Data.CheckOutId DueDate = context.Instance.DueDate }))) ); DuringAny( When(BookAddedToMemberCollection) .Then(context => logger.LogInformation("@@@@@ 오.. 사용자 컬렉션에 이 책이 추가 됬네요.")), When(AddBookToMemberCollectionFaulted) .Then(context => logger.LogWarning("!!!!!!!! 음. 사용자 컬렉션에 추가가 안되네요!!!")) ); }
public CheckOutStateMachine(CheckOutSettings settings) { Event(() => BookCheckedOut, x => x.CorrelateById(m => m.Message.CheckOutId)); Event(() => AddedToCollection, x => x.CorrelateBy((instance, context) => instance.BookId == context.Message.BookId && instance.MemberId == context.Message.MemberId)); Event(() => AddToCollectionFaulted, x => x.CorrelateBy((instance, context) => instance.BookId == context.Message.Message.BookId && instance.MemberId == context.Message.Message.MemberId)); Event(() => RenewCheckOutRequested, x => x.OnMissingInstance(m => m.ExecuteAsync(a => a.RespondAsync <CheckOutNotFound>(a.Message)))); InstanceState(x => x.CurrentState, CheckedOut); Initially( When(BookCheckedOut) .Then(context => { context.Instance.BookId = context.Data.BookId; context.Instance.MemberId = context.Data.MemberId; context.Instance.CheckOutDate = context.Data.Timestamp; context.Instance.DueDate = context.Instance.CheckOutDate + settings.CheckOutDuration; }) .Activity(x => x.OfInstanceType <NotifyMemberActivity>()) .PublishAsync(x => x.Init <AddBookToMemberCollection>(new { x.Instance.MemberId, x.Instance.BookId })) .TransitionTo(CheckedOut)); During(CheckedOut, When(RenewCheckOutRequested) .Then(context => { context.Instance.DueDate = DateTime.UtcNow + settings.CheckOutDuration; }) .IfElse(context => context.Instance.DueDate > context.Instance.CheckOutDate + settings.CheckOutDurationLimit, exceeded => exceeded .Then(context => context.Instance.DueDate = context.Instance.CheckOutDate + settings.CheckOutDurationLimit) .RespondAsync(context => context.Init <CheckOutDurationLimitReached>(new { context.Data.CheckOutId, context.Instance.DueDate })), otherwise => otherwise .Activity(x => x.OfInstanceType <NotifyMemberActivity>()) .RespondAsync(context => context.Init <CheckOutRenewed>(new { context.Data.CheckOutId, context.Instance.DueDate }))) ); DuringAny(When(AddedToCollection) .Then(context => { })); DuringAny(When(AddToCollectionFaulted) .Then(context => { Console.WriteLine("Add to collection faulted"); })); }