Skip to content

Commit

Permalink
Move Cache/Caching into Equinox
Browse files Browse the repository at this point in the history
  • Loading branch information
bartelink committed Aug 9, 2023
1 parent caee095 commit 8bdcf9d
Show file tree
Hide file tree
Showing 14 changed files with 50 additions and 53 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,14 @@ The components within this repository are delivered as multi-targeted Nuget pack
## Data Store libraries

- `Equinox.Core` [![NuGet](https://img.shields.io/nuget/v/Equinox.Core.svg)](https://www.nuget.org/packages/Equinox.Core/): Interfaces and helpers used in the concrete Store implementations, together with the default [`System.Runtime.Caching.MemoryCache`-based] `Cache` implementation. Hosts generic utility types frequently useful alongside Equinox: [`AsyncCacheCell`](https://github.com/jet/equinox/blob/master/src/Equinox.Core/AsyncCacheCell.fs#L36), [`Batcher`, `BatcherCache`, `BatcherDictionary`](https://github.com/jet/equinox/blob/master/src/Equinox.Core/Batching.fs#L44). ([depends](https://www.fuget.org/packages/Equinox.Core) on `Equinox`, `System.Runtime.Caching`)
- `Equinox.MemoryStore` [![MemoryStore NuGet](https://img.shields.io/nuget/v/Equinox.MemoryStore.svg)](https://www.nuget.org/packages/Equinox.MemoryStore/): In-memory store for integration testing/performance base-lining/providing out-of-the-box zero dependency storage for examples. ([depends](https://www.fuget.org/packages/Equinox.MemoryStore) on `Equinox.Core`, `FsCodec`)
- `Equinox.MemoryStore` [![MemoryStore NuGet](https://img.shields.io/nuget/v/Equinox.MemoryStore.svg)](https://www.nuget.org/packages/Equinox.MemoryStore/): In-memory store for integration testing/performance base-lining/providing out-of-the-box zero dependency storage for examples. ([depends](https://www.fuget.org/packages/Equinox.MemoryStore) on `Equinox`, `FsCodec`)
- `Equinox.CosmosStore` [![CosmosStore NuGet](https://img.shields.io/nuget/v/Equinox.CosmosStore.svg)](https://www.nuget.org/packages/Equinox.CosmosStore/): Azure CosmosDB Adapter with integrated 'unfolds' feature, facilitating optimal read performance in terms of latency and RU costs, instrumented to meet Jet's production monitoring requirements. ([depends](https://www.fuget.org/packages/Equinox.CosmosStore) on `Equinox.Core`, `Microsoft.Azure.Cosmos` >= `3.27`, `FsCodec`, `System.Text.Json`, `FSharp.Control.TaskSeq`)
- `Equinox.CosmosStore.Prometheus` [![CosmosStore.Prometheus NuGet](https://img.shields.io/nuget/v/Equinox.CosmosStore.Prometheus.svg)](https://www.nuget.org/packages/Equinox.CosmosStore.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.CosmosStore`, `prometheus-net >= 3.6.0`)
- `Equinox.DynamoStore` [![DynamoStore NuGet](https://img.shields.io/nuget/v/Equinox.DynamoStore.svg)](https://www.nuget.org/packages/Equinox.DynamoStore/): Amazon DynamoDB Adapter with integrated 'unfolds' feature, facilitating optimal read performance in terms of latency and RC costs, patterned after `Equinox.CosmosStore`. ([depends](https://www.fuget.org/packages/Equinox.DynamoStore) on `Equinox.Core`, `FSharp.AWS.DynamoDB` >= `0.11.2-beta`, `FsCodec`, `FSharp.Control.TaskSeq`)
- `Equinox.DynamoStore` [![DynamoStore NuGet](https://img.shields.io/nuget/v/Equinox.DynamoStore.svg)](https://www.nuget.org/packages/Equinox.DynamoStore/): Amazon DynamoDB Adapter with integrated 'unfolds' feature, facilitating optimal read performance in terms of latency and RC costs, patterned after `Equinox.CosmosStore`. ([depends](https://www.fuget.org/packages/Equinox.DynamoStore) on `Equinox`, `FSharp.AWS.DynamoDB` >= `0.11.2-beta`, `FsCodec`, `FSharp.Control.TaskSeq`)
- `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` >= `7.0.0`, `FSharp.Control.TaskSeq`))
- `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`, `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`, `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`, `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
34 changes: 1 addition & 33 deletions src/Equinox.Core/AsyncCacheCell.fs
Original file line number Diff line number Diff line change
@@ -1,41 +1,9 @@
namespace Equinox.Core

open System

/// Asynchronous Lazy<'T> used to gate a workflow to ensure at most once execution of a computation.
type AsyncLazy<'T>(startTask: Func<Task<'T>>) =

// NOTE due to `Lazy<T>` semantics, failed attempts will cache any exception; AsyncCacheCell compensates for this by rolling over to a new instance
let workflow = lazy startTask.Invoke()

/// Synchronously peek at what's been previously computed (if it's not Empty, or the last attempt Faulted).
member _.TryCompleted() =
if not workflow.IsValueCreated then ValueNone else

let t = workflow.Value
if t.Status <> System.Threading.Tasks.TaskStatus.RanToCompletion then ValueNone
else ValueSome t.Result

/// Used to rule out values where the computation yielded an exception or the result has now expired
member _.TryAwaitValid() = task {
let t = workflow.Value

// Determines if the last attempt completed, but failed; For TMI see https://stackoverflow.com/a/33946166/11635
if t.IsFaulted then return ValueNone
else
let! res = t
return ValueSome res }

/// Await the outcome of the computation.
member _.Await() = workflow.Value

/// Singleton Empty value
static member val Empty = AsyncLazy(fun () -> Task.FromException<'T>(InvalidOperationException "Uninitialized AsyncLazy"))

/// Generic async lazy caching implementation that admits expiration/recomputation/retry on exception semantics.
/// If `workflow` fails, all readers entering while the load/refresh is in progress will share the failure
/// The first caller through the gate triggers a recomputation attempt if the previous attempt ended in failure
type AsyncCacheCell<'T>(startWorkflow : Func<CancellationToken, Task<'T>>, [<O; D null>]?isExpired: Func<'T, bool>) =
type AsyncCacheCell<'T>(startWorkflow : System.Func<CancellationToken, Task<'T>>, [<O; D null>]?isExpired: System.Func<'T, bool>) =

let isValid = match isExpired with Some f -> not << f.Invoke | None -> fun _ -> true
let mutable cell = AsyncLazy<'T>.Empty
Expand Down
4 changes: 0 additions & 4 deletions src/Equinox.Core/Equinox.Core.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@

<ItemGroup>
<Compile Include="Infrastructure.fs" />
<Compile Include="StopwatchInterval.fs" />
<Compile Include="AsyncCacheCell.fs" />
<Compile Include="Cache.fs" />
<Compile Include="Caching.fs" />
<Compile Include="Retry.fs" />
<Compile Include="Batching.fs" />
</ItemGroup>
Expand All @@ -23,7 +20,6 @@

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

<PackageReference Include="System.Runtime.Caching" Version="6.0.0" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion src/Equinox.DynamoStore/Equinox.DynamoStore.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Equinox.Core\Equinox.Core.fsproj" />
<ProjectReference Include="..\Equinox\Equinox.fsproj" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion src/Equinox.EventStore/Equinox.EventStore.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Equinox.Core\Equinox.Core.fsproj" />
<ProjectReference Include="..\Equinox\Equinox.fsproj" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Equinox.EventStoreDb/Equinox.EventStoreDb.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Equinox.Core\Equinox.Core.fsproj" />
<ProjectReference Include="..\Equinox\Equinox.fsproj" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Equinox.MessageDb/Equinox.MessageDb.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Equinox.Core\Equinox.Core.fsproj" />
<ProjectReference Include="..\Equinox\Equinox.fsproj" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Equinox.SqlStreamStore/Equinox.SqlStreamStore.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Equinox.Core\Equinox.Core.fsproj" />
<ProjectReference Include="..\Equinox\Equinox.fsproj" />
</ItemGroup>

<ItemGroup>
Expand Down
31 changes: 31 additions & 0 deletions src/Equinox/AsyncLazy.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace Equinox.Core

/// Asynchronous Lazy<'T> used to gate a workflow to ensure at most once execution of a computation.
type AsyncLazy<'T>(startTask: System.Func<Task<'T>>) =

// NOTE due to `Lazy<T>` semantics, failed attempts will cache any exception; AsyncCacheCell compensates for this by rolling over to a new instance
let workflow = lazy startTask.Invoke()

/// Synchronously peek at what's been previously computed (if it's not Empty, or the last attempt Faulted).
member _.TryCompleted() =
if not workflow.IsValueCreated then ValueNone else

let t = workflow.Value
if t.Status <> System.Threading.Tasks.TaskStatus.RanToCompletion then ValueNone
else ValueSome t.Result

/// Used to rule out values where the computation yielded an exception or the result has now expired
member _.TryAwaitValid() = task {
let t = workflow.Value

// Determines if the last attempt completed, but failed; For TMI see https://stackoverflow.com/a/33946166/11635
if t.IsFaulted then return ValueNone
else
let! res = t
return ValueSome res }

/// Await the outcome of the computation.
member _.Await() = workflow.Value

/// Singleton Empty value
static member val Empty = AsyncLazy(fun () -> Task.FromException<'T>(System.InvalidOperationException "Uninitialized AsyncLazy"))
File renamed without changes.
4 changes: 1 addition & 3 deletions src/Equinox.Core/Caching.fs → src/Equinox/Caching.fs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
module Equinox.Core.Caching

open Serilog

type IReloadable<'state> =
abstract Reload: log: ILogger * streamName: string * requireLeader: bool * streamToken: StreamToken * state: 'state * ct: CancellationToken
abstract Reload: log: Serilog.ILogger * streamName: string * requireLeader: bool * streamToken: StreamToken * state: 'state * ct: CancellationToken
-> Task<struct (StreamToken * 'state)>

let private tee f (inner: CancellationToken -> Task<struct (StreamToken * 'state)>) ct = task {
Expand Down
5 changes: 5 additions & 0 deletions src/Equinox/Equinox.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@

<ItemGroup>
<Compile Include="..\Equinox.Core\Infrastructure.fs" Link="Infrastructure.fs" />
<Compile Include="StopwatchInterval.fs" />
<Compile Include="Tracing.fs" />
<Compile Include="Stream.fs" />
<Compile Include="Decider.fs" />
<Compile Include="Category.fs" />
<Compile Include="AsyncLazy.fs" />
<Compile Include="Cache.fs" />
<Compile Include="Caching.fs" />
</ItemGroup>

<ItemGroup>
Expand All @@ -22,6 +26,7 @@

<PackageReference Include="FsCodec" Version="3.0.0-rc.10.1" />
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="System.Runtime.Caching" Version="6.0.0" />

</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ type Stopwatch =
double ticks / ticksPerSecond

/// Represents a time measurement of a computation that includes the Stopwatch Timestamp metadata
and [<Struct; NoEquality; NoComparison>] StopwatchInterval(startTicks: int64, endTicks: int64) =
type [<Struct; NoEquality; NoComparison>] StopwatchInterval(startTicks: int64, endTicks: int64) =
// do if startTicks < 0L || startTicks > endTicks then invalidArg "ticks" "tick arguments do not form a valid interval."
member _.StartTicks = startTicks
member _.EndTicks = endTicks
member _.Elapsed = Stopwatch.TicksToSeconds(endTicks - startTicks) |> System.TimeSpan.FromSeconds
member _.ElapsedMilliseconds = Stopwatch.TicksToSeconds(endTicks - startTicks) * 1000.
override x.ToString () = sprintf "%g ms" x.ElapsedMilliseconds
override x.ToString () = $"%g{x.ElapsedMilliseconds} ms"

module Stopwatch =

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
module Equinox.Store.Integration.DocumentStoreIntegration

open Domain
open Equinox.Core
open FSharp.UMX
open Swensen.Unquote
open System
Expand Down

0 comments on commit 8bdcf9d

Please sign in to comment.