Skip to content

Commit

Permalink
typos/fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
bartelink committed Aug 31, 2023
1 parent 4e3c430 commit 06edb58
Showing 1 changed file with 43 additions and 44 deletions.
87 changes: 43 additions & 44 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,11 @@ module private Stream =
[<RequiredQualifiedAccess>]
module Events =
type Snapshotted = ... // NOTE: event body types past tense with same name as case
type Event =
| ...
| [<DataMember(Name = "Snapshotted">] Snapshotted of Snapshotted // NOTE: Snapshotted event explictly named to remind one can/should version it
// optionally: `encode`, `tryDecode` (only if you're doing manual decoding)
let codec = FsCodec ... Codec.Create<Event>(...)
```
Expand All @@ -347,13 +350,11 @@ module Fold =
module Snapshot =
let generate (state: State): Event =
let generate (state: State): Events.Event =
Events.Snapshotted { ... }
let isOrigin = function
| Events.Snapshotted -> true
| _ -> false
let isOrigin = function Events.Snapshotted -> true | _ -> false
let config = isOrigin, generate
let hydrate (e: Snapshotted): State = ...
let hydrate (e: Events.Snapshotted): State = ...
let private evolve state = function
| Events.Snapshotted e -> Snapshot.hydrate e
Expand All @@ -363,13 +364,13 @@ module Fold =
module Decisions =
let interpretX ... (state: Fold.State): Events list = ...
let interpretX ... (state: Fold.State): Events.Event[] = ...
type Decision =
| Accepted
| Failed of Reason
let decideY ... (state: Fold.State): Decision * Events list = ...
let decideY ... (state: Fold.State): Decision * Events.Event[] = ...
```

- `interpret`, `decide` _and related input and output types / interfaces_ are
Expand Down Expand Up @@ -433,17 +434,18 @@ either within the `module Aggregate`, or somewhere outside closer to the

```fsharp
let defaultCacheDuration = System.TimeSpan.FromMinutes 20.
let cacheStrategy = Equinox.CosmosStore.CachingStrategy.SlidingWindow (cache, defaultCacheDuration)
let cacheStrategy cache = Equinox.CosmosStore.CachingStrategy.SlidingWindow (cache, defaultCacheDuration)
module EventStore =
let accessStrategy = Equinox.EventStoreDb.AccessStrategy.RollingSnapshots (Fold.isOrigin, Fold.snapshot)
let accessStrategy = Equinox.EventStoreDb.AccessStrategy.RollingSnapshots Fold.Snapshot.config
let category (context, cache) =
Equinox.EventStore.EventStoreCategory(context, Stream.Category, Events.codec, Fold.fold, Fold.initial, cacheStrategy, accessStrategy)
Equinox.EventStore.EventStoreCategory(context, Stream.Category, Events.codec, Fold.fold, Fold.initial, accessStrategy, cacheStrategy cache)
module Cosmos =
let accessStrategy = Equinox.CosmosStore.AccessStrategy.Snapshot Fold.Snapshot.config
let category (context, cache) =
Equinox.CosmosStore.CosmosStoreCategory(context, Stream.Category, Events.codec, Fold.fold, Fold.initial, accessStrategy, cacheStrategy)
Equinox.CosmosStore.CosmosStoreCategory(context, Stream.Category, Events.codec, Fold.fold, Fold.initial, accessStrategy, cacheStrategy cache)
```

### `MemoryStore` Storage Binding Module

Expand All @@ -454,7 +456,7 @@ can use the `MemoryStore` in the context of your tests:
```fsharp
module MemoryStore =
let category (store: Equinox.MemoryStore.VolatileStore) =
Equinox.MemoryStore.MemoryStoreCategory(store, Category, Events.codec, Fold.fold, Fold.initial)
Equinox.MemoryStore.MemoryStoreCategory(store, Stream.Category, Events.codec, Fold.fold, Fold.initial)
```

Typically that binding module can live with your test helpers rather than
Expand Down Expand Up @@ -501,7 +503,7 @@ events on a given category of stream:
tests and used to parameterize the Category's storage configuration._.
Sometimes named `apply`)

