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);
        }
Esempio n. 2
0
        private static void RescheduleIfAppropriate(
            ScheduledCommand storedCommand,
            ScheduledCommandResult result,
            CommandSchedulerDbContext db)
        {
            var failure = (CommandFailed)result;

            var now = Domain.Clock.Now();

            if (failure.IsCanceled || failure.RetryAfter == null)
            {
                // no further retries
                storedCommand.FinalAttemptTime = now;
            }
            else
            {
                storedCommand.DueTime = now + failure.RetryAfter;
            }

            db.Errors.Add(new CommandExecutionError
            {
                ScheduledCommand = storedCommand,
                Error            = result.IfTypeIs <CommandFailed>()
                                   .Then(f => f.Exception.ToJson())
                                   .ElseDefault()
            });
        }
Esempio n. 3
0
        private static async Task <Clock> GetOrAddSchedulerClock(
            CommandSchedulerDbContext db,
            string clockName,
            DateTimeOffset startTime)
        {
            var schedulerClock = await db.Clocks.SingleOrDefaultAsync(c => c.Name == clockName);

            if (schedulerClock != null)
            {
                return(schedulerClock);
            }

            Debug.WriteLine($"Creating clock '{clockName}' @ {startTime}");

            schedulerClock = new Clock
            {
                Name      = clockName,
                UtcNow    = startTime,
                StartTime = startTime
            };

            db.Clocks.Add(schedulerClock);
            await db.SaveChangesAsync();

            return(schedulerClock);
        }
Esempio n. 4
0
        internal async Task Trigger(
            ScheduledCommand scheduled,
            SchedulerAdvancedResult result,
            CommandSchedulerDbContext db)
        {
            var deliver = commandDispatchers.IfContains(scheduled.AggregateType)
                          .ElseDefault();

            if (deliver == null)
            {
                // QUESTION: (Trigger) is this worth raising a warning for or is there a reasonable chance that not registering a handler was deliberate?
                //                var error = ScheduledCommandFailure();
                //
                //                activity.OnNext(new CommandSchedulerActivity(scheduled, error));
                //
                //                result.Add(error);
                //                db.Errors.Add(error);
            }
            else
            {
                await deliver(scheduled);

                result.Add(scheduled.Result);
            }

            scheduled.Attempts++;

            await db.SaveChangesAsync();
        }
Esempio n. 5
0
 public async Task Trigger(
     ScheduledCommand scheduled,
     SchedulerAdvancedResult result,
     CommandSchedulerDbContext db)
 {
     await deliver(scheduled, result, db);
 }
Esempio n. 6
0
        private static async Task SaveScheduledCommandToDatabase <TAggregate>(
            CommandSchedulerDbContext db,
            ScheduledCommand storedScheduledCommand,
            IScheduledCommand <TAggregate> scheduledCommand)
        {
            var etag = new ETag
            {
                Scope             = scheduledCommand.TargetId,
                ETagValue         = scheduledCommand.Command.ETag,
                CreatedDomainTime = Domain.Clock.Now(),
                CreatedRealTime   = DateTimeOffset.UtcNow
            };

            while (true)
            {
                try
                {
                    await InsertScheduledCommandAndETag(db, storedScheduledCommand, etag);

                    break;
                }
                catch (Exception exception)
                {
                    if (!exception.IsConcurrencyException())
                    {
                        throw;
                    }

                    if (exception.ToString().Contains(@"object 'Scheduler.ScheduledCommand'"))
                    {
                        if (storedScheduledCommand.SequenceNumber < 0)
                        {
                            // for scheduler-assigned sequence numbers, decrement and retry
                            storedScheduledCommand.SequenceNumber--;
                        }
                        else
                        {
                            // this is not a scheduler-assigned sequence number
                            break;
                        }
                    }
                    else if (exception.ToString().Contains(@"object 'Scheduler.ETag'"))
                    {
                        scheduledCommand.Result = new CommandDeduplicated(
                            scheduledCommand,
                            "Schedule");
                        return;
                    }
                    else
                    {
                        throw;
                    }
                }
            }

            scheduledCommand.Result = new CommandScheduled(
                scheduledCommand,
                storedScheduledCommand.Clock);
        }
Esempio n. 7
0
        private MigrationResult Migrate(CommandSchedulerDbContext ctx)
        {
            var sql = @"
delete from Scheduler.ScheduledCommand
where AppliedTime is not null and AppliedTime < {0}";

            var numberOfRecords = ctx.Database.ExecuteSqlCommand(
                sql,
                cutoffDate);

            return(new MigrationResult
            {
                Log = $"Deleted {numberOfRecords} records\n{string.Format(sql, cutoffDate)}",
                MigrationWasApplied = true
            });
        }
Esempio n. 8
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);
        }
