Skip to content

Instantly share code, notes, and snippets.

@haf
Created May 2, 2013 16:16
Show Gist options
  • Select an option

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

Select an option

Save haf/5503321 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
    | Deleted
    | Success of StreamSlice
    /// Convert a <see cref="EventStore.ClientAPI.StreamEventsSlice" /> to a
    /// ConnectionApi.EventsSlice.
  and StreamSlice =
    | StreamSlice of Slice
    | EndOfStream
  and Slice =
    { Events          : ResolvedEvent list
    ; Stream          : StreamId
    ; FromEventNumber : uint32
    ; ReadDirection   : ReadDirection
    ; NextEventNumber : uint32
    ; LastEventNumber : uint32 }
  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.

  // 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment