public CheckOutHttpClient(IOptions <CheckOutSettings> options)
 {
     _checkOutSettings = options.Value;
 }
Ejemplo n.º 2
0
        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");
            }));
        }