-
Notifications
You must be signed in to change notification settings - Fork 70
/
Copy pathTodo.fsx
184 lines (157 loc) · 7.05 KB
/
Todo.fsx
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
#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
#I "bin/Debug/net6.0/"
#r "System.Runtime.Caching.dll"
#r "Serilog.dll"
#r "Serilog.Sinks.Console.dll"
#r "TypeShape.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 [<Literal>] private CategoryName = "Todos"
let private streamId = FsCodec.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 }
module Snapshot =
let private generate state = Snapshotted { items = Array.ofList state.items }
let private isOrigin = function Cleared | Snapshotted _ -> true | _ -> false
let config = isOrigin, generate
let private evolve s = function
| Added item -> { 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 = Array.fold evolve
module Decisions =
let add value (state: State) = [|
Added { value with id = state.nextId } |]
let update (value: Todo) (state: State) = [|
match state.items |> List.tryFind (function { id = id } -> id = value.id) with
| Some current when current <> value -> Updated value
| _ -> () |]
let delete id (state: State) = [|
if state.items |> List.exists (fun x -> x.id = id) then
Deleted { id = id } |]
let clear (state: State) = [|
if state.items |> List.isEmpty |> not then
Cleared |]
type Service internal (resolve: string -> Equinox.Decider<Event, State>) =
member _.List(clientId): Async<Todo seq> =
let decider = resolve clientId
decider.Query(fun s -> s.items |> Seq.ofList)
member _.TryGet(clientId, id) =
let decider = resolve clientId
decider.Query(fun s -> s.items |> List.tryFind (fun x -> x.id = id))
member _.Create(clientId, template: Todo): Async<Todo> =
let decider = resolve clientId
decider.Transact(Decisions.add template, fun s -> s.items |> List.head)
member _.Patch(clientId, item: Todo): Async<Todo> =
let decider = resolve clientId
decider.Transact(Decisions.update item, fun s -> s.items |> List.find (fun x -> x.id = item.id))
member _.Delete(clientId, id): Async<unit> =
let decider = resolve clientId
decider.Transact(Decisions.delete id)
member _.Clear(clientId): Async<unit> =
let decider = resolve clientId
decider.Transact(Decisions.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, 2, TimeSpan.FromSeconds 5L)
let databaseId, containerId = read "EQUINOX_COSMOS_DATABASE", read "EQUINOX_COSMOS_CONTAINER"
let client = connector.Connect(databaseId, [| containerId |]) |> Async.RunSynchronously
let context = CosmosStoreContext(client, databaseId, containerId, tipMaxEvents = 100)
let cacheStrategy = Equinox.CachingStrategy.SlidingWindow (cache, TimeSpan.FromMinutes 20.)
let access = AccessStrategy.Snapshot Snapshot.config
let category = CosmosStoreCategory(context, CategoryName, codec, fold, initial, access, cacheStrategy)
let service = Service(streamId >> Equinox.Decider.forStream log Store.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.Clear(client) |> 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.Delete(client, 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> = []