diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index b757e1019..16e3a5aa2 100755 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -703,9 +703,8 @@ elements you'll touch in a normal application are: Transaction semantics that are central to Equinox and the overall `Decider` concept. - [`type Decider`](https://github.com/jet/equinox/blob/master/src/Equinox/Decider.fs#L11) - surface API one uses to `Transact` or `Query` against a specific stream's state -- [`type ResolveOption` Discriminated Union](https://github.com/jet/equinox/blob/master/src/Equinox/Decider.fs#L59) - - used to specify optimization overrides to be applied when - `resolve` hydrates a `Decider` +- [`type LoadOption` Discriminated Union](https://github.com/jet/equinox/blob/master/src/Equinox/Decider.fs#L59) - + used to specify optimization overrides to be applied when a `Decider`'s `Query` or `Transact` operations establishes the state of the stream Its recommended to read the examples in conjunction with perusing the code in order to see the relatively simple implementations that underlie the @@ -1325,10 +1324,11 @@ let interpretMany fold interpreters (state : 'state) : 'state * 'event list = type Service internal (resolve : CartId -> Equinox.Decider) = member __.Run(cartId, optimistic, commands : Command seq, ?prepare) : Async = - let decider = resolve (cartId,if optimistic then Some Equinox.AllowStale else None) - decider.TransactAsync(fun state -> async { + let decider = resolve cartId + let opt = if optimistic then Equinox.AllowStale else Equinox.RequireLoad + decider.Transact(fun state -> async { match prepare with None -> () | Some prep -> do! prep - return interpretMany Fold.fold (Seq.map interpret commands) state }) + return interpretMany Fold.fold (Seq.map interpret commands) state }, opt) ``` @@ -1368,7 +1368,7 @@ type Accumulator<'event, 'state>(fold : 'state -> 'event seq -> 'state, originSt interpret __.State |> accumulated.AddRange /// Invoke an Async decision function, gathering the events (if any) that /// it decides are necessary into the `Accumulated` sequence - member __.TransactAsync(interpret : 'state -> Async<'event list>) : Async = async { + member __.Transact(interpret : 'state -> Async<'event list>) : Async = async { let! events = interpret __.State accumulated.AddRange events } /// Invoke a decision function, while also propagating a result yielded as @@ -1379,21 +1379,22 @@ type Accumulator<'event, 'state>(fold : 'state -> 'event seq -> 'state, originSt result /// Invoke a decision function, while also propagating a result yielded as /// the fst of an (result, events) pair - member __.TransactAsync(decide : 'state -> Async<'result * 'event list>) : Async<'result> = async { + member __.Transact(decide : 'state -> Async<'result * 'event list>) : Async<'result> = async { let! result, newEvents = decide __.State accumulated.AddRange newEvents return result } type Service ... = member __.Run(cartId, optimistic, commands : Command seq, ?prepare) : Async = - let decider = resolve (cartId,if optimistic then Some Equinox.AllowStale else None) - decider.TransactAsync(fun state -> async { + let decider = resolve cartId + let opt = if optimistic then Some Equinox.AllowStale else Equinox.RequireLoad + decider.Transact(fun state -> async { match prepare with None -> () | Some prep -> do! prep let acc = Accumulator(Fold.fold, state) for cmd in commands do acc.Transact(interpret cmd) return acc.State, acc.Accumulated - }) + }, opt) ``` # Equinox Architectural Overview diff --git a/README.md b/README.md index cfa8c12ed..6bc8d79aa 100644 --- a/README.md +++ b/README.md @@ -806,7 +806,7 @@ and Equinox will supply the _initial_ value for the `project` function to render > Side note: the original question is for a read operation, but there's an interesting consideration if we are doing a `Transact`. Say, > for instance, that there's a PUT API endpoint where the code would register a fresh customer order for the customer in its order list -> via the Decider's `Transact` operation. As an optimization, one can utilize the `AssumeEmpty` hint as the `Equinox.ResolveOption` to +> via the Decider's `Transact` operation. As an optimization, one can utilize the `AssumeEmpty` hint as the `Equinox.LoadOption` to > hint that it's worth operating on the assumption that the stream is empty. When the internal sync operation attempts to perform the write, > that assumption will be tested; every write is always version checked. > In the scenario where we are dealing with a rerun of an attempt to create an order (lets say the call timed out, but the processing actually diff --git a/samples/Store/Domain/Cart.fs b/samples/Store/Domain/Cart.fs index a55904d09..245214792 100644 --- a/samples/Store/Domain/Cart.fs +++ b/samples/Store/Domain/Cart.fs @@ -112,7 +112,7 @@ type Accumulator<'event, 'state>(fold : 'state -> 'event seq -> 'state, originSt member __.Transact(interpret : 'state -> 'event list) : unit = interpret __.State |> accumulated.AddRange /// Invoke an Async decision function, gathering the events (if any) that it decides are necessary into the `Accumulated` sequence - member __.TransactAsync(interpret : 'state -> Async<'event list>) : Async = async { + member __.Transact(interpret : 'state -> Async<'event list>) : Async = async { let! events = interpret __.State accumulated.AddRange events } /// Invoke a decision function, while also propagating a result yielded as the fst of an (result, events) pair @@ -121,7 +121,7 @@ type Accumulator<'event, 'state>(fold : 'state -> 'event seq -> 'state, originSt accumulated.AddRange newEvents result /// Invoke a decision function, while also propagating a result yielded as the fst of an (result, events) pair - member __.TransactAsync(decide : 'state -> Async<'result * 'event list>) : Async<'result> = async { + member __.Transact(decide : 'state -> Async<'result * 'event list>) : Async<'result> = async { let! result, newEvents = decide __.State accumulated.AddRange newEvents return result } @@ -142,13 +142,14 @@ type Service internal (resolve : CartId -> Equinox.Decider = __.Run(cartId, optimistic, commands, ?prepare=prepare) |> Async.Ignore diff --git a/src/Equinox.CosmosStore/CosmosStore.fs b/src/Equinox.CosmosStore/CosmosStore.fs index 2bfcf0f40..d22e8fec0 100644 --- a/src/Equinox.CosmosStore/CosmosStore.fs +++ b/src/Equinox.CosmosStore/CosmosStore.fs @@ -1380,14 +1380,14 @@ type CachingStrategy = /// Retain a single 'state per streamName, together with the associated etag. /// Each cache hit for a stream renews the retention period for the defined window. /// Upon expiration of the defined window from the point at which the cache was entry was last used, a full reload is triggered. - /// Unless ResolveOption.AllowStale is used, each cache hit still incurs an etag-contingent Tip read (at a cost of a roundtrip with a 1RU charge if unmodified). + /// Unless LoadOption.AllowStale is used, each cache hit still incurs an etag-contingent Tip read (at a cost of a roundtrip with a 1RU charge if unmodified). // NB while a strategy like EventStore.Caching.SlidingWindowPrefixed is obviously easy to implement, the recommended approach is to // track all relevant data in the state, and/or have the `unfold` function ensure _all_ relevant events get held in the `u`nfolds in Tip | SlidingWindow of ICache * window : TimeSpan /// Retain a single 'state per streamName, together with the associated etag. /// Upon expiration of the defined period, a full reload is triggered. - /// Typically combined with `Equinox.ResolveOption.AllowStale` to minimize loads. - /// Unless ResolveOption.AllowStale is used, each cache hit still incurs an etag-contingent Tip read (at a cost of a roundtrip with a 1RU charge if unmodified). + /// Typically combined with `Equinox.LoadOption.AllowStale` to minimize loads. + /// Unless LoadOption.AllowStale is used, each cache hit still incurs an etag-contingent Tip read (at a cost of a roundtrip with a 1RU charge if unmodified). | FixedTimeSpan of ICache * period : TimeSpan [] diff --git a/src/Equinox.EventStore/EventStore.fs b/src/Equinox.EventStore/EventStore.fs index 7f46034ce..91ab98f09 100755 --- a/src/Equinox.EventStore/EventStore.fs +++ b/src/Equinox.EventStore/EventStore.fs @@ -575,11 +575,11 @@ type CachingStrategy = /// Retain a single 'state per streamName. /// Each cache hit for a stream renews the retention period for the defined window. /// Upon expiration of the defined window from the point at which the cache was entry was last used, a full reload is triggered. - /// Unless ResolveOption.AllowStale is used, each cache hit still incurs a roundtrip to load any subsequently-added events. + /// Unless LoadOption.AllowStale is used, each cache hit still incurs a roundtrip to load any subsequently-added events. | SlidingWindow of ICache * window : TimeSpan /// Retain a single 'state per streamName. /// Upon expiration of the defined period, a full reload is triggered. - /// Unless ResolveOption.AllowStale is used, each cache hit still incurs a roundtrip to load any subsequently-added events. + /// Unless LoadOption.AllowStale is used, each cache hit still incurs a roundtrip to load any subsequently-added events. | FixedTimeSpan of ICache * period : TimeSpan /// Prefix is used to segregate multiple folds per stream when they are stored in the cache. /// Semantics are identical to SlidingWindow. diff --git a/src/Equinox.SqlStreamStore/SqlStreamStore.fs b/src/Equinox.SqlStreamStore/SqlStreamStore.fs index c7abab4e0..48dd2e7e2 100644 --- a/src/Equinox.SqlStreamStore/SqlStreamStore.fs +++ b/src/Equinox.SqlStreamStore/SqlStreamStore.fs @@ -534,11 +534,11 @@ type CachingStrategy = /// Retain a single 'state per streamName. /// Each cache hit for a stream renews the retention period for the defined window. /// Upon expiration of the defined window from the point at which the cache was entry was last used, a full reload is triggered. - /// Unless ResolveOption.AllowStale is used, each cache hit still incurs a roundtrip to load any subsequently-added events. + /// Unless LoadOption.AllowStale is used, each cache hit still incurs a roundtrip to load any subsequently-added events. | SlidingWindow of ICache * window : TimeSpan /// Retain a single 'state per streamName /// Upon expiration of the defined period, a full reload is triggered. - /// Unless ResolveOption.AllowStale is used, each cache hit still incurs a roundtrip to load any subsequently-added events. + /// Unless LoadOption.AllowStale is used, each cache hit still incurs a roundtrip to load any subsequently-added events. | FixedTimeSpan of ICache * period : TimeSpan /// Prefix is used to segregate multiple folds per stream when they are stored in the cache. /// Semantics are identical to SlidingWindow.