Skip to content

Commit 885ee55

Browse files
Version 0.2.0: delays, sequential execution of a list of activities, activities returning unit
1 parent 52b3f44 commit 885ee55

File tree

11 files changed

+123
-37
lines changed

11 files changed

+123
-37
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,21 @@ let workflow = orchestrator {
212212

213213
See [the full example](https://github.com/mikhailshilkov/DurableFunctions.FSharp/blob/master/samples/FanOutFanIn.fs).
214214

215+
Delays
216+
------
217+
218+
You can pause the orchestrator by calling `Orchestrator.delay` function:
219+
220+
``` fsharp
221+
let sendAndPause email = orchestrator {
222+
do! Activity.call sendNewsletter email
223+
do! Orchestrator.delay (TimeSpan.FromHours 1.0)
224+
}
225+
```
226+
227+
Note that the durable timer is used to implement this delay, so the orchestrator function will actually stop the current
228+
execution and will resume after the delay expires. See [Timers in Durable Functions](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-timers).
229+
215230
Contributions
216231
-------------
217232

samples/Delay.fs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
module samples.Delay
2+
3+
open System
4+
open Microsoft.Azure.WebJobs
5+
open DurableFunctions.FSharp
6+
7+
let sendNewsletter =
8+
let impl (email: string) =
9+
Console.WriteLine (sprintf "Fake newsletter sent to %s" email)
10+
true
11+
Activity.define "SendNewsletter" impl
12+
13+
let newsletter = orchestrator {
14+
15+
let pauseDuration = TimeSpan.FromHours 1.0
16+
17+
let sendAndPause email = orchestrator {
18+
let! response = Activity.call sendNewsletter email
19+
do! Orchestrator.delay pauseDuration
20+
return response
21+
}
22+
23+
let! responses =
24+
["joe@foo.com"; "alex@bar.com"; "john@buzz.com"]
25+
|> List.map sendAndPause
26+
|> Activity.seq
27+
28+
return responses |> List.forall id
29+
}
30+
31+
[<FunctionName("SendNewsletter")>]
32+
let SendNewsletter([<ActivityTrigger>] url) = Activity.run sendNewsletter url
33+
34+
[<FunctionName("NewsletterWorkflow")>]
35+
let NewsletterWorkflow ([<OrchestrationTrigger>] context: DurableOrchestrationContext) =
36+
Orchestrator.run (newsletter, context)

samples/FanOutFanIn.fs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1-
namespace samples
1+
module samples.FanInFanOut
22

33
open Microsoft.Azure.WebJobs
44
open DurableFunctions.FSharp
55

6-
module FanInFanOut =
7-
8-
let hardWork =
6+
let hardWork =
97
fun item -> async {
108
do! Async.Sleep 1000
119
return sprintf "Worked hard on %s!" item
1210
}
1311
|> Activity.defineAsync "HardWork"
1412

15-
let workflow = orchestrator {
13+
let workflow = orchestrator {
1614
let! items =
1715
["Tokyo"; "Seattle"; "London"]
1816
|> List.map (Activity.call hardWork)
@@ -22,9 +20,9 @@ module FanInFanOut =
2220
return String.concat ", " items
2321
}
2422

25-
[<FunctionName("HardWork")>]
26-
let HardWork([<ActivityTrigger>] name) = hardWork.run name
23+
[<FunctionName("HardWork")>]
24+
let HardWork([<ActivityTrigger>] name) = hardWork.run name
2725

28-
[<FunctionName("FanInFanOut")>]
29-
let Run ([<OrchestrationTrigger>] context: DurableOrchestrationContext) =
26+
[<FunctionName("FanInFanOut")>]
27+
let FanInFanOut ([<OrchestrationTrigger>] context: DurableOrchestrationContext) =
3028
Orchestrator.run (workflow, context)

samples/Hello.fs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
namespace samples
1+
module samples.HelloSequence
22

33
open Microsoft.Azure.WebJobs
44
open DurableFunctions.FSharp
55

6-
module HelloSequence =
7-
8-
[<FunctionName("SayHello")>]
9-
let SayHello([<ActivityTrigger>] name) =
6+
[<FunctionName("SayHello")>]
7+
let SayHello([<ActivityTrigger>] name) =
108
sprintf "Hello %s!" name
119

12-
[<FunctionName("HelloSequence")>]
13-
let Run ([<OrchestrationTrigger>] context: DurableOrchestrationContext) =
10+
[<FunctionName("HelloSequence")>]
11+
let Run ([<OrchestrationTrigger>] context: DurableOrchestrationContext) =
1412
context |>
1513
orchestrator {
1614
let! hello1 = Activity.callByName<string> "SayHello" "Tokyo"

samples/Parameters.fs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
namespace samples
1+
module samples.InputParameter
22

33
open Microsoft.Azure.WebJobs
44
open DurableFunctions.FSharp
55

66
open TypedSequence
77

8-
module InputParameter =
9-
10-
let workflow input = orchestrator {
8+
let workflow input = orchestrator {
119
let! hello1 = Activity.call sayHello (input + " Tokyo")
1210
let! hello2 = Activity.call sayHello (input + " Seattle")
1311
let! hello3 = Activity.call sayHello (input + " London")
1412

1513
// given "Bla" returns ["Hello Bla Tokyo!", "Hello Bla Seattle!", "Hello Bla London!"]
1614
return [hello1; hello2; hello3]
17-
}
15+
}
1816

19-
[<FunctionName("WorkflowWithInputParameter")>]
20-
let Run ([<OrchestrationTrigger>] context: DurableOrchestrationContext) =
17+
[<FunctionName("WorkflowWithInputParameter")>]
18+
let Run ([<OrchestrationTrigger>] context: DurableOrchestrationContext) =
2119
Orchestrator.run (workflow, context)

samples/Typed.fs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
1-
namespace samples
1+
module samples.TypedSequence
22

33
open Microsoft.Azure.WebJobs
44
open DurableFunctions.FSharp
55

6-
module TypedSequence =
7-
8-
let sayHello =
6+
let sayHello =
97
Activity.define "SayTyped" (sprintf "Hello typed %s!")
108

11-
let workflow = orchestrator {
9+
let workflow = orchestrator {
1210
let! hello1 = Activity.call sayHello "Tokyo"
1311
let! hello2 = Activity.call sayHello "Seattle"
1412
let! hello3 = Activity.call sayHello "London"
1513

1614
// returns ["Hello typed Tokyo!", "Hello typed Seattle!", "Hello typed London!"]
1715
return [hello1; hello2; hello3]
18-
}
16+
}
1917

20-
[<FunctionName("SayTyped")>]
21-
let SayHello([<ActivityTrigger>] name) = sayHello.run name
18+
[<FunctionName("SayTyped")>]
19+
let SayHello([<ActivityTrigger>] name) = sayHello.run name
2220

23-
[<FunctionName("TypedSequence")>]
24-
let Run ([<OrchestrationTrigger>] context: DurableOrchestrationContext) =
21+
[<FunctionName("TypedSequence")>]
22+
let Run ([<OrchestrationTrigger>] context: DurableOrchestrationContext) =
2523
Orchestrator.run (workflow, context)

samples/samples.fsproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@
1010
<Compile Include="Typed.fs" />
1111
<Compile Include="Parameters.fs" />
1212
<Compile Include="FanOutFanIn.fs" />
13+
<Compile Include="Delay.fs" />
1314
<Compile Include="HttpStart.fs" />
1415
</ItemGroup>
1516

1617
<ItemGroup>
1718
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.24" />
18-
<PackageReference Include="DurableFunctions.FSharp" Version="0.1.0" />
19+
<PackageReference Include="DurableFunctions.FSharp" Version="0.2.0" />
1920
<PackageReference Include="TaskBuilder.fs" Version="1.0.0" />
2021
</ItemGroup>
2122

src/DurableFunctions.FSharp/Activity.fs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,17 @@ module Activity =
4141
let all (tasks: OrchestratorBuilder.ContextTask<'a> seq) (c: DurableOrchestrationContext) =
4242
let bla = tasks |> Seq.map (fun x -> x c)
4343
let whenAll = Task.WhenAll bla
44-
whenAll.ContinueWith(fun (xs: Task<'a []>) -> xs.Result |> List.ofArray)
44+
whenAll.ContinueWith(fun (xs: Task<'a []>) -> xs.Result |> List.ofArray)
45+
46+
/// Call all specified tasks sequentially one after the other and combine the results together.
47+
let seq (tasks: OrchestratorBuilder.ContextTask<'a> list) =
48+
let rec work acc (rem : OrchestratorBuilder.ContextTask<'a> list) =
49+
match rem with
50+
| [] -> fun _ -> Task.FromResult acc
51+
| d :: rest -> orchestrator {
52+
let! t = d
53+
return! work (acc @ [t]) rest
54+
}
55+
work [] tasks
56+
57+

src/DurableFunctions.FSharp/DurableFunctions.FSharp.fsproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
<!-- NuGet Publishing Metadata -->
1515
<PropertyGroup>
16-
<Title>Azure Functions F# API</Title>
16+
<Title>Azure Durable Functions F# API</Title>
1717
<Authors>Mikhail Shilkov</Authors>
1818
<Description>F#-friendly API layer for Azure Durable Functions</Description>
1919
<PackageReleaseNotes>https://github.com/mikhailshilkov/DurableFunctions.FSharp/releases/</PackageReleaseNotes>
@@ -24,6 +24,6 @@
2424
<RepositoryUrl>https://github.com/mikhailshilkov/DurableFunctions.FSharp</RepositoryUrl>
2525
<RepositoryType>git</RepositoryType>
2626
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
27-
<Version>0.1.0</Version>
27+
<Version>0.2.0</Version>
2828
</PropertyGroup>
2929
</Project>

src/DurableFunctions.FSharp/Orchestrator.fs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
namespace DurableFunctions.FSharp
22

3+
open System
4+
open System.Threading
35
open System.Threading.Tasks
46
open Microsoft.Azure.WebJobs
57
open OrchestratorBuilder
@@ -16,4 +18,14 @@ type Orchestrator = class
1618
static member run (workflow : 'a -> ContextTask<'b>, context : DurableOrchestrationContext) : Task<'b> =
1719
let input = context.GetInput<'a> ()
1820
workflow input context
21+
22+
/// Returns a fixed value as a orchestrator.
23+
static member ret value (_: DurableOrchestrationContext) =
24+
Task.FromResult value
25+
26+
/// Delays orchestrator execution by the specified timespan.
27+
static member delay (timespan: TimeSpan) (context: DurableOrchestrationContext) =
28+
let deadline = context.CurrentUtcDateTime.Add timespan
29+
context.CreateTimer(deadline, CancellationToken.None)
30+
1931
end

src/DurableFunctions.FSharp/OrchestratorCE.fs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ open System.Runtime.CompilerServices
66
open Microsoft.Azure.WebJobs
77

88
module OrchestratorBuilder =
9+
10+
type ContextTask = DurableOrchestrationContext -> Task
911
type ContextTask<'a> = DurableOrchestrationContext -> Task<'a>
1012

1113
/// Represents the state of a computation:
@@ -100,6 +102,13 @@ module OrchestratorBuilder =
100102
else // Await and continue later when a result is available.
101103
Await (awt, (fun () -> continuation(awt.GetResult())))
102104

105+
let inline bindTaskUnit (task : Task) (continuation : unit -> Step<'b>) =
106+
let awt = task.GetAwaiter()
107+
if awt.IsCompleted then // Proceed to the next step based on the result we already have.
108+
continuation(awt.GetResult())
109+
else // Await and continue later when a result is available.
110+
Await (awt, (fun () -> continuation(awt.GetResult())))
111+
103112
/// Chains together a step with its following step.
104113
/// Note that this requires that the first step has no result.
105114
/// This prevents constructs like `task { return 1; return 2; }`.
@@ -133,6 +142,12 @@ module OrchestratorBuilder =
133142
let a = bindTask (task c) continuation
134143
run (fun () -> a) c)
135144

145+
let inline bindContextTaskUnit (task : ContextTask) (continuation : unit -> Step<'b>) =
146+
ReturnFrom(
147+
fun c ->
148+
let a = bindTaskUnit (task c) continuation
149+
run (fun () -> a) c)
150+
136151
/// Builds a `System.Threading.Tasks.Task<'a>` similarly to a C# async/await method, but with
137152
/// all awaited tasks automatically configured *not* to resume on the captured context.
138153
/// This is often preferable when writing library code that is not context-aware, but undesirable when writing
@@ -152,6 +167,8 @@ module OrchestratorBuilder =
152167
// Everything else can use bindGenericAwaitable via an extension member (defined later).
153168
member inline __.Bind(task : ContextTask<'a>, continuation : 'a -> 'b Step) : 'b Step =
154169
bindContextTask task continuation
170+
member inline __.Bind(task : ContextTask, continuation : unit -> 'b Step) : 'b Step =
171+
bindContextTaskUnit task continuation
155172

156173
[<AutoOpen>]
157174
module Builders =

0 commit comments

Comments
 (0)