forked from jet/equinox
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTodo.fsx
More file actions
192 lines (167 loc) · 7.47 KB
/
Todo.fsx
File metadata and controls
192 lines (167 loc) · 7.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#if LOCAL
// Compile Tutorial.fsproj by either a) right-clicking or b) typing
// dotnet build samples/Tutorial before attempting to send this to FSI with Alt-Enter
#if VISUALSTUDIO
#r "netstandard"
#endif
#I "bin/Debug/net6.0/"
#r "System.Runtime.Caching.dll"
#r "Serilog.dll"
#r "Serilog.Sinks.Console.dll"
#r "Newtonsoft.Json.dll"
#r "TypeShape.dll"
#r "Equinox.Core.dll"
#r "Equinox.dll"
#r "FSharp.UMX.dll"
#r "FsCodec.dll"
#r "FsCodec.SystemTextJson.dll"
#r "FSharp.Control.TaskSeq.dll"
#r "Microsoft.Azure.Cosmos.Client.dll"
#r "Equinox.CosmosStore.dll"
#else
#r "nuget: Equinox.CosmosStore, *-*"
#r "nuget: FsCodec.SystemTextJson, *-*"
#r "nuget: Serilog.Sinks.Console"
#r "nuget: Serilog.Sinks.Seq"
#endif
open System
(* NB It's recommended to look at Favorites.fsx first as it establishes the groundwork
This tutorial stresses different aspects *)
let Category = "Todos"
let streamId = Equinox.StreamId.gen id
type Todo = { id: int; order: int; title: string; completed: bool }
type DeletedInfo = { id: int }
type Snapshotted = { items: Todo[] }
type Event =
| Added of Todo
| Updated of Todo
| Deleted of DeletedInfo
| Cleared
| Snapshotted of Snapshotted
interface TypeShape.UnionContract.IUnionContract
let codec = FsCodec.SystemTextJson.CodecJsonElement.Create<Event>()
type State = { items : Todo list; nextId : int }
let initial = { items = []; nextId = 0 }
let evolve s (e : Event) =
match e with
| Added item -> { s with items = item :: s.items; nextId = s.nextId + 1 }
| Updated value -> { s with items = s.items |> List.map (function { id = id } when id = value.id -> value | item -> item) }
| Deleted { id=id } -> { s with items = s.items |> List.filter (fun x -> x.id <> id) }
| Cleared -> { s with items = [] }
| Snapshotted { items=items } -> { s with items = List.ofArray items }
let fold : State -> Event seq -> State = Seq.fold evolve
let isOrigin = function Cleared | Snapshotted _ -> true | _ -> false
let snapshot state = Snapshotted { items = Array.ofList state.items }
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 }]
| 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=id }] else []
| Clear -> if state.items |> List.isEmpty then [] else [Cleared]
type Service internal (resolve : string -> Equinox.Decider<Event, State>) =
let execute clientId command : Async<unit> =
let decider = resolve clientId
decider.Transact(interpret command)
let handle clientId command : Async<Todo list> =
let decider = resolve clientId
decider.Transact(fun state ->
let events = interpret command state
let state' = fold state events
state'.items, events)
let query clientId (projection : State -> 't) : Async<'t> =
let decider = resolve clientId
decider.Query projection
member _.List clientId : Async<Todo seq> =
query clientId (fun s -> s.items |> Seq.ofList)
member _.TryGet(clientId, id) =
query clientId (fun x -> x.items |> List.tryFind (fun x -> x.id = id))
member _.Execute(clientId, command) : Async<unit> =
execute clientId command
member _.Create(clientId, template: Todo) : Async<Todo> = async {
let! state' = handle clientId (Add template)
return List.head state' }
member _.Patch(clientId, item: Todo) : Async<Todo> = async {
let! state' = handle clientId (Update item)
return List.find (fun x -> x.id = item.id) state' }
member _.Clear clientId : Async<unit> =
execute clientId Clear
(*
* EXERCISE THE SERVICE
*)
let initialState = initial
//val initialState : State = {items = [];
// nextId = 0;}
let oneItem = fold initialState [Added { id = 0; order = 0; title = "Feed cat"; completed = false }]
//val oneItem : State = {items = [{id = 0;
// order = 0;
// title = "Feed cat";
// completed = false;}];
// nextId = 1;}
fold oneItem [Cleared]
//val it : State = {items = [];
// nextId = 1;}
open Serilog
let log = LoggerConfiguration().WriteTo.Console().CreateLogger()
let [<Literal>] appName = "equinox-tutorial"
let cache = Equinox.Cache(appName, 20)
open Equinox.CosmosStore
module Store =
let read key = Environment.GetEnvironmentVariable key |> Option.ofObj |> Option.get
let discovery = Discovery.ConnectionString (read "EQUINOX_COSMOS_CONNECTION")
let connector = CosmosStoreConnector(discovery, TimeSpan.FromSeconds 5., 2, TimeSpan.FromSeconds 5.)
let storeClient = CosmosStoreClient.Connect(connector.CreateAndInitialize, read "EQUINOX_COSMOS_DATABASE", read "EQUINOX_COSMOS_CONTAINER") |> Async.RunSynchronously
let context = CosmosStoreContext(storeClient, tipMaxEvents = 100) // Keep up to 100 events in tip before moving events to a new document
let cacheStrategy = CachingStrategy.SlidingWindow (cache, TimeSpan.FromMinutes 20.)
module TodosCategory =
let access = AccessStrategy.Snapshot (isOrigin,snapshot)
let resolve = CosmosStoreCategory(Store.context, codec, fold, initial, Store.cacheStrategy, access=access)
|> Equinox.Decider.resolve log
let service = Service(streamId >> TodosCategory.resolve Category)
let client = "ClientJ"
let item = { id = 0; order = 0; title = "Feed cat"; completed = false }
service.Create(client, item) |> Async.RunSynchronously
//val it : Todo = {id = 0;
// order = 0;
// title = "Feed cat";
// completed = false;}
service.List(client) |> Async.RunSynchronously
//val it : seq<Todo> = [{id = 0;
// order = 0;
// title = "Feed cat";
// completed = false;}]
service.Execute(client, Clear) |> Async.RunSynchronously
//val it : unit = ()
service.TryGet(client,42) |> Async.RunSynchronously
//val it : Todo option = None
let item2 = { id = 3; order = 0; title = "Feed dog"; completed = false }
service.Create(client, item2) |> Async.RunSynchronously
service.TryGet(client, 3) |> Async.RunSynchronously
//val it : Todo option = Some {id = 3;
// order = 0;
// title = "Feed dog";
// completed = false;}
let itemH = { id = 1; order = 0; title = "Feed horse"; completed = false }
service.Patch(client, itemH) |> Async.RunSynchronously
//[05:49:33 INF] EqxCosmos Tip 304 116ms rc=1
//Updated {id = 1;
// order = 0;
// title = "Feed horse";
// completed = false;}Updated {id = 1;
// order = 0;
// title = "Feed horse";
// completed = false;}[05:49:33 INF] EqxCosmos Sync 1+1 534ms rc=14.1
//val it : Todo = {id = 1;
// order = 0;
// title = "Feed horse";
// completed = false;}
service.Execute(client, Delete 1) |> Async.RunSynchronously
//[05:47:18 INF] EqxCosmos Tip 304 224ms rc=1
//Deleted 1[05:47:19 INF] EqxCosmos Sync 1+1 230ms rc=13.91
//val it : unit = ()
service.List(client) |> Async.RunSynchronously
//[05:47:22 INF] EqxCosmos Tip 304 119ms rc=1
//val it : seq<Todo> = []