From 60160a9a903cda2795ef338f07f9a0dbd48853ae Mon Sep 17 00:00:00 2001 From: Ruben Bartelink Date: Mon, 24 Dec 2018 10:14:05 +0000 Subject: [PATCH] Cleanup --- Directory.Build.props | 5 +++-- README.md | 17 ++++++++--------- build.proj | 10 +++++----- build.ps1 | 4 ++-- samples/Infrastructure/Storage.fs | 12 ++++++------ samples/Store/Integration/CartIntegration.fs | 4 ++++ samples/Store/Integration/CodecIntegration.fs | 4 ++++ .../ContactPreferencesIntegration.fs | 4 ++++ .../Store/Integration/FavoritesIntegration.fs | 4 ++++ src/Equinox.Cosmos/Equinox.Cosmos.fsproj | 2 ++ .../Equinox.EventStore.fsproj | 2 +- .../CosmosIntegration.fs | 3 ++- 12 files changed, 45 insertions(+), 26 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index bebac5421..5b9b3b1cd 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -20,8 +20,9 @@ false - - $(NoWarn);FS2003;NU5105 + + + $(NoWarn);FS2003;NU5105;FS0988 diff --git a/README.md b/README.md index 3c9fc509f..dbbf241b8 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,14 @@ _If you're looking to learn more about and/or discuss Event Sourcing and it's my - Logging is both high performance and pluggable (using [Serilog](https://github.com/serilog/serilog) to your hosting context (we feed log info to Splunk and the metrics embedded in the LogEvent Properties to Prometheus; see relevant tests for examples) - Extracted from working software; currently used for all data storage within Jet's API gateway and Cart processing. - Significant test coverage for core facilities, and per Storage system. -- **`Equinox.EventStore` Transactionally-consistent Rolling Snapshots**: Command processing can be optimized by employing in-stream 'compaction' events in service of the following ends: +- **`Equinox.EventStore` In-stream Rolling Snapshots**: Command processing can be optimized by employing 'compaction' events in service of the following ends: - no additional roundtrips to the store needed at either the Load or Sync points in the flow - support, (via `UnionContractEncoder`) for the maintenance of multiple co-existing compaction schemas in a given stream (A snapshot isa Event) - compaction events typically do not get deleted (consistent with how EventStore works), although it is safe to do so in concept - NB while this works well, and can deliver excellent performance (especially when allied with the Cache), [it's not a panacea, as noted in this excellent EventStore article on the topic](https://eventstore.org/docs/event-sourcing-basics/rolling-snapshots/index.html) - **`Equinox.Cosmos` 'Tip with Unfolds' schema**: In contrast to `Equinox.EventStore`'s `Access.RollingSnapshots`, when using `Equinox.Cosmos`, optimized command processing is managed via the `Tip`; a document per stream with a well-known identity enabling syncs via point-reads by virtue of the fact that the document maintains: a) the present Position of the stream - i.e. the index at which the next events will be appended - b) (compressed) [_unfolds_]((https://github.com/jet/equinox/wiki/Cosmos-Storage-Model) + b) (compressed) [_unfolds_](https://github.com/jet/equinox/wiki/Cosmos-Storage-Model) c) (optionally) events since those unfolded events ([presently removed](https://github.com/jet/equinox/pull/58), but [should return](https://github.com/jet/equinox/wiki/Roadmap)) This yields many of the benefits of the in-stream Rolling Snapshots approach while reducing latency, RU provisioning requirement, and Request Charges:- @@ -48,21 +48,21 @@ _If you're looking to learn more about and/or discuss Event Sourcing and it's my The Equinox components within this repository are delivered as a series of multi-targeted Nuget packages targeting `net461` (F# 3.1+) and `netstandard2.0` (F# 4.5+) profiles; each of the constituent elements is designed to be easily swappable as dictated by the task at hand. Each of the components can be inlined or customized easily:- - `Equinox.Handler` (Nuget: `Equinox`, depends on `Serilog` (but no specific Serilog sinks, i.e. you can forward to `NLog` etc)): Store-agnostic decision flow runner that manages the optimistic concurrency protocol -- `Equinox.Codec` (Nuget: `Equinox.Codec`, depends on `TypeShape`, (`Newtonsoft.Json` `>= 10.0.3` on `net461`, `>= 11.0.2` on `netstandard2.0` but can support any serializer): [a scheme for the serializing Events modelled as an F# Discriminated Union with the following capabilities](https://eiriktsarpalis.wordpress.com/2018/10/30/a-contract-pattern-for-schemaless-datastores/): +- `Equinox.Codec` (Nuget: `Equinox.Codec`, depends on `TypeShape`, `Newtonsoft.Json` (`>= 10.0.3` on `net461`, `>= 11.0.2` on `netstandard2.0` but can support any serializer): [a scheme for the serializing Events modelled as an F# Discriminated Union with the following capabilities](https://eiriktsarpalis.wordpress.com/2018/10/30/a-contract-pattern-for-schemaless-datastores/): - independent of any specific serializer - - allows tagging of Discriminated Union cases in a versionable manner with low-dependency `DataMember(Name=` tags using [TypeShape](https://github.com/eiriktsarpalis/TypeShape)'s [`UnionContractEncoder`](https://github.com/eiriktsarpalis/TypeShape/blob/master/tests/TypeShape.Tests/UnionContractTests.fs) -- `Equinox.Cosmos` (Nuget: `Equinox.Cosmos`, depends on `System.Runtime.Caching`, `FSharp.Control.AsyncSeq`, `TypeShape`, `Microsoft.Azure.DocumentDb[.Core]`): Production-strength Azure CosmosDb Adapter with integrated transactionally-consistent snapshotting, facilitating optimal read performance in terms of latency and RU costs, instrumented to the degree necessitated by Jet's production monitoring requirements. -- `Equinox.EventStore` (Nuget: `Equinox.EventStore`, depends on `EventStore.Client[Api.NetCore] >= 4`, `System.Runtime.Caching`, `FSharp.Control.AsyncSeq`, `TypeShape`): Production-strength [EventStore](https://eventstore.org/) Adapter instrumented to the degree necessitated by Jet's production monitoring requirements + - allows tagging of F# Discriminated Union cases in a versionable manner with low-dependency `DataMember(Name=` tags using [TypeShape](https://github.com/eiriktsarpalis/TypeShape)'s [`UnionContractEncoder`](https://github.com/eiriktsarpalis/TypeShape/blob/master/tests/TypeShape.Tests/UnionContractTests.fs) +- `Equinox.Tool` (Nuget: `dotnet tool install Equinox.Tool -g`): Tool incorporating a benchmark scenario runner, facilitating running representative load tests composed of transactions in `samples/Store` and `samples/TodoBackend` against any nominated store; this allows perf tuning and measurement in terms of both latency and transaction charge aspects. - `Equinox.MemoryStore` (Nuget: `Equinox.MemoryStore`): In-memory store for integration testing/performance baselining/providing out-of-the-box zero dependency storage for examples. +- `Equinox.EventStore` (Nuget: `Equinox.EventStore`, depends on `EventStore.Client[Api.NetCore] >= 4`, `System.Runtime.Caching`, `FSharp.Control.AsyncSeq`): Production-strength [EventStore](https://eventstore.org/) Adapter instrumented to the degree necessitated by Jet's production monitoring requirements +- `Equinox.Cosmos` (Nuget: `Equinox.Cosmos`, depends on `System.Runtime.Caching`, `FSharp.Control.AsyncSeq`, `Microsoft.Azure.DocumentDb[.Core]`): Production-strength Azure CosmosDb Adapter with integrated 'unfolds' feature, facilitating optimal read performance in terms of latency and RU costs, instrumented to the degree necessitated by Jet's production monitoring requirements. - `samples/Store` (in this repo): Example domain types reflecting examples of how one applies Equinox to a diverse set of stream-based models - `samples/TodoBackend` (in this repo): Standard https://todobackend.com compliant backend -- `Equinox.Tool` (Nuget: `dotnet tool install Equinox.Tool -g`): Tool incorporating a benchmark scenario runner, facilitating running representative load tests composed of transactions in `samples/Store` and `samples/TodoBackend` against any nominated store; this allows perf tuning and measurement in terms of both latency and transaction charge aspects. ## Versioning ## About Versioning -The repo is versioned based on [SemVer 2.0](https://semver.org/spec/v2.0.0.html) using the tiny-but-mighty [MinVer](https://github.com/adamralph/minver) from @adamralph. [See here](https://github.com/adamralph/minver#how-it-works) for more information on how it works. +The repo is versioned based on [SemVer 2.0](https://semver.org/spec/v2.0.0.html) using the tiny-but-mighty [MinVer](https://github.com/adamralph/minver) from [@adamralph](https://github.com/adamralph). [See here](https://github.com/adamralph/minver#how-it-works) for more information on how it works. ## CONTRIBUTING @@ -174,7 +174,6 @@ The CLI can drive the Store and TodoBackend samples in the `samples/Web` ASP.NET eqx run -t saveforlater -f 200 web ### run CosmosDb benchmark (when provisioned) - $env:EQUINOX_COSMOS_CONNECTION="AccountEndpoint=https://....;AccountKey=....=;" $env:EQUINOX_COSMOS_DATABASE="equinox-test" $env:EQUINOX_COSMOS_COLLECTION="equinox-test" diff --git a/build.proj b/build.proj index fd1713fa0..96eeef436 100644 --- a/build.proj +++ b/build.proj @@ -15,11 +15,11 @@ - - - - - + + + + + diff --git a/build.ps1 b/build.ps1 index 49d61f363..54842ad50 100644 --- a/build.ps1 +++ b/build.ps1 @@ -21,8 +21,8 @@ $env:EQUINOX_INTEGRATION_SKIP_EVENTSTORE=[string]$skipEs if ($skipEs) { warn "Skipping EventStore tests" } function cliCosmos($arghs) { - Write-Host "dotnet run cli/Equinox.Cli -- $arghs cosmos -s -d $cosmosDatabase -c $cosmosCollection" - dotnet run -p cli/Equinox.Cli -f netcoreapp2.1 -- @arghs cosmos -s $cosmosServer -d $cosmosDatabase -c $cosmosCollection + Write-Host "dotnet run tools/Equinox.Tool -- $arghs cosmos -s -d $cosmosDatabase -c $cosmosCollection" + dotnet run -p tools/Equinox.Tool -f netcoreapp2.1 -- @arghs cosmos -s $cosmosServer -d $cosmosDatabase -c $cosmosCollection } if ($skipCosmos) { diff --git a/samples/Infrastructure/Storage.fs b/samples/Infrastructure/Storage.fs index 873d829df..cabf2bdf2 100644 --- a/samples/Infrastructure/Storage.fs +++ b/samples/Infrastructure/Storage.fs @@ -9,7 +9,7 @@ type [] MemArguments = interface IArgParserTemplate with member a.Usage = a |> function | VerboseStore -> "Include low level Store logging." -type [] EsArguments = +and [] EsArguments = | [] VerboseStore | [] Timeout of float | [] Retries of int @@ -73,7 +73,7 @@ module EventStore = heartbeatTimeout=heartbeatTimeout, concurrentOperationsLimit = col, log=(if log.IsEnabled(Serilog.Events.LogEventLevel.Debug) then Logger.SerilogVerbose log else Logger.SerilogNormal log), tags=["M", Environment.MachineName; "I", Guid.NewGuid() |> string]) - .Establish("equinox-tool", Discovery.GossipDns dnsQuery, ConnectionStrategy.ClusterTwinPreferSlaveReads) + .Establish("equinox-samples", Discovery.GossipDns dnsQuery, ConnectionStrategy.ClusterTwinPreferSlaveReads) let private createGateway connection batchSize = GesGateway(connection, GesBatchingPolicy(maxBatchSize = batchSize)) let config (log: ILogger, storeLog) (cache, unfolds) (sargs : ParseResults) = let host = sargs.GetResult(Host,"localhost") @@ -89,7 +89,7 @@ module EventStore = let conn = connect storeLog (host, heartbeatTimeout, concurrentOperationsLimit) creds operationThrottling |> Async.RunSynchronously let cacheStrategy = if cache then - let c = Caching.Cache("Cli", sizeMb = 50) + let c = Caching.Cache("equinox-samples", sizeMb = 50) CachingStrategy.SlidingWindow (c, TimeSpan.FromMinutes 20.) |> Some else None StorageConfig.Es ((createGateway conn defaultBatchSize), cacheStrategy, unfolds) @@ -99,11 +99,11 @@ module Cosmos = /// Standing up an Equinox instance is necessary to run for test purposes; You'll need to either: /// 1) replace connection below with a connection string or Uri+Key for an initialized Equinox instance with a database and collection named "equinox-test" - /// 2) Set the 3x environment variables and create a local Equinox using cli/Equinox.cli/bin/Release/net461/Equinox.Cli ` + /// 2) Set the 3x environment variables and create a local Equinox using tools/Equinox.Tool/bin/Release/net461/eqx.exe ` /// cosmos -s $env:EQUINOX_COSMOS_CONNECTION -d $env:EQUINOX_COSMOS_DATABASE -c $env:EQUINOX_COSMOS_COLLECTION provision -ru 1000 let private connect (log: ILogger) mode discovery operationTimeout (maxRetryForThrottling, maxRetryWaitTime) = EqxConnector(log=log, mode=mode, requestTimeout=operationTimeout, maxRetryAttemptsOnThrottledRequests=maxRetryForThrottling, maxRetryWaitTimeInSeconds=maxRetryWaitTime) - .Connect("equinox-cli", discovery) + .Connect("equinox-samples", discovery) let private createGateway connection (maxItems,maxEvents) = EqxGateway(connection, EqxBatchingPolicy(defaultMaxItems=maxItems, maxEventsPerSlice=maxEvents)) let conn (log: ILogger, storeLog) (sargs : ParseResults) = let read key = Environment.GetEnvironmentVariable key |> Option.ofObj @@ -126,7 +126,7 @@ module Cosmos = let dbName, collName, pageSize, conn = conn (log, storeLog) sargs let cacheStrategy = if cache then - let c = Caching.Cache("Cli", sizeMb = 50) + let c = Caching.Cache("equinox-tool", sizeMb = 50) CachingStrategy.SlidingWindow (c, TimeSpan.FromMinutes 20.) |> Some else None StorageConfig.Cosmos (createGateway conn (defaultBatchSize,pageSize), cacheStrategy, unfolds, dbName, collName) \ No newline at end of file diff --git a/samples/Store/Integration/CartIntegration.fs b/samples/Store/Integration/CartIntegration.fs index 291feb431..16c451eee 100644 --- a/samples/Store/Integration/CartIntegration.fs +++ b/samples/Store/Integration/CartIntegration.fs @@ -5,6 +5,7 @@ open Equinox.Cosmos.Integration open Equinox.EventStore open Equinox.MemoryStore open Swensen.Unquote +open Xunit #nowarn "1182" // From hereon in, we may have some 'unused' privates (the tests) @@ -47,6 +48,9 @@ type Tests(testOutputHelper) = } [] +#if NET461 + [] // Likely due to net461 not having consistent json.net refs and no binding redirects +#endif let ``Can roundtrip in Memory, correctly folding the events`` args = Async.RunSynchronously <| async { let log, store = createLog (), createMemoryStore () let service = createServiceMem log store diff --git a/samples/Store/Integration/CodecIntegration.fs b/samples/Store/Integration/CodecIntegration.fs index 89eea866e..9fa894a87 100644 --- a/samples/Store/Integration/CodecIntegration.fs +++ b/samples/Store/Integration/CodecIntegration.fs @@ -4,6 +4,7 @@ module Samples.Store.Integration.CodecIntegration open Domain open Swensen.Unquote open TypeShape.UnionContract +open Xunit let serializationSettings = Newtonsoft.Json.Converters.FSharp.Settings.CreateCorrect(converters= @@ -42,6 +43,9 @@ let render = function let codec = genCodec() [] +#if NET461 + [] // Likely due to net461 not having consistent json.net refs and no binding redirects +#endif let ``Can roundtrip, rendering correctly`` (x: SimpleDu) = let serialized = codec.Encode x render x =! System.Text.Encoding.UTF8.GetString(serialized.payload) diff --git a/samples/Store/Integration/ContactPreferencesIntegration.fs b/samples/Store/Integration/ContactPreferencesIntegration.fs index 571d079bb..91a527301 100644 --- a/samples/Store/Integration/ContactPreferencesIntegration.fs +++ b/samples/Store/Integration/ContactPreferencesIntegration.fs @@ -5,6 +5,7 @@ open Equinox.Cosmos.Integration open Equinox.EventStore open Equinox.MemoryStore open Swensen.Unquote +open Xunit #nowarn "1182" // From hereon in, we may have some 'unused' privates (the tests) @@ -38,6 +39,9 @@ type Tests(testOutputHelper) = test <@ value = actual @> } [] +#if NET461 + [] // Likely due to net461 not having consistent json.net refs and no binding redirects +#endif let ``Can roundtrip in Memory, correctly folding the events`` args = Async.RunSynchronously <| async { let service = let log, store = createLog (), createMemoryStore () in createServiceMem log store do! act service args diff --git a/samples/Store/Integration/FavoritesIntegration.fs b/samples/Store/Integration/FavoritesIntegration.fs index c3a382278..bbf603124 100644 --- a/samples/Store/Integration/FavoritesIntegration.fs +++ b/samples/Store/Integration/FavoritesIntegration.fs @@ -5,6 +5,7 @@ open Equinox.Cosmos.Integration open Equinox.EventStore open Equinox.MemoryStore open Swensen.Unquote +open Xunit #nowarn "1182" // From hereon in, we may have some 'unused' privates (the tests) @@ -40,6 +41,9 @@ type Tests(testOutputHelper) = test <@ Array.isEmpty items @> } [] +#if NET461 + [] // Likely due to net461 not having consistent json.net refs and no binding redirects +#endif let ``Can roundtrip in Memory, correctly folding the events`` args = Async.RunSynchronously <| async { let store = createMemoryStore () let service = let log = createLog () in createServiceMem log store diff --git a/src/Equinox.Cosmos/Equinox.Cosmos.fsproj b/src/Equinox.Cosmos/Equinox.Cosmos.fsproj index 5377af83e..4572b39b5 100644 --- a/src/Equinox.Cosmos/Equinox.Cosmos.fsproj +++ b/src/Equinox.Cosmos/Equinox.Cosmos.fsproj @@ -23,6 +23,8 @@ + + diff --git a/src/Equinox.EventStore/Equinox.EventStore.fsproj b/src/Equinox.EventStore/Equinox.EventStore.fsproj index 92af6061c..27f1a0810 100644 --- a/src/Equinox.EventStore/Equinox.EventStore.fsproj +++ b/src/Equinox.EventStore/Equinox.EventStore.fsproj @@ -7,7 +7,7 @@ false true true - NET461 + $(DefineConstants);NET461 diff --git a/tests/Equinox.Cosmos.Integration/CosmosIntegration.fs b/tests/Equinox.Cosmos.Integration/CosmosIntegration.fs index 23db4fa03..e2742db14 100644 --- a/tests/Equinox.Cosmos.Integration/CosmosIntegration.fs +++ b/tests/Equinox.Cosmos.Integration/CosmosIntegration.fs @@ -12,7 +12,8 @@ let genCodec<'Union when 'Union :> TypeShape.UnionContract.IUnionContract>() = Equinox.UnionCodec.JsonUtf8.Create<'Union>(serializationSettings) module Cart = - let fold, initial, snapshot = Domain.Cart.Folds.fold, Domain.Cart.Folds.initial, Domain.Cart.Folds.snapshot + let fold, initial = Domain.Cart.Folds.fold, Domain.Cart.Folds.initial + let snapshot = Domain.Cart.Folds.isOrigin, Domain.Cart.Folds.compact let codec = genCodec() let createServiceWithoutOptimization connection batchSize log = let store = createEqxStore connection batchSize