Skip to content

Commit

Permalink
feat!: change 'event lists/seq to consistently use arrays (#411)
Browse files Browse the repository at this point in the history
  • Loading branch information
bartelink authored Jul 26, 2023
1 parent fa12d6b commit 6df7c58
Show file tree
Hide file tree
Showing 76 changed files with 460 additions and 507 deletions.
27 changes: 14 additions & 13 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,30 @@ The `Unreleased` section name is replaced by the expected version of next releas

### Changed

- Performance: Switch surface APIs to `struct` Tuples and Options where relevant, some due to `struct` changes in [`FsCodec` #82](https://github.com/jet/FsCodec/pull/82), and use `task` in hot paths [#337](https://github.com/jet/equinox/pull/337)
- `Equinox`: Rename `ResolveOption` to `LoadOption` [#308](https://github.com/jet/equinox/pull/308) [#413](https://github.com/jet/equinox/pull/413)
- `Equinox`: Replace `XXXStoreCategory.Resolve(sn, ?ResolveOption)` with `?load = LoadOption` parameter on all `Transact` and `Query` methods [#308](https://github.com/jet/equinox/pull/308)
- `Equinox.*.*Category`: Added mandatory `name` argument, and `Name` property [#410](https://github.com/jet/equinox/pull/410)
- `Equinox.*.*Category`: Changed caching to be last argument, to reflect that it is applied over the top [#410](https://github.com/jet/equinox/pull/410)
- Change surface APIs that use Tuples and Options to `struct` equivalents. Some due to `struct` changes in [`FsCodec` #82](https://github.com/jet/FsCodec/pull/82), and use `task` in hot paths [#337](https://github.com/jet/equinox/pull/337)
- Change surface APIs that use`'event list` or `'event seq` to `'event[]` [#411](https://github.com/jet/equinox/pull/411)
- Raise `FSharp.Core` req to `6.0.7`, framework req to `net6.0` [#310](https://github.com/jet/equinox/pull/310) [#337](https://github.com/jet/equinox/pull/337) [#33](https://github.com/jet/equinox/pull/33) [#411](https://github.com/jet/equinox/pull/411)
- Replace `AsyncSeq` usage with `FSharp.Control.TaskSeq` v `0.4.0` [#361](https://github.com/jet/equinox/pull/361) [#391](https://github.com/jet/equinox/pull/391)
- `Equinox`: Push `Serilog` dependency out to `Equinox.Core` [#337](https://github.com/jet/equinox/pull/337)
- `Equinox.ResolveOption`: rename to `LoadOption` [#308](https://github.com/jet/equinox/pull/308) [#413](https://github.com/jet/equinox/pull/413)
- `Equinox.LoadOption`: Rename `AllowStale` to `AnyCachedValue` [#386](https://github.com/jet/equinox/pull/386)
- `Equinox.Decider`: `log` is now supplied via `Equinox.Category` [#337](https://github.com/jet/equinox/pull/337)
- `Equinox.Decider`: Replaced `maxAttempts` with a default policy and an optional argument on `Transact*` APIs [#337](https://github.com/jet/equinox/pull/337)
- `Equinox`: push `Serilog` dependency out to `Equinox.Core` [#337](https://github.com/jet/equinox/pull/337)
- `Equinox.Decider`: Replace `'event list` with `'event[]` [#411](https://github.com/jet/equinox/pull/411)
- `Equinox.Decider`: Replace `maxAttempts` with a default policy and an optional argument on `Transact*` APIs [#337](https://github.com/jet/equinox/pull/337)
- `Equinox.Core`: push `FsCodec` dependency out to concrete stores [#337](https://github.com/jet/equinox/pull/337)
- `Equinox.Core.AsyncBatchingGate`: renamed to `Batching.Batcher` [#390](https://github.com/jet/equinox/pull/390)
- Stores: Change Event Body types, requiring `FsCodec` v `3.0.0`, with [`EventBody` types switching from `byte[]` to `ReadOnlyMemory<byte>` and/or `JsonElement` see FsCodec#75](https://github.com/jet/FsCodec/pull/75) [#323](https://github.com/jet/equinox/pull/323)
- Stores: `*Category.Resolve`: Replace `Resolve(sn, ?ResolveOption)` with `?load = LoadOption` parameter on all `Transact` and `Query` methods [#308](https://github.com/jet/equinox/pull/308)
- Stores: `*Category` ctor: Add mandatory `name` argument, and `Name` property [#410](https://github.com/jet/equinox/pull/410)
- Stores: `*Category` ctor: Change `caching` to be last argument, to reflect that it is applied over the top [#410](https://github.com/jet/equinox/pull/410)
- `CosmosStore`: Require `Microsoft.Azure.Cosmos` v `3.27.0` [#310](https://github.com/jet/equinox/pull/310)
- `CosmosStore`: Switch to natively using `JsonElement` event bodies [#305](https://github.com/jet/equinox/pull/305) :pray: [@ylibrach](https://github.com/ylibrach)
- `CosmosStore`: Switch to natively using `System.Text.Json` for serialization of all `Microsoft.Azure.Cosmos` round-trips [#305](https://github.com/jet/equinox/pull/305) :pray: [@ylibrach](https://github.com/ylibrach)
- `CosmosStore`: Only log `bytes` when log level is `Debug` [#305](https://github.com/jet/equinox/pull/305)
- `CosmosStore.AccessStrategy.MultiSnapshot`,`Custom`: Change `list` and `seq` types to `array` [#338](https://github.com/jet/equinox/pull/338)
- `EventStore`: Target `EventStore.Client` v `22.0.0-preview`; rename `Connector` -> `EventStoreConnector` [#317](https://github.com/jet/equinox/pull/317)
- `Equinox.Tool`/`samples/`: switched to use `Equinox.EventStoreDb` [#196](https://github.com/jet/equinox/pull/196)
- Replace `AsyncSeq` usage with `FSharp.Control.TaskSeq` v `0.4.0` [#361](https://github.com/jet/equinox/pull/361) [#391](https://github.com/jet/equinox/pull/391)
- Raise `FSharp.Core` requirement to `6.0.7` [#337](https://github.com/jet/equinox/pull/337) [#33](https://github.com/jet/equinox/pull/362)
- Update all Stores to use `FsCodec` v `3.0.0`, with [`EventBody` types switching from `byte[]` to `ReadOnlyMemory<byte>` and/or `JsonElement` see FsCodec#75](https://github.com/jet/FsCodec/pull/75) [#323](https://github.com/jet/equinox/pull/323)
- Update all non-Client dependencies except `FSharp.Core`, `FSharp.Control.AsyncSeq` [#310](https://github.com/jet/equinox/pull/310)
- `CosmosStore.Core.Initialization.initAux`: Replace hard-coded manual 400 RU with `mode` parameter [#328](https://github.com/jet/equinox/pull/328) :pray: [@brihadish](https://github.com/brihadish)
- `EventStore`: Target `EventStore.Client` v `22.0.0-preview`; rename `Connector` -> `EventStoreConnector` [#317](https://github.com/jet/equinox/pull/317)
- `Tool`/`samples/`: switched to use `Equinox.EventStoreDb` [#196](https://github.com/jet/equinox/pull/196)

### Removed

Expand Down
48 changes: 25 additions & 23 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ module Fold =
let evolve state = function
| Events.X -> (state update)
| Events.Y -> (state update)
let fold events = Seq.fold evolve events
let fold = Array.fold evolve
(* Storage Model helpers *)
Expand Down Expand Up @@ -472,7 +472,7 @@ events on a given category of stream:
[Null Object Pattern](https://en.wikipedia.org/wiki/Null_object_pattern),
[Identity element](https://en.wikipedia.org/wiki/Identity_element)

- `fold : 'state -> 'event seq -> 'state`: function used to fold one or more
- `fold : 'state -> 'event[] -> 'state`: function used to fold one or more
loaded (or proposed) events (real ones and/or unfolded ones) into a given
running
[persistent data structure](https://en.wikipedia.org/wiki/Persistent_data_structure)
Expand Down Expand Up @@ -567,7 +567,7 @@ let evolve state = function
| Snapshotted items -> List.ofArray items
| Added item -> item :: state
| Removed id -> state |> List.filter (is id)
let fold state = Seq.fold evolve state
let fold = Array.fold evolve
(*
* Decision Processing to translate a Command's intent to Events that would
Expand Down Expand Up @@ -743,11 +743,11 @@ type Event =
| Added of string
| Removed of string
let initial : string list = []
let initial: string list = []
let evolve state = function
| Added sku -> sku :: state
| Removed sku -> state |> List.filter (fun x -> x <> sku)
let fold s xs = Seq.fold evolve s xs
let fold = Array.fold evolve
```

Events are represented as an F# Discriminated Union; see the [article on the
Expand Down Expand Up @@ -986,7 +986,7 @@ let evolve s = function
| Deleted id -> { s with items = s.items |> List.filter (fun x -> x.id <> id) }
| Cleared -> { s with items = [] }
| Snapshotted items -> { s with items = List.ofArray items }
let fold : State -> Events.Event seq -> State = Seq.fold evolve
let fold = Array.fold evolve
let isOrigin = function Cleared | Snapshotted _ -> true | _ -> false
let snapshot state = Snapshotted (Array.ofList state.items)
```
Expand Down Expand Up @@ -1257,7 +1257,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 @@ -1302,19 +1302,21 @@ There's an example of such a case in the
[Cart's Domain Service](https://github.com/jet/equinox/blob/master/samples/Store/Domain/Cart.fs#L128):

```fsharp
let interpretMany fold interpreters (state : 'state) : 'state * 'event list =
((state,[]),interpreters)
||> Seq.fold (fun (state : 'state, acc : 'event list) interpret ->
let events = interpret state
let state' = fold state events
state', acc @ events)
let interpretMany fold interpreters (state: 'state): 'state * 'event[] =
let mutable state = state
let events = [|
for interpret in interpreters do
let events = interpret state
yield! events
state <- fold state events |]
state, events
type Service internal (resolve : CartId -> Equinox.Decider<Events.Event, Fold.State>) =
type Service internal (resolve: CartId -> Equinox.Decider<Events.Event, Fold.State>) =
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
decider.Transact(fun state -> async {
decider.TransactAsync(fun state -> async {
match prepare with None -> () | Some prep -> do! prep
return interpretMany Fold.fold (Seq.map interpret commands) state }, opt)
```
Expand All @@ -1337,13 +1339,13 @@ first)
```fsharp
/// Maintains a rolling folded State while Accumulating Events pended as part
/// of a decision flow
type Accumulator<'event, 'state>(fold : 'state -> 'event seq -> 'state, originState : 'state) =
type Accumulator<'event, 'state>(fold : 'state -> 'event[] -> 'state, originState : 'state) =
let accumulated = ResizeArray<'event>()
/// The Events that have thus far been pended via the `decide` functions
/// `Execute`/`Decide`d during the course of this flow
member _.Accumulated : 'event list =
accumulated |> List.ofSeq
member _.Accumulated : 'event[] =
accumulated.ToArray()
/// The current folded State, based on the Stream's `originState` + any
/// events that have been Accumulated during the the decision flow
Expand All @@ -1352,22 +1354,22 @@ type Accumulator<'event, 'state>(fold : 'state -> 'event seq -> 'state, originSt
/// Invoke a decision function, gathering the events (if any) that it
/// decides are necessary into the `Accumulated` sequence
member x.Transact(interpret : 'state -> 'event list) : unit =
member x.Transact(interpret : 'state -> 'event[]) : unit =
interpret x.State |> accumulated.AddRange
/// Invoke an Async decision function, gathering the events (if any) that
/// it decides are necessary into the `Accumulated` sequence
member x.Transact(interpret : 'state -> Async<'event list>) : Async<unit> = async {
member x.Transact(interpret : 'state -> Async<'event[]>) : Async<unit> = async {
let! events = interpret x.State
accumulated.AddRange events }
/// Invoke a decision function, while also propagating a result yielded as
/// the fst of an (result, events) pair
member x.Transact(decide : 'state -> 'result * 'event list) : 'result =
member x.Transact(decide : 'state -> 'result * 'event[]) : 'result =
let result, newEvents = decide x.State
accumulated.AddRange newEvents
result
/// Invoke a decision function, while also propagating a result yielded as
/// the fst of an (result, events) pair
member x.Transact(decide : 'state -> Async<'result * 'event list>) : Async<'result> = async {
member x.Transact(decide : 'state -> Async<'result * 'event[]>) : Async<'result> = async {
let! result, newEvents = decide x.State
accumulated.AddRange newEvents
return result }
Expand Down Expand Up @@ -1851,7 +1853,7 @@ let! res =
eventData
match res with
| AppendResult.Ok -> ()
| c -> failwithf "conflict %A" c
| c -> c |> failwithf "conflict %A"
```
# Access Strategies

Expand Down
14 changes: 13 additions & 1 deletion Equinox.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Appendf/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Appendfn/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Autoscale/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=backoffs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Batcher/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Batchers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=emptor/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=esdb/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=etags/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Favorited/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=hypotheticals/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=idempotently/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=kvps/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=nfolds/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nullary/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=recomputation/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=resync/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Resyncs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=roundtripping/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=roundtrips/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Serdes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Snapshotted/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Uncompacted/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=stddev/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Uncompacted/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unfavorite/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=voronoipotato/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ The components within this repository are delivered as multi-targeted Nuget pack
- `Equinox.DynamoStore.Prometheus` [![DynamoStore.Prometheus NuGet](https://img.shields.io/nuget/v/Equinox.DynamoStore.Prometheus.svg)](https://www.nuget.org/packages/Equinox.DynamoStore.Prometheus/): Integration package providing a `Serilog.Core.ILogEventSink` that extracts detailed metrics information attached to the `LogEvent`s and feeds them to the `prometheus-net`'s `Prometheus.Metrics` static instance. ([depends](https://www.fuget.org/packages/Equinox.CosmosStore.Prometheus) on `Equinox.DynamoStore`, `prometheus-net >= 3.6.0`)
- `Equinox.EventStore` [![EventStore NuGet](https://img.shields.io/nuget/v/Equinox.EventStore.svg)](https://www.nuget.org/packages/Equinox.EventStore/): [EventStoreDB](https://eventstore.org/) Adapter designed to meet Jet's production monitoring requirements. ([depends](https://www.fuget.org/packages/Equinox.EventStore) on `Equinox.Core`, `EventStore.Client >= 22.0.0-preview`, `FSharp.Control.TaskSeq`), EventStore Server version `21.10` or later). **NO NOT use for new projects - the TCP interface to EventStoreDB has long been deprecated, this package is only provided to ease migration scenarios and will be removed in due course**
- `Equinox.EventStoreDb` [![EventStoreDb NuGet](https://img.shields.io/nuget/v/Equinox.EventStoreDb.svg)](https://www.nuget.org/packages/Equinox.EventStoreDb/): Production-strength [EventStoreDB](https://eventstore.org/) Adapter. ([depends](https://www.fuget.org/packages/Equinox.EventStoreDb) on `Equinox.Core`, `EventStore.Client.Grpc.Streams` >= `22.0.0`, `FSharp.Control.TaskSeq`, EventStore Server version `21.10` or later)
- `Equinox.MessageDb` [![MessageDb NuGet](https://img.shields.io/nuget/v/Equinox.MessageDb.svg)](https://www.nuget.org/packages/Equinox.MessageDb/): [MessageDb](http://docs.eventide-project.org/user-guide/message-db/) Adapter. ([depends](https://www.fuget.org/packages/Equinox.MessageDb) on `Equinox.Core`, `Npgsql` >= `6.0.0`, `FSharp.Control.TaskSeq`))
- `Equinox.MessageDb` [![MessageDb NuGet](https://img.shields.io/nuget/v/Equinox.MessageDb.svg)](https://www.nuget.org/packages/Equinox.MessageDb/): [MessageDb](http://docs.eventide-project.org/user-guide/message-db/) Adapter. ([depends](https://www.fuget.org/packages/Equinox.MessageDb) on `Equinox.Core`, `Npgsql` >= `7.0.0`, `FSharp.Control.TaskSeq`))
- `Equinox.SqlStreamStore` [![SqlStreamStore NuGet](https://img.shields.io/nuget/v/Equinox.SqlStreamStore.svg)](https://www.nuget.org/packages/Equinox.SqlStreamStore/): [SqlStreamStore](https://github.com/SQLStreamStore/SQLStreamStore) Adapter derived from `Equinox.EventStore` - provides core facilities (but does not connect to a specific database; see sibling `SqlStreamStore`.* packages). ([depends](https://www.fuget.org/packages/Equinox.SqlStreamStore) on `Equinox.Core`, `FsCodec`, `SqlStreamStore >= 1.2.0-beta.8`, `FSharp.Control.TaskSeq`)
- `Equinox.SqlStreamStore.MsSql` [![MsSql NuGet](https://img.shields.io/nuget/v/Equinox.SqlStreamStore.MsSql.svg)](https://www.nuget.org/packages/Equinox.SqlStreamStore.MsSql/): [SqlStreamStore.MsSql](https://sqlstreamstore.readthedocs.io/en/latest/sqlserver) Sql Server `Connector` implementation for `Equinox.SqlStreamStore` package). ([depends](https://www.fuget.org/packages/Equinox.SqlStreamStore.MsSql) on `Equinox.SqlStreamStore`, `SqlStreamStore.MsSql >= 1.2.0-beta.8`)
- `Equinox.SqlStreamStore.MySql` [![MySql NuGet](https://img.shields.io/nuget/v/Equinox.SqlStreamStore.MySql.svg)](https://www.nuget.org/packages/Equinox.SqlStreamStore.MySql/): `SqlStreamStore.MySql` MySQL `Connector` implementation for `Equinox.SqlStreamStore` package). ([depends](https://www.fuget.org/packages/Equinox.SqlStreamStore.MySql) on `Equinox.SqlStreamStore`, `SqlStreamStore.MySql >= 1.2.0-beta.8`)
Expand Down
2 changes: 1 addition & 1 deletion samples/Infrastructure/Infrastructure.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="FSharp.Core" Version="6.0.7" />
<PackageReference Include="FSharp.Core" Version="6.0.7" ExcludeAssets="contentfiles" />

<PackageReference Include="Argu" Version="6.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
Expand Down
2 changes: 1 addition & 1 deletion samples/Infrastructure/Services.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type Store(store) =
member _.Category
( name,
codec: FsCodec.IEventCodec<'event, ReadOnlyMemory<byte>, unit>,
fold: 'state -> 'event seq -> 'state,
fold: 'state -> 'event[] -> 'state,
initial: 'state,
snapshot: ('event -> bool) * ('state -> 'event)): Category<'event, 'state, unit> =
match store with
Expand Down
Loading

0 comments on commit 6df7c58

Please sign in to comment.