private async Task RunEntity(EventSourcedInit init, IEventSourcedStatefulService statefulService, MessageStreamingContext <EventSourcedStreamIn, EventSourcedStreamOut> stream) { var entityId = init.EntityId; var entityHandler = statefulService.CreateEntityHandler( new EventSourcedContext(entityId, new AbstractContext(RootContext)) ); var startingSequenceNumber = 0L; var any = init?.Snapshot?.Snapshot; if (any != null) { var snapshotSequence = init.Snapshot.SnapshotSequence; var snapshotContext = new SnapshotContext( entityId, snapshotSequence, RootContext.ServiceCallFactory ); entityHandler.HandleSnapshot( any, snapshotContext ); startingSequenceNumber = snapshotSequence; } async Task ProcessStream(long sequence, EventSourcedStreamIn message) { switch (message.MessageCase) { case MessageOneofCase.Command: await HandleCommand(statefulService, stream, message, entityId, sequence, entityHandler); break; case MessageOneofCase.Event: var eventContext = new EventContext(entityId, message.Event.Sequence, new AbstractContext(RootContext)); entityHandler.HandleEvent(message.Event.Payload, eventContext); await stream.Response.WriteAsync(new EventSourcedStreamOut()); break; case MessageOneofCase.Init: throw new InvalidOperationException($"Entity [{entityId}] already initialized"); case MessageOneofCase.None: throw new InvalidOperationException($"Missing message"); default: throw new NotImplementedException("Unknown message case"); } } await stream.Request.SelectAsync(startingSequenceNumber, ProcessStream); }
public override async Task handle(IAsyncStreamReader <CrdtStreamIn> requestStream, IServerStreamWriter <CrdtStreamOut> responseStream, ServerCallContext context) { if (!await requestStream.MoveNext()) { return; } switch (requestStream.Current.MessageCase) { case CrdtStreamIn.MessageOneofCase.Command: case CrdtStreamIn.MessageOneofCase.Changed: case CrdtStreamIn.MessageOneofCase.Deleted: case CrdtStreamIn.MessageOneofCase.State: case CrdtStreamIn.MessageOneofCase.StreamCancelled: case CrdtStreamIn.MessageOneofCase.None: throw new InvalidOperationException( $"First message on entity stream is expected to be 'Init'. Received: [{requestStream.Current.MessageCase}]" ); case CrdtStreamIn.MessageOneofCase.Init: var init = requestStream.Current.Init; if (!CrdtStatefulServices.TryGetValue(init.ServiceName, out var crdtService) ) { throw new InvalidOperationException($"Failed to locate service with name {init.ServiceName}"); } try { var streamContext = new MessageStreamingContext <CrdtStreamIn, CrdtStreamOut>(requestStream, responseStream, context); await RunEntity(init, crdtService, streamContext); } catch (Exception ex) { Logger.LogError(ex, "Stream processing failed for entity."); // TODO: translate to response error throw; } break; default: throw new InvalidOperationException( $"First message on entity stream is expected to be 'Init'. Received unknown case." ); } }
private async Task RunEntity(CrdtInit init, ICrdtStatefulService service, MessageStreamingContext <CrdtStreamIn, CrdtStreamOut> stream) { var state = CrdtStateTransformer.Create(init.State, service.AnySupport).Some(); var runner = new EntityRunner(service, init.EntityId, state); Task ProcessStream(CrdtStreamIn message) { switch (message.MessageCase) { case CrdtStreamIn.MessageOneofCase.Command: runner.HandleCommand(message.Command); break; case CrdtStreamIn.MessageOneofCase.Changed: runner.HandleDelta(message.Changed); break; case CrdtStreamIn.MessageOneofCase.State: break; case CrdtStreamIn.MessageOneofCase.Deleted: break; case CrdtStreamIn.MessageOneofCase.StreamCancelled: break; case CrdtStreamIn.MessageOneofCase.None: case CrdtStreamIn.MessageOneofCase.Init: default: throw new NotImplementedException("Unknown message case"); } return(Task.CompletedTask); } await stream.Request.SelectAsync(ProcessStream); }
private async Task HandleCommand(IEventSourcedStatefulService statefulService, MessageStreamingContext <EventSourcedStreamIn, EventSourcedStreamOut> stream, EventSourcedStreamIn message, string entityId, long sequence, IEventSourcedEntityHandler eventSourcedEntityHandler) { var command = message.Command; if (command.EntityId != entityId) { throw new InvalidOperationException("Entity received a message was not intended for itself"); } var activatableContext = new AbstractActivatableContext(); var commandContext = new CommandContext( entityId, sequence, command.Name, command.Id, statefulService.AnySupport, eventSourcedEntityHandler, statefulService.SnapshotEvery, new AbstractContext(RootContext), new AbstractClientActionContext(command.Id, RootContext, activatableContext), new AbstractEffectContext(activatableContext), activatableContext ); var reply = Optional.Option.None <Any>(); try { // FIXME is this allowed to throw reply = eventSourcedEntityHandler.HandleCommand(command.Payload, commandContext); } catch (Exception ex) { switch (ex) { case FailInvokedException _: reply = Optional.Option.Some(Any.Pack(new Empty())); break; } } finally { activatableContext.Deactivate(); } var anyResult = reply.ValueOr(() => throw new InvalidOperationException("Command result was null")); var clientAction = commandContext.AbstractClientActionContext.CreateClientAction(reply, false); EventSourcedReply outReply; if (!commandContext.AbstractClientActionContext.HasError) { var endSequenceNumber = sequence + commandContext.Events.Count; var snapshot = Optional.Option.None <Any>(); if (commandContext.PerformSnapshot) { var s = eventSourcedEntityHandler.Snapshot( new SnapshotContext( entityId, endSequenceNumber, RootContext.ServiceCallFactory ) ); if (s.HasValue) { snapshot = s; } } outReply = new EventSourcedReply { CommandId = message.Command.Id, ClientAction = clientAction.ValueOr(() => throw new ArgumentNullException(nameof(clientAction))) }; outReply.SideEffects.Add(commandContext.AbstractEffectContext.SideEffects); outReply.Events.Add(commandContext.Events); // UNSAFE snapshot.MatchSome(x => outReply.Snapshot = x); } else { outReply = new EventSourcedReply { CommandId = message.Command.Id, ClientAction = new ClientAction { Reply = new Reply { Payload = anyResult } } }; } await stream.Response.WriteAsync( new EventSourcedStreamOut { Reply = outReply } ); }