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); }
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() }); }
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); }
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(); }
public async Task Trigger( ScheduledCommand scheduled, SchedulerAdvancedResult result, CommandSchedulerDbContext db) { await deliver(scheduled, result, db); }
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); }
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 }); }
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); }
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); }
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); }
private Task <string> GetClockName( IScheduledCommand scheduledCommand, CommandSchedulerDbContext dbContext) => Task.FromResult(getClockName()(scheduledCommand));
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)); }