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