Skip to content

Instantly share code, notes, and snippets.

@haf
Last active December 16, 2015 21:59
Show Gist options
  • Select an option

  • Save haf/5503331 to your computer and use it in GitHub Desktop.

Select an option

Save haf/5503331 to your computer and use it in GitHub Desktop.
Edge cases GetEventStore API

Some examples of edge cases that have to be watched out for in GetEventStore, starting with the read API. Here's the F#-API type for ReadStreamEventsForwardAsync:

  /// <see cref="EventStore.ClientAPI.StreamEventsSlice" />
  type StreamEventsSlice =
    | NotFound of StreamId
    | Deleted of StreamId
    | Success of StreamSlice
  /// Convert a <see cref="EventStore.ClientAPI.StreamEventsSlice" /> to a
  /// ConnectionApi.EventsSlice.
  and StreamSlice =
    | StreamSlice of Slice
    | EndOfStream of Slice
  and Slice =
    { Events          : ResolvedEvent list
    ; Stream          : StreamId
    ; FromEventNumber : uint32
    ; ReadDirection   : ReadDirection
    ; NextEventNumber : uint32
    ; LastEventNumber : uint32 }
  and StreamId = string
  and StreamId = string

Here's the corresponding decompiled API source:

  public class StreamEventsSlice
  {
    public readonly SliceReadStatus Status;
    public readonly string Stream;
    public readonly int FromEventNumber;
    public readonly ReadDirection ReadDirection;
    public readonly EventStore.ClientAPI.ResolvedEvent[] Events;
    public readonly int NextEventNumber;
    public readonly int LastEventNumber;
    public readonly bool IsEndOfStream;

    internal StreamEventsSlice(SliceReadStatus status, string stream, int fromEventNumber, ReadDirection readDirection, ClientMessage.ResolvedIndexedEvent[] events, int nextEventNumber, int lastEventNumber, bool isEndOfStream)
    {
      Ensure.NotNullOrEmpty(stream, "stream");
      this.Status = status;
      this.Stream = stream;
      this.FromEventNumber = fromEventNumber;
      this.ReadDirection = readDirection;
      if (events == null || events.Length == 0)
      {
        this.Events = Empty.ResolvedEvents;
      }
      else
      {
        this.Events = new EventStore.ClientAPI.ResolvedEvent[events.Length];
        for (int index = 0; index < this.Events.Length; ++index)
          this.Events[index] = new EventStore.ClientAPI.ResolvedEvent(events[index]);
      }
      this.NextEventNumber = nextEventNumber;
      this.LastEventNumber = lastEventNumber;
      this.IsEndOfStream = isEndOfStream;
    }
  }

Leaving aside the pain of having public APIs with internal c'tors (forcing this darling* to come to life), it is clear there are some implicit invariants:

  • We might get IsEndOfStream = true and then all other properties are pretty much possible to ignore. This has to be abducted.
  • The stream can both be not found (Status = StreamReadStatus.NotFound) and,
  • previously deleted (Status = StreamReadStatus.Deleted) or it can be
  • Found, having data.

Besides that, the call to ReadStreamEventsForwardAsync has two System.Int32 parameters, which always have to be zero or a positive number. Why not use System.UInt32? Also, passing a zero count makes no sense, so that should really be always a positive number.

It becomes even more interesting when passing expectedVersion and to tell the API -2 (int) for no optimistic concurrency checking, -1 for NoStream, 0 for empty stream, having runtime exceptions in all other negative cases, and on top of that, having a possibility of getting WrongExpectedVersionException as an exception on the asynchronous call, if the optimistic check doesn't pass.

footnote:

  // returns the constructor for the instance and a setter function, as a tuple, respectively
  let reflPkgFor<'a> () : (unit -> 'a) * ('a -> string -> obj -> unit) =
    let setterFor name = typeof<'a>.GetField(name, System.Reflection.BindingFlags.Instance ||| System.Reflection.BindingFlags.NonPublic)
    let memSetterFor = memoize setterFor
    let emptyCtor = fun () -> System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof<'a>) :?> 'a
    let setter (instance : 'a) prop (value : obj) = memSetterFor prop |> (fun fi -> fi.SetValue(instance, value))
    emptyCtor, setter
@haf
Copy link
Author

haf commented May 7, 2013

@anakryko

Thanks for your input. I noticed the same thing about EndOfStream (updated gist to my current code) - but it goes to show that the returned data doesn't show me how to use it in C#.

The comments that you added about LastEventNumber are interesting and definitely something hadn't heard of before. Thanks for telling me about it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment