private async Task <string> ClockNameForEvent(
            IScheduledCommand <TAggregate> scheduledCommandEvent,
            CommandSchedulerDbContext db)
        {
            // TODO: (ClockNameForEvent) clean this up
            var clockName =
                scheduledCommandEvent.IfTypeIs <IHaveExtensibleMetada>()
                .Then(e => ((object)e.Metadata)
                      .IfTypeIs <IDictionary <string, object> >()
                      .Then(m => m.IfContains("ClockName")
                            .Then(v => v.ToString())))
                .ElseDefault();

            if (clockName == null)
            {
                clockName = GetClockName(scheduledCommandEvent);

                if (clockName == null)
                {
                    var lookupValue = GetClockLookupKey(scheduledCommandEvent);
                    clockName = (await db.ClockMappings
                                 .Include(m => m.Clock)
                                 .SingleOrDefaultAsync(c => c.Value == lookupValue))
                                .IfNotNull()
                                .Then(c => c.Clock.Name)
                                .Else(() => SqlCommandScheduler.DefaultClockName);
                }
            }

            return(clockName);
        }
Exemple #2
0
        private static async Task <ScheduledCommand> GetStoredScheduledCommand <TAggregate>(
            IScheduledCommand <TAggregate> scheduledCommand,
            CommandSchedulerDbContext db,
            Guid scheduledCommandGuid) where TAggregate : class
        {
            var sequenceNumber = scheduledCommand
                                 .IfTypeIs <IEvent>()
                                 .Then(c => c.SequenceNumber)
                                 .Else(() => scheduledCommand
                                       .IfTypeIs <ScheduledCommand <TAggregate> >()
                                       .Then(c => c.SequenceNumber))
                                 .ElseThrow(() => new InvalidOperationException("Cannot look up stored scheduled command based on a " + scheduledCommand.GetType()));

            var storedCommand = await db.ScheduledCommands
                                .SingleOrDefaultAsync(
                c => c.AggregateId == scheduledCommandGuid &&
                c.SequenceNumber == sequenceNumber);

            return(storedCommand);
        }
Exemple #3
0
        internal static async Task <ScheduledCommand> StoreScheduledCommand <TAggregate>(
            IScheduledCommand <TAggregate> scheduledCommand,
            Func <CommandSchedulerDbContext> createDbContext,
            Func <IScheduledCommand <TAggregate>, CommandSchedulerDbContext, Task <string> > clockNameForEvent)
            where TAggregate : class
        {
            ScheduledCommand storedScheduledCommand;

            using (var db = createDbContext())
            {
                var domainTime = Domain.Clock.Now();

                // get or create a clock to schedule the command on
                var clock = scheduledCommand.Clock as Clock;
                if (clock == null)
                {
                    var clockName = await clockNameForEvent(scheduledCommand, db);

                    var clockStartTime = scheduledCommand.Clock?.Now() ??
                                         domainTime;

                    clock = await GetOrAddSchedulerClock(
                        db,
                        clockName,
                        clockStartTime);
                }

                storedScheduledCommand = CreateStoredScheduledCommand(
                    scheduledCommand,
                    domainTime,
                    clock);

                if (scheduledCommand.IsDue() &&
                    !scheduledCommand.Command.RequiresDurableScheduling())
                {
                    storedScheduledCommand.NonDurable = true;
                    return(storedScheduledCommand);
                }

                Debug.WriteLine(
                    $"Storing command '{scheduledCommand.Command.CommandName}' ({scheduledCommand.TargetId}:{storedScheduledCommand.SequenceNumber}) on clock '{clock.Name}'");

                await SaveScheduledCommandToDatabase(db,
                                                     storedScheduledCommand,
                                                     scheduledCommand);
            }

            scheduledCommand.IfTypeIs <ScheduledCommand <TAggregate> >()
            .ThenDo(c => c.SequenceNumber = storedScheduledCommand.SequenceNumber);

            return(storedScheduledCommand);
        }
Exemple #4
0
 private static ScheduledCommand CreateStoredScheduledCommand <TAggregate>(
     IScheduledCommand <TAggregate> scheduledCommand,
     DateTimeOffset domainTime,
     Clock schedulerClock)
     where TAggregate : class =>
 new ScheduledCommand
 {
     AggregateId    = ScheduledCommand <TAggregate> .TargetGuid(scheduledCommand),
     SequenceNumber = scheduledCommand
                      .IfTypeIs <IEvent>()
                      .Then(e => e.SequenceNumber)
                      .Else(() => - DateTimeOffset.UtcNow.Ticks),
     AggregateType     = Command.TargetNameFor(scheduledCommand.Command.GetType()),
     SerializedCommand = scheduledCommand.ToJson(),
     CreatedTime       = domainTime,
     DueTime           = scheduledCommand.DueTime ?? schedulerClock.Now(),
     Clock             = schedulerClock
 };
        private async Task Deliver(IScheduledCommand <TAggregate> scheduledCommand, bool durable)
        {
            IClock clock = null;

            if (scheduledCommand.DueTime != null)
            {
                clock = Domain.Clock.Create(() => scheduledCommand.DueTime.Value);
            }

            using (CommandContext.Establish(scheduledCommand.Command, clock))
            {
                Debug.WriteLine("SqlCommandScheduler.Deliver: " + Description(scheduledCommand));

                var repository = getRepository();

                var result = await repository.ApplyScheduledCommand(scheduledCommand,
                                                                    () => commandPreconditionVerifier.VerifyPrecondition(scheduledCommand));

                Activity.OnNext(result);

                scheduledCommand.IfTypeIs <CommandScheduled <TAggregate> >()
                .ThenDo(c => c.Result = result);

                if (!durable)
                {
                    return;
                }

                using (var db = createCommandSchedulerDbContext())
                {
                    var storedCommand = await db.ScheduledCommands
                                        .SingleAsync(c => c.AggregateId == scheduledCommand.AggregateId &&
                                                     c.SequenceNumber == scheduledCommand.SequenceNumber);

                    storedCommand.Attempts++;

                    if (result.WasSuccessful)
                    {
                        storedCommand.AppliedTime = Domain.Clock.Now();
                    }
                    else
                    {
                        var failure = (CommandFailed)result;

                        // reschedule as appropriate
                        var now = Domain.Clock.Now();
                        if (failure.RetryAfter != null)
                        {
                            Debug.WriteLine("SqlCommandScheduler.Deliver (scheduling retry): " + Description(scheduledCommand));
                            storedCommand.DueTime = now + failure.RetryAfter;
                        }
                        else
                        {
                            Debug.WriteLine("SqlCommandScheduler.Deliver (abandoning): " + Description(scheduledCommand));
                            // no further retries
                            storedCommand.FinalAttemptTime = now;
                        }

                        db.Errors.Add(new CommandExecutionError
                        {
                            ScheduledCommand = storedCommand,
                            Error            = result.IfTypeIs <CommandFailed>()
                                               .Then(f => f.Exception.ToJson()).ElseDefault()
                        });
                    }

                    await db.SaveChangesAsync();
                }
            }
        }