Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EventId to IEventData re #35 #36

Merged
merged 3 commits into from
Feb 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ The `Unreleased` section name is replaced by the expected version of next releas
## [Unreleased]

### Added

- Add `EventId` to `IEventData` [#36](https://github.com/jet/FsCodec/pull/36)

### Changed

- Permit embedded dashes in `FsCodec.StreamName`'s `{aggregateId}` segment [#34](https://github.com/jet/FsCodec/pull/34)
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,13 +251,15 @@ type IEventData<'Format> =
abstract member Data : 'Format
/// Optional metadata (null, or same as Data, not written if missing)
abstract member Meta : 'Format
/// The Event's Creation Time (as defined by the writer, i.e. in a mirror, this is intended to reflect the original time)
/// <remarks>- For EventStore, this value is not honored when writing; the server applies an authoritative timestamp when accepting the write.</remarks>
abstract member Timestamp : System.DateTimeOffset
/// Application-generated identifier used to drive idempotent writes based on deterministic Ids and/or Request Id
abstract member EventId : System.Guid
/// The Correlation Id associated with the flow that generated this event. Can be `null`
abstract member CorrelationId : string
/// The Causation Id associated with the flow that generated this event. Can be `null`
abstract member CausationId : string
/// The Event's Creation Time (as defined by the writer, i.e. in a mirror, this is intended to reflect the original time)
/// <remarks>- For EventStore, this value is not honored when writing; the server applies an authoritative timestamp when accepting the write.</remarks>
abstract member Timestamp : System.DateTimeOffset
```

<a name="ITimelineEvent"></a>
Expand Down
18 changes: 9 additions & 9 deletions src/FsCodec.NewtonsoftJson/BoxCodec.fs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ type Codec private () =
up : FsCodec.ITimelineEvent<obj> * 'Contract -> 'Event,
/// Maps a fresh Event resulting from a Decision in the Domain representation type down to the TypeShape <c>UnionContract</c> <c>'Contract</c>
/// The function is also expected to derive a <c>meta</c> object that will be held alongside the data (if it's not <c>None</c>)
/// together with its <c>correlationId</c>, <c>causationId</c> and an event creation <c>timestamp</c> (defaults to <c>UtcNow</c>).
down : 'Context option * 'Event -> 'Contract * 'Meta option * string * string * DateTimeOffset option,
/// together with its <c>eventId</c>, <c>correlationId</c>, <c>causationId</c> and an event creation <c>timestamp</c> (defaults to <c>UtcNow</c>).
down : 'Context option * 'Event -> 'Contract * 'Meta option * Guid * string * string * DateTimeOffset option,
/// Enables one to fail encoder generation if 'Contract contains nullary cases. Defaults to <c>false</c>, i.e. permitting them
[<Optional; DefaultParameterValue(null)>] ?rejectNullaryCases)
: FsCodec.IEventCodec<'Event, obj, 'Context> =

let boxEncoder : TypeShape.UnionContract.IEncoder<obj> = new TypeShape.UnionContract.BoxEncoder() :> _
let boxEncoder : TypeShape.UnionContract.IEncoder<obj> = TypeShape.UnionContract.BoxEncoder() :> _
let dataCodec =
TypeShape.UnionContract.UnionContractEncoder.Create<'Contract, obj>(
boxEncoder,
Expand All @@ -39,10 +39,10 @@ type Codec private () =

{ new FsCodec.IEventCodec<'Event, obj, 'Context> with
member __.Encode(context, event) =
let (c, meta : 'Meta option, correlationId, causationId, timestamp : DateTimeOffset option) = down (context, event)
let (c, meta : 'Meta option, eventId, correlationId, causationId, timestamp : DateTimeOffset option) = down (context, event)
let enc = dataCodec.Encode c
let meta = meta |> Option.map boxEncoder.Encode<'Meta>
FsCodec.Core.EventData.Create(enc.CaseName, enc.Payload, defaultArg meta null, correlationId, causationId, ?timestamp = timestamp)
FsCodec.Core.EventData.Create(enc.CaseName, enc.Payload, defaultArg meta null, eventId, correlationId, causationId, ?timestamp = timestamp)

member __.TryDecode encoded =
let cOption = dataCodec.TryDecode { CaseName = encoded.EventType; Payload = encoded.Data }
Expand All @@ -63,15 +63,15 @@ type Codec private () =
/// and an Event Creation <c>timestamp</c>.
down : 'Event -> 'Contract * 'Meta option * DateTimeOffset option,
/// Uses the 'Context passed to the Encode call and the 'Meta emitted by <c>down</c> to a) the final metadata b) the <c>correlationId</c> and c) the correlationId
mapCausation : 'Context option * 'Meta option -> 'Meta option * string * string,
mapCausation : 'Context option * 'Meta option -> 'Meta option * Guid * string * string,
/// Enables one to fail encoder generation if union contains nullary cases. Defaults to <c>false</c>, i.e. permitting them
[<Optional; DefaultParameterValue(null)>] ?rejectNullaryCases)
: FsCodec.IEventCodec<'Event, obj, 'Context> =

let down (context, event) =
let c, m, t = down event
let m', correlationId, causationId = mapCausation (context, m)
c, m', correlationId, causationId, t
let m', eventId, correlationId, causationId = mapCausation (context, m)
c, m', eventId, correlationId, causationId, t
Codec.Create(up = up, down = down, ?rejectNullaryCases = rejectNullaryCases)

/// Generate an <code>IEventCodec</code> that roundtrips events by holding the boxed form of the Event body.
Expand All @@ -92,7 +92,7 @@ type Codec private () =
[<Optional; DefaultParameterValue(null)>] ?rejectNullaryCases)
: FsCodec.IEventCodec<'Event, obj, obj> =

let mapCausation (_context : obj, m : 'Meta option) = m, null, null
let mapCausation (_context : obj, m : 'Meta option) = m, Guid.NewGuid(), null, null
Codec.Create(up = up, down = down, mapCausation = mapCausation, ?rejectNullaryCases = rejectNullaryCases)

/// Generate an <code>IEventCodec</code> that roundtrips events by holding the boxed form of the Event body.
Expand Down
16 changes: 8 additions & 8 deletions src/FsCodec.NewtonsoftJson/Codec.fs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ module private Utf8BytesEncoder =
let private utf8NoBom = new System.Text.UTF8Encoding(false, true)
let makeJsonWriter ms =
// We need to `leaveOpen` in order to allow .Dispose of the `.rentStream`'d to return it
let sw = new StreamWriter(ms, utf8NoBom, 1024, leaveOpen = true) // same middle args as StreamWriter default ctor
let sw = new StreamWriter(ms, utf8NoBom, 1024, leaveOpen = true) // same middle args as StreamWriter default ctor
new JsonTextWriter(sw, ArrayPool = CharBuffersPool.instance)

module Core =
Expand Down Expand Up @@ -69,7 +69,7 @@ type Codec private () =
/// The function is also expected to derive
/// a <c>meta</c> object that will be serialized with the same settings (if it's not <c>None</c>)
/// and an Event Creation <c>timestamp</c>.
down : 'Context option * 'Event -> 'Contract * 'Meta option * string * string * DateTimeOffset option,
down : 'Context option * 'Event -> 'Contract * 'Meta option * Guid * string * string * DateTimeOffset option,
/// Configuration to be used by the underlying <c>Newtonsoft.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Settings.Create()</c>
[<Optional; DefaultParameterValue(null)>] ?settings,
/// Enables one to fail encoder generation if union contains nullary cases. Defaults to <c>false</c>, i.e. permitting them
Expand All @@ -86,10 +86,10 @@ type Codec private () =

{ new FsCodec.IEventCodec<'Event, byte[], 'Context> with
member __.Encode(context, event) =
let (c, meta : 'Meta option, correlationId, causationId, timestamp : DateTimeOffset option) = down (context, event)
let (c, meta : 'Meta option, eventId, correlationId, causationId, timestamp : DateTimeOffset option) = down (context, event)
let enc = dataCodec.Encode c
let metaUtf8 = meta |> Option.map bytesEncoder.Encode<'Meta>
FsCodec.Core.EventData.Create(enc.CaseName, enc.Payload, defaultArg metaUtf8 null, correlationId, causationId, ?timestamp = timestamp)
FsCodec.Core.EventData.Create(enc.CaseName, enc.Payload, defaultArg metaUtf8 null, eventId, correlationId, causationId, ?timestamp = timestamp)

member __.TryDecode encoded =
match dataCodec.TryDecode { CaseName = encoded.EventType; Payload = encoded.Data } with
Expand All @@ -111,7 +111,7 @@ type Codec private () =
/// and an Event Creation <c>timestamp</c>.
down : 'Event -> 'Contract * 'Meta option * DateTimeOffset option,
/// Uses the 'Context passed to the Encode call and the 'Meta emitted by <c>down</c> to a) the final metadata b) the <c>correlationId</c> and c) the correlationId
mapCausation : 'Context option * 'Meta option -> 'Meta option * string * string,
mapCausation : 'Context option * 'Meta option -> 'Meta option * Guid * string * string,
/// Configuration to be used by the underlying <c>Newtonsoft.Json</c> Serializer when encoding/decoding. Defaults to same as <c>Settings.Create()</c>
[<Optional; DefaultParameterValue(null)>] ?settings,
/// Enables one to fail encoder generation if union contains nullary cases. Defaults to <c>false</c>, i.e. permitting them
Expand All @@ -120,8 +120,8 @@ type Codec private () =

let down (context, union) =
let c, m, t = down union
let m', correlationId, causationId = mapCausation (context, m)
c, m', correlationId, causationId, t
let m', eventId, correlationId, causationId = mapCausation (context, m)
c, m', eventId, correlationId, causationId, t
Codec.Create(up = up, down = down, ?settings = settings, ?rejectNullaryCases = rejectNullaryCases)

/// Generate an <code>IEventCodec</code> using the supplied <c>Newtonsoft.Json<c/> <c>settings</c>.
Expand All @@ -144,7 +144,7 @@ type Codec private () =
[<Optional; DefaultParameterValue(null)>] ?rejectNullaryCases)
: FsCodec.IEventCodec<'Event, byte[], obj> =

let mapCausation (_context : obj, m : 'Meta option) = m, null, null
let mapCausation (_context : obj, m : 'Meta option) = m, Guid.NewGuid(), null, null
Codec.Create(up = up, down = down, mapCausation = mapCausation, ?settings = settings, ?rejectNullaryCases = rejectNullaryCases)

/// Generate an <code>IEventCodec</code> using the supplied <c>Newtonsoft.Json</c> <c>settings</c>.
Expand Down
17 changes: 9 additions & 8 deletions src/FsCodec/Codec.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@ type Codec =
// employed in the convention-based Codec
// (IME, while many systems have some code touching the metadata, it's not something one typically wants to encourage)
static member private Create<'Event, 'Format, 'Context>
( /// Maps an 'Event to: an Event Type Name, a pair of <>'Format</c>'s representing the <c>Data</c> and <c>Meta</c> together with the <c>correlationId</c>, <c>causationId</c> and <c>timestamp</c>.
encode : 'Context option * 'Event -> string * 'Format * 'Format * string * string * System.DateTimeOffset option,
( /// Maps an 'Event to: an Event Type Name, a pair of <>'Format</c>'s representing the <c>Data</c> and <c>Meta</c> together with the
/// <c>eventId</c>, <c>correlationId</c>, <c>causationId</c> and <c>timestamp</c>.
encode : 'Context option * 'Event -> string * 'Format * 'Format * Guid * string * string * System.DateTimeOffset option,
/// Attempts to map from an Event's stored data to <c>Some 'Event</c>, or <c>None</c> if not mappable.
tryDecode : ITimelineEvent<'Format> -> 'Event option)
: IEventCodec<'Event, 'Format, 'Context> =

{ new IEventCodec<'Event, 'Format, 'Context> with
member __.Encode(context, event) =
let eventType, data, metadata, correlationId, causationId, timestamp = encode (context, event)
Core.EventData.Create(eventType, data, metadata, correlationId, causationId, ?timestamp = timestamp)
let eventType, data, metadata, eventId, correlationId, causationId, timestamp = encode (context, event)
Core.EventData.Create(eventType, data, metadata, eventId, correlationId, causationId, ?timestamp = timestamp)

member __.TryDecode encoded =
tryDecode encoded }
Expand All @@ -37,13 +38,13 @@ type Codec =
/// to the <c>'Event</c> representation (typically a Discriminated Union) that is to be presented to the programming model.
tryDecode : FsCodec.ITimelineEvent<'Format> -> 'Event option,
/// Uses the 'Context passed to the Encode call and the 'Meta emitted by <c>down</c> to a) the final metadata b) the <c>correlationId</c> and c) the correlationId
mapCausation : 'Context option * 'Event -> 'Format * string * string)
mapCausation : 'Context option * 'Event -> 'Format * Guid * string * string)
: FsCodec.IEventCodec<'Event, 'Format, 'Context> =

let encode (context, event) =
let et, d, t = encode event
let m, correlationId, causationId = mapCausation (context, event)
et, d, m, correlationId, causationId, t
let m, eventId, correlationId, causationId = mapCausation (context, event)
et, d, m, eventId, correlationId, causationId, t
Codec.Create(encode, tryDecode)

/// Generate an <code>IEventCodec</code> using the supplied pair of <c>encode</c> and <c>tryDecode</code> functions.
Expand All @@ -54,6 +55,6 @@ type Codec =
tryDecode : string * 'Format -> 'Event option)
: IEventCodec<'Event, 'Format, obj> =

let encode' (_context : obj, event) = let (et, d : 'Format) = encode event in et, d, null, null, null, None
let encode' (_context : obj, event) = let (et, d : 'Format) = encode event in et, d, null, Guid.NewGuid(), null, null, None
let tryDecode' (encoded : FsCodec.ITimelineEvent<'Format>) = tryDecode (encoded.EventType, encoded.Data)
Codec.Create(encode', tryDecode')
30 changes: 18 additions & 12 deletions src/FsCodec/FsCodec.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ type IEventData<'Format> =
abstract member Data : 'Format
/// Optional metadata (null, or same as Data, not written if missing)
abstract member Meta : 'Format
/// The Event's Creation Time (as defined by the writer, i.e. in a mirror, this is intended to reflect the original time)
/// <remarks>- For EventStore, this value is not honored when writing; the server applies an authoritative timestamp when accepting the write.</remarks>
abstract member Timestamp : System.DateTimeOffset
/// Application-generated identifier used to drive idempotent writes based on deterministic Ids and/or Request Id
abstract member EventId : System.Guid
/// The Correlation Id associated with the flow that generated this event. Can be `null`
abstract member CorrelationId : string
/// The Causation Id associated with the flow that generated this event. Can be `null`
abstract member CausationId : string
/// The Event's Creation Time (as defined by the writer, i.e. in a mirror, this is intended to reflect the original time)
/// <remarks>- For EventStore, this value is not honored when writing; the server applies an authoritative timestamp when accepting the write.</remarks>
abstract member Timestamp : System.DateTimeOffset

/// Represents a Domain Event or Unfold, together with it's 0-based <c>Index</c> in the event sequence
type ITimelineEvent<'Format> =
Expand All @@ -40,27 +42,30 @@ open System

/// An Event about to be written, see <c>IEventData<c> for further information
[<NoComparison; NoEquality>]
type EventData<'Format> private (eventType, data, meta, correlationId, causationId, timestamp) =
static member Create(eventType, data, ?meta, ?correlationId, ?causationId, ?timestamp) : IEventData<'Format> =
type EventData<'Format> private (eventType, data, meta, eventId, correlationId, causationId, timestamp) =
static member Create(eventType, data, ?meta, ?eventId, ?correlationId, ?causationId, ?timestamp) : IEventData<'Format> =
let meta, correlationId, causationId = defaultArg meta Unchecked.defaultof<_>, defaultArg correlationId null, defaultArg causationId null
EventData(eventType, data, meta, correlationId, causationId, match timestamp with Some ts -> ts | None -> DateTimeOffset.UtcNow) :> _
let eventId = match eventId with Some id -> id | None -> Guid.NewGuid()
EventData(eventType, data, meta, eventId, correlationId, causationId, match timestamp with Some ts -> ts | None -> DateTimeOffset.UtcNow) :> _

interface FsCodec.IEventData<'Format> with
member __.EventType = eventType
member __.Data = data
member __.Meta = meta
member __.Timestamp = timestamp
member __.EventId = eventId
member __.CorrelationId = correlationId
member __.CausationId = causationId
member __.Timestamp = timestamp

/// An Event or Unfold that's been read from a Store and hence has a defined <c>Index</c> on the Event Timeline
[<NoComparison; NoEquality>]
type TimelineEvent<'Format> private (index, isUnfold, eventType, data, meta, correlationId, causationId, timestamp, context) =
static member Create(index, eventType, data, ?meta, ?correlationId, ?causationId, ?timestamp, ?isUnfold, ?context) : ITimelineEvent<'Format> =
let meta, timestamp = defaultArg meta Unchecked.defaultof<_>, match timestamp with Some ts -> ts | None -> DateTimeOffset.UtcNow
type TimelineEvent<'Format> private (index, isUnfold, eventType, data, meta, eventId, correlationId, causationId, timestamp, context) =
static member Create(index, eventType, data, ?meta, ?eventId, ?correlationId, ?causationId, ?timestamp, ?isUnfold, ?context) : ITimelineEvent<'Format> =
let isUnfold, context = defaultArg isUnfold false, defaultArg context null
let meta, eventId = defaultArg meta Unchecked.defaultof<_>, match eventId with Some x -> x | None -> Guid.Empty
let timestamp = match timestamp with Some ts -> ts | None -> DateTimeOffset.UtcNow
let correlationId, causationId = defaultArg correlationId null, defaultArg causationId null
TimelineEvent(index, isUnfold, eventType, data, meta, correlationId, causationId, timestamp, context) :> _
TimelineEvent(index, isUnfold, eventType, data, meta, eventId, correlationId, causationId, timestamp, context) :> _

interface FsCodec.ITimelineEvent<'Format> with
member __.Index = index
Expand All @@ -69,6 +74,7 @@ type TimelineEvent<'Format> private (index, isUnfold, eventType, data, meta, cor
member __.EventType = eventType
member __.Data = data
member __.Meta = meta
member __.Timestamp = timestamp
member __.EventId = eventId
member __.CorrelationId = correlationId
member __.CausationId = causationId
member __.Timestamp = timestamp