Esempio n. 9
0
        private static async Task <string> ClockNameForEvent(
            SqlCommandScheduler <TAggregate> sqlCommandScheduler,
            IScheduledCommand <TAggregate> scheduledCommandEvent,
            CommandSchedulerDbContext db)
        {
            var clockName = sqlCommandScheduler.GetClockName(scheduledCommandEvent);

            if (clockName == null)
            {
                var lookupValue = sqlCommandScheduler.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);
        }
Esempio n. 10
0
        private static async Task SaveScheduledCommandToDatabase <TAggregate>(
            CommandSchedulerDbContext db,
            ScheduledCommand storedScheduledCommand,
            IScheduledCommand <TAggregate> scheduledCommand)
        {
            db.ScheduledCommands.Add(storedScheduledCommand);
            db.ETags.Add(new ETag
            {
                Scope             = scheduledCommand.TargetId,
                ETagValue         = scheduledCommand.Command.ETag,
                CreatedDomainTime = Domain.Clock.Now(),
                CreatedRealTime   = DateTimeOffset.UtcNow
            });

            while (true)
            {
                try
                {
                    using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
                    {
                        await db.SaveChangesAsync();

                        transaction.Complete();
                    }
                    break;
                }
                catch (DbUpdateException exception)
                {
                    if (!exception.IsConcurrencyException())
                    {
                        throw;
                    }

                    if (exception.ToString().Contains(@"object 'Scheduler.ScheduledCommand'"))
                    {
                        if (storedScheduledCommand.SequenceNumber < 0)
                        {
                            // for scheduler-assigned sequence numbers, decrement and retry
                            storedScheduledCommand.SequenceNumber--;
                        }
                        else
                        {
                            // this is not a scheduler-assigned sequence number
                            break;
                        }
                    }
                    else if (exception.ToString().Contains(@"object 'Scheduler.ETag'"))
                    {
                        scheduledCommand.Result = new CommandDeduplicated(
                            scheduledCommand,
                            "Schedule");
                        return;
                    }
                    else
                    {
                        throw;
                    }
                }
            }

            scheduledCommand.Result = new CommandScheduled(
                scheduledCommand,
                storedScheduledCommand.Clock);
        }
Esempio n. 11
0
 private Task <string> GetClockName(
     IScheduledCommand scheduledCommand,
     CommandSchedulerDbContext dbContext) =>
 Task.FromResult(getClockName()(scheduledCommand));
Esempio n. 12
0
        private static async Task InsertScheduledCommandAndETag(CommandSchedulerDbContext db, ScheduledCommand storedScheduledCommand, ETag etag)
        {
            var sql = @"
INSERT [Scheduler].[ScheduledCommand] 
(
    [AggregateId], 
    [SequenceNumber], 
    [AggregateType],
    [CommandName], 
    [CreatedTime], 
    [DueTime], 
    [SerializedCommand], 
    [Attempts], 
    [Clock_Id]
)
VALUES 
(
    @aggregateId,
    @sequenceNumber,
    @aggregateType,
    @commandName,
    @createdTime,
    @dueTime,
    @serializedCommand,
    @attempts,
    @clock_Id
)

INSERT [Scheduler].[ETag]
(
    [Scope], 
    [ETagValue], 
    [CreatedDomainTime], 
    [CreatedRealTime]
)
VALUES 
(
    @scope,
    @eTagValue,
    @createdDomainTime,
    @createdRealTime
)";

            var parameters = new[]
            {
                // ScheduledCommand
                new SqlParameter("@aggregateId", storedScheduledCommand.AggregateId),
                new SqlParameter("@sequenceNumber", storedScheduledCommand.SequenceNumber),
                new SqlParameter("@aggregateType", storedScheduledCommand.AggregateType),
                new SqlParameter("@commandName", storedScheduledCommand.CommandName),
                new SqlParameter("@createdTime", storedScheduledCommand.CreatedTime),
                new SqlParameter("@dueTime", storedScheduledCommand.DueTime),
                new SqlParameter("@serializedCommand", storedScheduledCommand.SerializedCommand),
                new SqlParameter("@attempts", storedScheduledCommand.Attempts),
                new SqlParameter("@clock_Id", storedScheduledCommand.Clock.Id),
                // ETag
                new SqlParameter("@scope", etag.Scope),
                new SqlParameter("@eTagValue", etag.ETagValue),
                new SqlParameter("@createdDomainTime", etag.CreatedDomainTime),
                new SqlParameter("@createdRealTime", etag.CreatedRealTime)
            };

            await db.Database.ExecuteSqlCommandAsync(
                TransactionalBehavior.EnsureTransaction,
                sql,
                parameters);
        }
 private async Task <string> GetClockName(
     IScheduledCommand <TAggregate> scheduledCommand,
     CommandSchedulerDbContext dbContext)
 {
     return(getClockName()(scheduledCommand));
 }