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 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); }
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); }
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(); } } }