- `interpret: (context/command etc ->) 'state -> event' list` or `decide: (context/command etc ->) 'state -> 'result*'event list`: responsible for _Deciding_ (in an [idempotent](https://en.wikipedia.org/wiki/Idempotence) manner) how the intention represented by `context/command` should be mapped with regard to the provided `state` in terms of:
- `interpret: (context/command etc ->) 'state -> 'event[]` or `decide: (context/command etc ->) 'state -> 'result * 'event[]`: responsible for _Deciding_ (in an [idempotent](https://en.wikipedia.org/wiki/Idempotence) manner) how the intention represented by `context/command` should be mapped with regard to the provided `state` in terms of:
a) the `'events` that should be written to the stream to record the decision
b) (for the `'result` in the `decide` signature) any response to be returned to the invoker (NB returning a result likely represents a violation of the [CQS](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation) and/or CQRS principles, [see Synchronous Query in the Glossary](#glossary))

Expand Down Expand Up @@ -596,10 +598,10 @@ type Command =
| Remove of itemId: int
let interpret command state =
let has id = state |> List.exits (is id)
let has id = state |> List.exists (is id)
match command with
| Add item -> if has item.id then [] else [Added item]
| Remove id -> if has id then [Removed id] else []
| Add item -> if has item.id then [||] else [| Added item |]
| Remove id -> if has id then [| Removed id |] else [||]
(*
* Optional: Snapshot/Unfold-related functions to allow establish state
Expand Down Expand Up @@ -732,15 +734,15 @@ follow!

```fsharp
type Equinox.Decider(...) =
StoreIntegration
// Run interpret function with present state, retrying with Optimistic Concurrency
member _.Transact(interpret: State -> Event list): Async<unit>
member _.Transact(interpret: 'state -> 'event[]): Async<unit>
// Run decide function with present state, retrying with Optimistic Concurrency, yielding Result on exit
member _.Transact(decide: State -> Result*Event list): Async<Result>
member _.Transact(decide: 'state -> 'result * 'event[]): Async<'result>
// Runs a Null Flow that simply yields a `projection` of `Context.State`
member _.Query(projection: State -> View): Async<View>
member _.Query(projection: 'state -> 'view): Async<'view>
```

### Favorites walkthrough
Expand Down Expand Up @@ -811,8 +813,8 @@ type Command =
| Remove of string
let interpret command state =
match command with
| Add sku -> if state |> List.contains sku then [] else [Added sku]
| Remove sku -> if state |> List.contains sku |> not then [] else [Removed sku]
| Add sku -> if state |> List.contains sku then [||] else [| Added sku |]
| Remove sku -> if state |> List.contains sku |> not then [||] else [| Removed sku |]
```

Command handling should almost invariably be implemented in an
Expand Down Expand Up @@ -1028,13 +1030,13 @@ let fold = Array.fold evolve
type Command = Add of Todo | Update of Todo | Delete of id: int | Clear
let interpret c (state: State) =
match c with
| Add value -> [Added { value with id = state.nextId }]
| Add value -> [| Added { value with id = state.nextId } |]
| Update value ->
match state.items |> List.tryFind (function { id = id } -> id = value.id) with
| Some current when current <> value -> [Updated value]
| _ -> []
| Delete id -> if state.items |> List.exists (fun x -> x.id = id) then [Deleted id] else []
| Clear -> if state.items |> List.isEmpty then [] else [Cleared]
| Some current when current <> value -> [| Updated value |]
| _ -> [||]
| Delete id -> if state.items |> List.exists (fun x -> x.id = id) then [| Deleted id |] else [||]
| Clear -> if state.items |> List.isEmpty then [||] else [| Cleared |]
```

- Note `Add` does not adhere to the normal idempotency constraint, being
Expand Down Expand Up @@ -1152,7 +1154,7 @@ In this case, the Decision Process is `interpret`ing the _Command_ in the
context of a `'state`.

The function signature is:
`let interpret (context, command, args) state: Events.Event list`
`let interpret (context, command, args) state: Events.Event[]`

Note the `'state` is the last parameter; it's computed and supplied by the
Equinox Flow.
Expand All @@ -1169,12 +1171,12 @@ conflicting write have taken place since the loading of the state_

```fsharp
let interpret (context, command) state: Events.Event list =
let interpret (context, command) state: Events.Event[] =
match tryCommand context command state with
| None ->
[] // not relevant / already in effect
[||] // not relevant / already in effect
| Some eventDetails -> // accepted, mapped to event details record
[Event.HandledCommand eventDetails]
[| Events.HandledCommand eventDetails |]
type Service internal (resolve: ClientId -> Equinox.Decider<Events.Event, Fold.State>)
Expand Down Expand Up @@ -1202,20 +1204,20 @@ signature: you're both potentially emitting events and yielding an outcome or
projecting some of the 'state'.

In this case, the signature is: `let decide (context, command, args) state:
'result * Events.Event list`
'result * Events.Event[]`

Note that the return value is a _tuple_ of `('result,Event list):
Note that the return value is a _tuple_ of `('result, Events.Event[])`:
- the `fst` element is returned from `decider.Transact`
- the `snd` element of the tuple represents the events (if any) that should
represent the state change implied by the request.with
represent the state change implied by the request.

Note if the decision function yields events, and a conflict is detected, the
flow may result in the `decide` function being rerun with the conflicting state
until either no events are emitted, or there were on further conflicting writes
supplied by competing writers.

```fsharp
let decide (context, command) state: int * Events.Event list =
let decide (context, command) state: int * Events.Event[] =
// ... if `snd` contains event, they are written
// `fst` (an `int` in this instance) is returned as the outcome to the caller
Expand Down Expand Up @@ -1281,7 +1283,7 @@ let validateInterpret contextAndOrArgsAndOrCommand state =
let validateIdempotent contextAndOrArgsAndOrCommand state' =
let events' = interpret contextAndOrArgsAndOrCommand state'
match events' with
| [|] -> ()
| [||] -> ()
// TODO add clauses to validate edge cases that should still generate events on a re-run
| xs -> failwithf "Not idempotent; Generated %A in response to %A" xs contextAndOrArgsAndOrCommand
```
Expand Down Expand Up @@ -1339,7 +1341,7 @@ type Service internal (resolve: CartId -> Equinox.Decider<Events.Event, Fold.Sta
member _.Run(cartId, optimistic, commands: Command seq, ?prepare): Async<Fold.State> =
let decider = resolve cartId
let opt = if optimistic then Equinox.AnyCachedValue else Equinox.RequireLoad
let opt = if optimistic then Equinox.LoadOption.AnyCachedValue else Equinox.LoadOption.RequireLoad
decider.Transact(fun state -> async {
match prepare with None -> () | Some prep -> do! prep
return interpretMany Fold.fold (Seq.map interpret commands) state }, opt)
Expand Down Expand Up @@ -1401,7 +1403,7 @@ type Accumulator<'event, 'state>(fold: 'state -> 'event[] -> 'state, originState
type Service ... =
member _.Run(cartId, optimistic, commands: Command seq, ?prepare): Async<Fold.State> =
let decider = resolve cartId
let opt = if optimistic then Equinox.AnyCachedValue else Equinox.RequireLoad
let opt = if optimistic then Equinox.LoadOption.AnyCachedValue else Equinox.LoadOption.RequireLoad
decider.Transact(fun state -> async {
match prepare with None -> () | Some prep -> do! prep
let acc = Accumulator(Fold.fold, state)
Expand Down Expand Up @@ -1475,11 +1477,8 @@ Key aspects relevant to the Equinox programming model:
- In general, EventStore provides excellent caching and performance
characteristics intrinsically by virtue of its design

- Projections can be managed by either tailing streams (including the synthetic
`$all` stream) or using the Projections facility - there's no obvious reason
to wrap it, aside from being able to uniformly target CosmosDB (i.e. one
could build an `Equinox.EventStore.Projection` library and an `eqx project
stats es` with very little code).
- Projections can be managed by the `Propulsion.EventStoreDb` library; there is also
an `eqx project stats es` feature).

- In general event streams should be considered append only, with no mutations
or deletes
Expand Down Expand Up @@ -2361,7 +2360,7 @@ For Domain Events in an event-sourced model, their permanence and immutability i

It should be noted with regard to such requirements:
- EventStoreDB does not present any APIs for mutation of events, though deleting events is a fully supported operation (although that can be restricted). Rewrites are typically approached by doing an offline database rebuild.
- `Equinox.Cosmos` and `Equinox.CosmosStore` include support for pruning events (only) from the head of a stream. Obviously, there's nothing stopping you deleting or altering the Batch documents out of band via the underlying CosmosDB APIs directly (Note however that the semantics of document ordering within a logical partition means its strongly advised not to mutate any event Batch documents as this will cause their ordering to become incorrect relative to other events, invalidating a key tenet that Change Feed Processors rely on).
- `Equinox.CosmosStore` includes support for pruning events (only) from the head of a stream. Obviously, there's nothing stopping you deleting or altering the Batch documents out of band via the underlying CosmosDB APIs directly (Note however that the semantics of document ordering within a logical partition means its strongly advised not to mutate any event Batch documents as this will cause their ordering to become incorrect relative to other events, invalidating a key tenet that Change Feed Processors rely on).

### Growth handling strategies

Expand Down

0 comments on commit 06edb58

Please sign in to comment.