Skip to content

Commit

Permalink
Version 3 (#28)
Browse files Browse the repository at this point in the history
* Kicking off version 3 development - #27

* Version 3 complete for Cosmos & TableStorage #27

* Cleaning up collection -> container

* Fixing InMemory + dependencies

* Position changed to Version but with back-compat #27

* Tests refactor to test back-compat WIP #27

* Back compat now fully tested for CosmosDb #27

* Back compat now fully tested for TableStorage #27

* Show error version in exception message #26

* Ready for v3 release
  • Loading branch information
Dzoukr authored Oct 14, 2019
1 parent f8fb2dd commit f2c32e0
Show file tree
Hide file tree
Showing 48 changed files with 4,435 additions and 3,328 deletions.
12 changes: 12 additions & 0 deletions CosmoStore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "CosmoStore.InMemory", "src\
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "CosmoStore.InMemory.Tests", "tests\CosmoStore.InMemory.Tests\CosmoStore.InMemory.Tests.fsproj", "{A11AC6EB-9E23-4E9F-87F0-99EF38F0779B}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "CosmoStore.CosmosDb.Tests.V2", "tests\CosmoStore.CosmosDb.Tests.V2\CosmoStore.CosmosDb.Tests.V2.fsproj", "{FE7B0AD7-2BF3-422B-8F34-1A6EA9D981FE}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "CosmoStore.TableStorage.Tests.V2", "tests\CosmoStore.TableStorage.Tests.V2\CosmoStore.TableStorage.Tests.V2.fsproj", "{049F25E8-E168-4392-B5BC-D728503AA669}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -69,6 +73,14 @@ Global
{A11AC6EB-9E23-4E9F-87F0-99EF38F0779B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A11AC6EB-9E23-4E9F-87F0-99EF38F0779B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A11AC6EB-9E23-4E9F-87F0-99EF38F0779B}.Release|Any CPU.Build.0 = Release|Any CPU
{FE7B0AD7-2BF3-422B-8F34-1A6EA9D981FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FE7B0AD7-2BF3-422B-8F34-1A6EA9D981FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FE7B0AD7-2BF3-422B-8F34-1A6EA9D981FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FE7B0AD7-2BF3-422B-8F34-1A6EA9D981FE}.Release|Any CPU.Build.0 = Release|Any CPU
{049F25E8-E168-4392-B5BC-D728503AA669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{049F25E8-E168-4392-B5BC-D728503AA669}.Debug|Any CPU.Build.0 = Debug|Any CPU
{049F25E8-E168-4392-B5BC-D728503AA669}.Release|Any CPU.ActiveCfg = Release|Any CPU
{049F25E8-E168-4392-B5BC-D728503AA669}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
99 changes: 45 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,33 @@ F# Event Store library for various storage providers (Cosmos DB, Table Storage,

## Available storage providers

| Storage Provider | Package | Version | Author
|---|---|---|---|
| none (API definition only) | CosmoStore | [![NuGet](https://img.shields.io/nuget/v/CosmoStore.svg?style=flat)](https://www.nuget.org/packages/CosmoStore/) | @dzoukr |
| Azure Cosmos DB | CosmoStore.CosmosDb | [![NuGet](https://img.shields.io/nuget/v/CosmoStore.CosmosDb.svg?style=flat)](https://www.nuget.org/packages/CosmoStore.CosmosDb/) |@dzoukr |
| Azure Table Storage | CosmoStore.TableStorage | [![NuGet](https://img.shields.io/nuget/v/CosmoStore.TableStorage.svg?style=flat)](https://www.nuget.org/packages/CosmoStore.TableStorage/) | @dzoukr |
| Marten (Postgres) | CosmoStore.Marten | [![NuGet](https://img.shields.io/nuget/v/CosmoStore.Marten.svg?style=flat)](https://www.nuget.org/packages/CosmoStore.Marten/) | @kunjee
| InMemory | CosmoStore.InMemory | [![NuGet](https://img.shields.io/nuget/v/CosmoStore.InMemory.svg?style=flat)](https://www.nuget.org/packages/CosmoStore.InMemory/) | @kunjee
| Storage Provider | Payload type | Package | Version | Author
|---|---|---|---|---|
| none (API definition only) | - | CosmoStore | [![NuGet](https://img.shields.io/nuget/v/CosmoStore.svg?style=flat)](https://www.nuget.org/packages/CosmoStore/) | @dzoukr |
| Azure Cosmos DB | `Newtonsoft.Json` | CosmoStore.CosmosDb | [![NuGet](https://img.shields.io/nuget/v/CosmoStore.CosmosDb.svg?style=flat)](https://www.nuget.org/packages/CosmoStore.CosmosDb/) |@dzoukr |
| Azure Table Storage | `Newtonsoft.Json` | CosmoStore.TableStorage | [![NuGet](https://img.shields.io/nuget/v/CosmoStore.TableStorage.svg?style=flat)](https://www.nuget.org/packages/CosmoStore.TableStorage/) | @dzoukr |
| InMemory | `Newtonsoft.Json` | CosmoStore.InMemory | [![NuGet](https://img.shields.io/nuget/v/CosmoStore.InMemory.svg?style=flat)](https://www.nuget.org/packages/CosmoStore.InMemory/) | @kunjee

## What is new in version 3

## Breaking changes from versions < 2
* CorrelationId is no longer required value (now `Guid option`)
* CausationId (optional) is part of `EventRead` & `EventWrite` record (#9)
* CosmoStore no longer contains any storage specific implementation (#10) - now you need to reference specific `CosmoStore.*` package.

All previous version of CosmoStore were tightly connected with `Newtonsoft.Json` library and used its `JToken` as default payload for events. Since version 3.0 this does not apply anymore.
Whole definition of `EventStore` was rewritten to be fully generic on payload and also on version level. Why? Some libraries not only use different payload than `JToken`, but possibly use
different type for `Version` then `int64` (default before version 3). Authors of libraries using `CosmoStore` API now can use any payload and any version type that fits best their storage mechanism.

## Event store

Event store (defined as F# record) is by design *storage agnostic* which means that no matter if you use Cosmos DB or Table Storage, the API is the same.

```fsharp
type EventStore = {
AppendEvent : string -> ExpectedPosition -> EventWrite -> Task<EventRead>
AppendEvents : string -> ExpectedPosition -> EventWrite list -> Task<EventRead list>
GetEvent : string -> int64 -> Task<EventRead>
GetEvents : string -> EventsReadRange -> Task<EventRead list>
GetEventsByCorrelationId : Guid -> Task<EventRead list>
GetStreams : StreamsReadFilter -> Task<Stream list>
GetStream : string -> Task<Stream>
EventAppended : IObservable<EventRead>
type EventStore<'payload,'version> = {
AppendEvent : StreamId -> ExpectedVersion<'version> -> EventWrite<'payload> -> Task<EventRead<'payload,'version>>
AppendEvents : StreamId -> ExpectedVersion<'version> -> EventWrite<'payload> list -> Task<EventRead<'payload,'version> list>
GetEvent : StreamId -> 'version -> Task<EventRead<'payload,'version>>
GetEvents : StreamId -> EventsReadRange<'version> -> Task<EventRead<'payload,'version> list>
GetEventsByCorrelationId : Guid -> Task<EventRead<'payload,'version> list>
GetStreams : StreamsReadFilter -> Task<Stream<'version> list>
GetStream : StreamId -> Task<Stream<'version>>
EventAppended : IObservable<EventRead<'payload,'version>>
}
```

Expand All @@ -54,21 +52,16 @@ Each function on record is explained in separate chapter.

## Initializing Event store for Azure Cosmos DB

Cosmos DB Event store has own configuration type that follows some specifics of database like *Request units* throughput or *collection capacity* (fixed, unlimited).
Cosmos DB Event store has own configuration type that follows some specifics of database like *Request units* throughput.

```fsharp
type Capacity =
| Fixed
| Unlimited
type Configuration = {
DatabaseName : string
ServiceEndpoint : Uri
AuthKey : string
Capacity : Capacity
ContainerName : string
ConnectionString : string
Throughput : int
InitializeContainer : bool
}
```

> Note: If you don't know these terms check [official documentation](https://docs.microsoft.com/en-us/azure/cosmos-db/request-units)
Expand Down Expand Up @@ -119,39 +112,37 @@ let eventStore = myConfig |> TableStorage.EventStore.getEventStore
Events are data structures you want to write (append) to some "shelf" also known as *Stream*. Event for writing is defined as this type:

```fsharp
type EventWrite = {
type EventWrite<'payload> = {
Id : Guid
CorrelationId : Guid option
CausationId : Guid option
Name : string
Data : JToken
Metadata : JToken option
Data : 'payload
Metadata : 'payload option
}
```

> Note: Newtonsoft.Json library (.NET industry standard) JToken is used as default type for data and metadata.
When writing Events to some Stream, you usually expect them to be written at some position hence you must specify *optimistic concurrency* strategy. For this purpose the type `ExpectedPosition` exists:
When writing Events to some Stream, you usually expect them to be written having some version hence you must specify *optimistic concurrency* strategy. For this purpose the type `ExpectedVersion` exists:

```fsharp
type ExpectedPosition =
| Any // we don't care
| NoStream // no event must exist in that stream
| Exact of int64 // exact position of next event
type ExpectedVersion<'version> =
| Any
| NoStream
| Exact of 'version
```

There are two defined functions to write Event to Stream. `AppendEvent` for writing single Event and `AppendEvents` to write more Events.

```fsharp
let expected = ExpectedPosition.NoStream // we are expecting brand new stream
let expected = ExpectedVersion.NoStream // we are expecting brand new stream
let eventToWrite = ... // get new event to be written
let streamId = "MyAmazingStream"
// writing first event
eventToWrite |> eventStore.AppendEvent streamId expected
let moreEventsToWrite = ... // get list of another events
let newExpected = ExpectedPosition.Exact 2L // we are expecting next event to be on 2nd position
let newExpected = ExpectedVersion.Exact 2L // we are expecting next event to be in 2nd version
// writing another N events
moreEventsToWrite |> eventStore.AppendEvents streamId newExpected
Expand All @@ -165,20 +156,20 @@ If everything goes well, you will get back list (in *Task*) of written events (t
When reading back Events from Stream, you'll a little bit more information than you wrote:

```fsharp
type EventRead = {
type EventRead<'payload,'version> = {
Id : Guid
CorrelationId : Guid option
CausationId : Guid option
StreamId : string
Position : int64
StreamId : StreamId
Version: 'version
Name : string
Data : JToken
Metadata : JToken option
Data : 'payload
Metadata : 'payload option
CreatedUtc : DateTime
}
```

You have two options how to read back stored Events. You can read single Event by Position using `GetEvent` function:
You have two options how to read back stored Events. You can read single Event by Version using `GetEvent` function:


```fsharp
Expand All @@ -190,7 +181,7 @@ Or read list of Events using `GetEvents` function. For such reading you need to

```fsharp
// return 1st-2nd Event from Stream
let firstTwoEvents = EventsReadRange.PositionRange(1,2) |> eventStore.GetEvents "MyAmazingStream"
let firstTwoEvents = EventsReadRange.VersionRange(1,2) |> eventStore.GetEvents "MyAmazingStream"
// return all events
let allEvents = EventsReadRange.AllEvents |> eventStore.GetEvents "MyAmazingStream"
Expand All @@ -199,11 +190,11 @@ let allEvents = EventsReadRange.AllEvents |> eventStore.GetEvents "MyAmazingStre
To fully understand what are the possibilities have a look at `EventsReadRange` definition:

```fsharp
type EventsReadRange =
type EventsReadRange<'version> =
| AllEvents
| FromPosition of int64
| ToPosition of int64
| PositionRange of fromPosition:int64 * toPosition:int64
| FromVersion of 'version
| ToVersion of 'version
| VersionRange of fromVersion:'version * toVersion:'version
```

If you are interested in Events based on stored `CorrelationId`, you can use function introduced in version 2 - `GetEventsByCorrelationId`
Expand All @@ -220,7 +211,7 @@ Each Stream has own metadata:
```fsharp
type Stream = {
Id : string
LastPosition : int64
LastVersion : int64
LastUpdatedUtc : DateTime
}
```
Expand Down
8 changes: 0 additions & 8 deletions build.proj

This file was deleted.

2 changes: 0 additions & 2 deletions fake.cmd

This file was deleted.

7 changes: 0 additions & 7 deletions fake.sh

This file was deleted.

30 changes: 20 additions & 10 deletions paket.dependencies
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
version 5.193.0

source https://api.nuget.org/v3/index.json
storage: none
nuget FSharp.Core = 4.3.1
nuget Newtonsoft.Json !~> 11 lowest_matching: true
nuget System.Reactive !~> 4 lowest_matching: true
version 5.216.0

group Build
source https://api.nuget.org/v3/index.json
Expand All @@ -16,15 +10,24 @@ group Build
nuget Fake.DotNet.Testing.NUnit
nuget Fake.Core.ReleaseNotes

group CosmoStore
source https://api.nuget.org/v3/index.json
storage: none
nuget FSharp.Core = 4.3.1
nuget System.Reactive !~> 4 lowest_matching: true

group CosmosDb
source https://api.nuget.org/v3/index.json
storage: none
nuget FSharp.Core = 4.3.1
nuget TaskBuilder.fs !~> 2 lowest_matching: true
nuget Microsoft.Azure.DocumentDB.Core !~> 2 lowest_matching: true
nuget Microsoft.Azure.Cosmos !~> 3 lowest_matching: true
nuget Newtonsoft.Json !~> 11 lowest_matching: true

group TableStorage
source https://api.nuget.org/v3/index.json
storage: none
nuget FSharp.Core = 4.3.1
nuget TaskBuilder.fs !~> 2 lowest_matching: true
nuget WindowsAzure.Storage !~> 9 lowest_matching: true

Expand All @@ -39,13 +42,20 @@ group Marten
group InMemory
source https://api.nuget.org/v3/index.json
storage: none
nuget FSharp.Core = 4.3.1
nuget TaskBuilder.fs
nuget FSharp.Core >= 4.3.4 < 4.5.4

nuget Newtonsoft.Json !~> 11 lowest_matching: true

group Tests
source https://api.nuget.org/v3/index.json
storage: none
nuget Expecto
nuget Expecto.VisualStudio.TestAdapter
nuget FSharp.Control.Reactive
nuget Newtonsoft.Json !~> 11 lowest_matching: true

group TestsV2
source https://api.nuget.org/v3/index.json
storage: none
nuget CosmoStore.CosmosDb = 2.1.0
nuget CosmoStore.TableStorage = 2.0.1
Loading

0 comments on commit f2c32e0

Please sign in to comment.