diff --git a/all.sln b/all.sln index d5db7a9e9..6339c47b0 100644 --- a/all.sln +++ b/all.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29318.209 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32929.385 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.Actors", "src\Dapr.Actors\Dapr.Actors.csproj", "{C2DB4B64-B7C3-4FED-8753-C040F677C69A}" EndProject @@ -35,57 +35,63 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Actors.AspNetCore.Test", "test\Dapr.Actors.AspNetCore.Test\Dapr.Actors.AspNetCore.Test.csproj", "{9C1D6ABA-5EDE-4FA0-A8A9-0AB98CB74737}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.Actors.AspNetCore.Test", "test\Dapr.Actors.AspNetCore.Test\Dapr.Actors.AspNetCore.Test.csproj", "{9C1D6ABA-5EDE-4FA0-A8A9-0AB98CB74737}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Actors.AspNetCore.IntegrationTest", "test\Dapr.Actors.AspNetCore.IntegrationTest\Dapr.Actors.AspNetCore.IntegrationTest.csproj", "{95BAF30B-8089-42CE-8530-6DFBCE1F6A07}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.Actors.AspNetCore.IntegrationTest", "test\Dapr.Actors.AspNetCore.IntegrationTest\Dapr.Actors.AspNetCore.IntegrationTest.csproj", "{95BAF30B-8089-42CE-8530-6DFBCE1F6A07}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Actors.AspNetCore.IntegrationTest.App", "test\Dapr.Actors.AspNetCore.IntegrationTest.App\Dapr.Actors.AspNetCore.IntegrationTest.App.csproj", "{1BA7E772-8AA7-4D5A-800D-66B17F62421C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.Actors.AspNetCore.IntegrationTest.App", "test\Dapr.Actors.AspNetCore.IntegrationTest.App\Dapr.Actors.AspNetCore.IntegrationTest.App.csproj", "{1BA7E772-8AA7-4D5A-800D-66B17F62421C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Extensions.Configuration.Test", "test\Dapr.Extensions.Configuration.Test\Dapr.Extensions.Configuration.Test.csproj", "{78FC19B2-396C-4ED2-BFD9-6C5667C61666}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.Extensions.Configuration.Test", "test\Dapr.Extensions.Configuration.Test\Dapr.Extensions.Configuration.Test.csproj", "{78FC19B2-396C-4ED2-BFD9-6C5667C61666}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Extensions.Configuration", "src\Dapr.Extensions.Configuration\Dapr.Extensions.Configuration.csproj", "{B615B353-476C-43B9-A776-B193B0DBD256}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.Extensions.Configuration", "src\Dapr.Extensions.Configuration\Dapr.Extensions.Configuration.csproj", "{B615B353-476C-43B9-A776-B193B0DBD256}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{D687DDC4-66C5-4667-9E3A-FD8B78ECAA78}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspNetCore", "AspNetCore", "{A11DC259-D1DB-4686-AD28-A427D0BABA83}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrpcServiceSample", "examples\AspNetCore\GrpcServiceSample\GrpcServiceSample.csproj", "{2EC50C79-782D-4985-ABB1-AD07F35D1621}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GrpcServiceSample", "examples\AspNetCore\GrpcServiceSample\GrpcServiceSample.csproj", "{2EC50C79-782D-4985-ABB1-AD07F35D1621}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RoutingSample", "examples\AspNetCore\RoutingSample\RoutingSample.csproj", "{15A16323-2CCA-472E-BE79-07259DAD5F6F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RoutingSample", "examples\AspNetCore\RoutingSample\RoutingSample.csproj", "{15A16323-2CCA-472E-BE79-07259DAD5F6F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecretStoreConfigurationProviderSample", "examples\AspNetCore\SecretStoreConfigurationProviderSample\SecretStoreConfigurationProviderSample.csproj", "{5BACBA51-03FE-4CE1-B0F5-9E9C2A132FAB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecretStoreConfigurationProviderSample", "examples\AspNetCore\SecretStoreConfigurationProviderSample\SecretStoreConfigurationProviderSample.csproj", "{5BACBA51-03FE-4CE1-B0F5-9E9C2A132FAB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControllerSample", "examples\AspNetCore\ControllerSample\ControllerSample.csproj", "{3160CC92-1D6E-42CB-AE89-9401C8CEC5CB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControllerSample", "examples\AspNetCore\ControllerSample\ControllerSample.csproj", "{3160CC92-1D6E-42CB-AE89-9401C8CEC5CB}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Actor", "Actor", "{02374BD0-BF0B-40F8-A04A-C4C4D61D4992}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IDemoActor", "examples\Actor\IDemoActor\IDemoActor.csproj", "{7957E852-1291-4FAA-9034-FB66CE817FF1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IDemoActor", "examples\Actor\IDemoActor\IDemoActor.csproj", "{7957E852-1291-4FAA-9034-FB66CE817FF1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoActor", "examples\Actor\DemoActor\DemoActor.csproj", "{626D74DD-4F37-4F74-87A3-5A6888684F5E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoActor", "examples\Actor\DemoActor\DemoActor.csproj", "{626D74DD-4F37-4F74-87A3-5A6888684F5E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActorClient", "examples\Actor\ActorClient\ActorClient.csproj", "{CC0A5C98-ACDE-4139-BA2F-2995A9B8E18C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ActorClient", "examples\Actor\ActorClient\ActorClient.csproj", "{CC0A5C98-ACDE-4139-BA2F-2995A9B8E18C}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{A7F41094-8648-446B-AECD-DCC2CC871F73}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StateManagement", "examples\Client\StateManagement\StateManagement.csproj", "{F70AC78E-8925-4770-832A-2FC67A620EB2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StateManagement", "examples\Client\StateManagement\StateManagement.csproj", "{F70AC78E-8925-4770-832A-2FC67A620EB2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceInvocation", "examples\Client\ServiceInvocation\ServiceInvocation.csproj", "{8B570E70-0E73-4042-A4B6-1CC3CC782A65}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceInvocation", "examples\Client\ServiceInvocation\ServiceInvocation.csproj", "{8B570E70-0E73-4042-A4B6-1CC3CC782A65}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PublishSubscribe", "examples\Client\PublishSubscribe\PublishSubscribe.csproj", "{DE6913E3-E5D9-4D1D-95F9-9FED87BD09BC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PublishSubscribe", "examples\Client\PublishSubscribe\PublishSubscribe.csproj", "{DE6913E3-E5D9-4D1D-95F9-9FED87BD09BC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.E2E.Test", "test\Dapr.E2E.Test\Dapr.E2E.Test.csproj", "{4AA9E7B7-36BF-4AAE-BFA3-C9CE8740F4A0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.E2E.Test", "test\Dapr.E2E.Test\Dapr.E2E.Test.csproj", "{4AA9E7B7-36BF-4AAE-BFA3-C9CE8740F4A0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.E2E.Test.App", "test\Dapr.E2E.Test.App\Dapr.E2E.Test.App.csproj", "{345FC3FB-D1E9-4AE8-9052-17D20AB01FA2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.E2E.Test.App", "test\Dapr.E2E.Test.App\Dapr.E2E.Test.App.csproj", "{345FC3FB-D1E9-4AE8-9052-17D20AB01FA2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.E2E.Test.Actors", "test\Dapr.E2E.Test.Actors\Dapr.E2E.Test.Actors.csproj", "{2AED1542-A8ED-488D-B6D0-E16AB5D6EF6C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.E2E.Test.Actors", "test\Dapr.E2E.Test.Actors\Dapr.E2E.Test.Actors.csproj", "{2AED1542-A8ED-488D-B6D0-E16AB5D6EF6C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.E2E.Test.App.Grpc", "test\Dapr.E2E.Test.App.Grpc\Dapr.E2E.Test.App.Grpc.csproj", "{E8212911-344B-4638-ADC3-B215BCDCAFD1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.E2E.Test.App.Grpc", "test\Dapr.E2E.Test.App.Grpc\Dapr.E2E.Test.App.Grpc.csproj", "{E8212911-344B-4638-ADC3-B215BCDCAFD1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfigurationApi", "examples\Client\ConfigurationApi\ConfigurationApi.csproj", "{F80F837E-D2FC-4FFC-B68F-3CF0EC015F66}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigurationApi", "examples\Client\ConfigurationApi\ConfigurationApi.csproj", "{F80F837E-D2FC-4FFC-B68F-3CF0EC015F66}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.E2E.Test.App.ReentrantActors", "test\Dapr.E2E.Test.App.ReentrantActor\Dapr.E2E.Test.App.ReentrantActors.csproj", "{5BE7F505-7D77-4C3A-ABFD-54088774DAA7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.E2E.Test.App.ReentrantActors", "test\Dapr.E2E.Test.App.ReentrantActor\Dapr.E2E.Test.App.ReentrantActors.csproj", "{5BE7F505-7D77-4C3A-ABFD-54088774DAA7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DistributedLock", "examples\Client\DistributedLock\DistributedLock.csproj", "{35031EDB-C0DE-453A-8335-D2EBEA2FC640}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DistributedLock", "examples\Client\DistributedLock\DistributedLock.csproj", "{35031EDB-C0DE-453A-8335-D2EBEA2FC640}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.Workflow", "src\Dapr.Workflow\Dapr.Workflow.csproj", "{07578B6C-9B96-4B3D-BA2E-7800EFCA7F99}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workflow", "Workflow", "{BF3ED6BF-ADF3-4D25-8E89-02FB8D945CA9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowWebApp", "examples\Workflow\WorkflowWebApp\WorkflowWebApp.csproj", "{5C61ABED-7623-4C28-A5C9-C5972A0F669C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -217,6 +223,14 @@ Global {35031EDB-C0DE-453A-8335-D2EBEA2FC640}.Debug|Any CPU.Build.0 = Debug|Any CPU {35031EDB-C0DE-453A-8335-D2EBEA2FC640}.Release|Any CPU.ActiveCfg = Release|Any CPU {35031EDB-C0DE-453A-8335-D2EBEA2FC640}.Release|Any CPU.Build.0 = Release|Any CPU + {07578B6C-9B96-4B3D-BA2E-7800EFCA7F99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07578B6C-9B96-4B3D-BA2E-7800EFCA7F99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07578B6C-9B96-4B3D-BA2E-7800EFCA7F99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07578B6C-9B96-4B3D-BA2E-7800EFCA7F99}.Release|Any CPU.Build.0 = Release|Any CPU + {5C61ABED-7623-4C28-A5C9-C5972A0F669C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C61ABED-7623-4C28-A5C9-C5972A0F669C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C61ABED-7623-4C28-A5C9-C5972A0F669C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C61ABED-7623-4C28-A5C9-C5972A0F669C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -256,6 +270,9 @@ Global {F80F837E-D2FC-4FFC-B68F-3CF0EC015F66} = {A7F41094-8648-446B-AECD-DCC2CC871F73} {5BE7F505-7D77-4C3A-ABFD-54088774DAA7} = {DD020B34-460F-455F-8D17-CF4A949F100B} {35031EDB-C0DE-453A-8335-D2EBEA2FC640} = {A7F41094-8648-446B-AECD-DCC2CC871F73} + {07578B6C-9B96-4B3D-BA2E-7800EFCA7F99} = {27C5D71D-0721-4221-9286-B94AB07B58CF} + {BF3ED6BF-ADF3-4D25-8E89-02FB8D945CA9} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78} + {5C61ABED-7623-4C28-A5C9-C5972A0F669C} = {BF3ED6BF-ADF3-4D25-8E89-02FB8D945CA9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40} diff --git a/examples/Workflow/WorkflowWebApp/Program.cs b/examples/Workflow/WorkflowWebApp/Program.cs new file mode 100644 index 000000000..838141baf --- /dev/null +++ b/examples/Workflow/WorkflowWebApp/Program.cs @@ -0,0 +1,51 @@ +using Dapr.Workflow; + +// The workflow host is a background service that connects to the sidecar over gRPC +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + +// Dapr workflows are registered as part of the service configuration +builder.Services.AddDaprWorkflow(options => +{ + // Example of registering a "PlaceOrder" workflow function + options.RegisterWorkflow("PlaceOrder", implementation: async (context, input) => + { + // In real life there are other steps related to placing an order, like reserving + // inventory and charging the customer credit card etc. But let's keep it simple ;) + return await context.CallActivityAsync("ShipProduct", "Coffee Beans"); + }); + + // Example of registering a "ShipProduct" workflow activity function + options.RegisterActivity("ShipProduct", implementation: (context, input) => + { + return Task.FromResult($"We are shipping {input} to the customer using our hoard of drones!"); + }); +}); + +WebApplication app = builder.Build(); + +// POST starts new workflow instances +app.MapPost("/order", async (HttpContext context, WorkflowClient client) => +{ + string id = Guid.NewGuid().ToString()[..8]; + await client.ScheduleNewWorkflowAsync("PlaceOrder", id); + + // return an HTTP 202 and a Location header to be used for status query + return Results.AcceptedAtRoute("GetOrderEndpoint", new { id }); +}); + +// GET fetches metadata for specific order workflow instances +app.MapGet("/order/{id}", async (string id, WorkflowClient client) => +{ + WorkflowMetadata metadata = await client.GetWorkflowMetadataAsync(id, getInputsAndOutputs: true); + if (metadata.Exists) + { + return Results.Ok(metadata); + } + else + { + return Results.NotFound($"No workflow created for order with ID = '{id}' was found."); + } +}).WithName("GetOrderEndpoint"); + +app.Run(); + diff --git a/examples/Workflow/WorkflowWebApp/Properties/launchSettings.json b/examples/Workflow/WorkflowWebApp/Properties/launchSettings.json new file mode 100644 index 000000000..8254ce0fe --- /dev/null +++ b/examples/Workflow/WorkflowWebApp/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "OrderingWebApi": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:10080" + } + } +} \ No newline at end of file diff --git a/examples/Workflow/WorkflowWebApp/WorkflowWebApp.csproj b/examples/Workflow/WorkflowWebApp/WorkflowWebApp.csproj new file mode 100644 index 000000000..28e9dadb9 --- /dev/null +++ b/examples/Workflow/WorkflowWebApp/WorkflowWebApp.csproj @@ -0,0 +1,15 @@ + + + + + + + + Exe + net6 + enable + latest + + + + \ No newline at end of file diff --git a/examples/Workflow/WorkflowWebApp/demo.http b/examples/Workflow/WorkflowWebApp/demo.http new file mode 100644 index 000000000..00210e840 --- /dev/null +++ b/examples/Workflow/WorkflowWebApp/demo.http @@ -0,0 +1,6 @@ +### Create new order +POST http://localhost:8080/workflow +Content-Type: application/json + +### Query placeholder +GET http://localhost:8080/workflow/XXX \ No newline at end of file diff --git a/src/Dapr.Workflow/Dapr.Workflow.csproj b/src/Dapr.Workflow/Dapr.Workflow.csproj new file mode 100644 index 000000000..28e4e2513 --- /dev/null +++ b/src/Dapr.Workflow/Dapr.Workflow.csproj @@ -0,0 +1,24 @@ + + + + + netcoreapp3.1;net5;net6 + enable + Dapr.Workflow + Dapr Workflow Authoring SDK + Dapr Workflow SDK for building workflows as code with Dapr + 0.1.0 + alpha + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Dapr.Workflow/WorkflowActivityContext.cs b/src/Dapr.Workflow/WorkflowActivityContext.cs new file mode 100644 index 000000000..267ba4cd9 --- /dev/null +++ b/src/Dapr.Workflow/WorkflowActivityContext.cs @@ -0,0 +1,43 @@ +// ------------------------------------------------------------------------ +// Copyright 2022 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Workflow +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.DurableTask; + + /// + /// Defines properties and methods for task activity context objects. + /// + public class WorkflowActivityContext + { + readonly TaskActivityContext innerContext; + + internal WorkflowActivityContext(TaskActivityContext innerContext) + { + this.innerContext = innerContext ?? throw new ArgumentNullException(nameof(innerContext)); + } + + /// + /// Gets the name of the activity. + /// + public TaskName Name => this.innerContext.Name; + + /// + /// Gets the unique ID of the current workflow instance. + /// + public string InstanceId => this.innerContext.InstanceId; + } +} diff --git a/src/Dapr.Workflow/WorkflowClient.cs b/src/Dapr.Workflow/WorkflowClient.cs new file mode 100644 index 000000000..597768fa4 --- /dev/null +++ b/src/Dapr.Workflow/WorkflowClient.cs @@ -0,0 +1,87 @@ +// ------------------------------------------------------------------------ +// Copyright 2022 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System; +using System.Threading.Tasks; +using Microsoft.DurableTask; +using Microsoft.DurableTask.Client; + +namespace Dapr.Workflow +{ + // TODO: This will be replaced by the official Dapr Workflow management client. + /// + /// Defines client operations for managing Dapr Workflow instances. + /// + public sealed class WorkflowClient : IAsyncDisposable + { + readonly DurableTaskClient innerClient; + + /// + /// Initializes a new instance of the class. + /// + /// The Durable Task client used to communicate with the Dapr sidecar. + /// Thrown if is null. + public WorkflowClient(DurableTaskClient innerClient) + { + this.innerClient = innerClient ?? throw new ArgumentNullException(nameof(innerClient)); + } + + /// + /// Schedules a new workflow instance for execution. + /// + /// The name of the orchestrator to schedule. + /// + /// The unique ID of the orchestration instance to schedule. If not specified, a new GUID value is used. + /// + /// + /// The time when the orchestration instance should start executing. If not specified or if a date-time in the past + /// is specified, the orchestration instance will be scheduled immediately. + /// + /// + /// The optional input to pass to the scheduled orchestration instance. This must be a serializable value. + /// + public Task ScheduleNewWorkflowAsync( + string name, + string? instanceId = null, + object? input = null, + DateTime? startTime = null) + { + StartOrchestrationOptions options = new(instanceId, startTime); + return this.innerClient.ScheduleNewOrchestrationInstanceAsync(name, input, options); + } + + /// + /// Fetches runtime metadata for the specified workflow instance. + /// + /// The unique ID of the orchestration instance to fetch. + /// + /// Specify true to fetch the orchestration instance's inputs, outputs, and custom status, or false to + /// omit them. Defaults to false. + /// + public async Task GetWorkflowMetadataAsync(string instanceId, bool getInputsAndOutputs = false) + { + OrchestrationMetadata? metadata = await this.innerClient.GetInstanceMetadataAsync( + instanceId, + getInputsAndOutputs); + return new WorkflowMetadata(metadata); + } + + /// + /// Disposes any unmanaged resources associated with this client. + /// + public ValueTask DisposeAsync() + { + return ((IAsyncDisposable)this.innerClient).DisposeAsync(); + } + } +} diff --git a/src/Dapr.Workflow/WorkflowContext.cs b/src/Dapr.Workflow/WorkflowContext.cs new file mode 100644 index 000000000..9a9bd8a7e --- /dev/null +++ b/src/Dapr.Workflow/WorkflowContext.cs @@ -0,0 +1,90 @@ +// ------------------------------------------------------------------------ +// Copyright 2022 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Workflow +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.DurableTask; + + /// + /// Context object used by workflow implementations to perform actions such as scheduling activities, durable timers, waiting for + /// external events, and for getting basic information about the current workflow instance. + /// + public class WorkflowContext + { + readonly TaskOrchestrationContext innerContext; + + internal WorkflowContext(TaskOrchestrationContext innerContext) + { + this.innerContext = innerContext ?? throw new ArgumentNullException(nameof(innerContext)); + } + + /// + /// Gets the name of the current workflow. + /// + public TaskName Name => this.innerContext.Name; + + /// + /// Gets the instance ID of the current workflow. + /// + public string InstanceId => this.innerContext.InstanceId; + + /// + /// Gets the current workflow time in UTC. + /// + public DateTime CurrentUtcDateTime => this.innerContext.CurrentUtcDateTime; + + /// + /// Assigns a custom status value to the current workflow. + /// + public void SetCustomStatus(object? customStatus) => this.innerContext.SetCustomStatus(customStatus); + + /// + /// Creates a durable timer that expires after the specified delay. + /// + /// + public Task CreateTimer(TimeSpan delay, CancellationToken cancellationToken = default) + { + return this.innerContext.CreateTimer(delay, cancellationToken); + } + + /// + /// Waits for an event to be raised with name and returns the event data. + /// + /// + public Task WaitForExternalEventAsync(string eventName, TimeSpan timeout) + { + return this.innerContext.WaitForExternalEvent(eventName, timeout); + } + + /// + /// Waits for an event to be raised with name and returns the event data. + /// + /// + public Task WaitForExternalEventAsync(string eventName, CancellationToken cancellationToken = default) + { + return this.innerContext.WaitForExternalEvent(eventName, cancellationToken); + } + + /// + /// Asynchronously invokes an activity by name and with the specified input value. + /// + /// + public Task CallActivityAsync(TaskName name, object? input = null, TaskOptions? options = null) + { + return this.innerContext.CallActivityAsync(name, input, options); + } + } +} diff --git a/src/Dapr.Workflow/WorkflowMetadata.cs b/src/Dapr.Workflow/WorkflowMetadata.cs new file mode 100644 index 000000000..603b058c6 --- /dev/null +++ b/src/Dapr.Workflow/WorkflowMetadata.cs @@ -0,0 +1,44 @@ +// ------------------------------------------------------------------------ +// Copyright 2022 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Microsoft.DurableTask.Client; + +namespace Dapr.Workflow +{ + /// + /// Represents a snapshot of a workflow instance's current state, including metadata. + /// + public class WorkflowMetadata + { + internal WorkflowMetadata(OrchestrationMetadata? metadata) + { + this.Details = metadata; + } + + /// + /// Gets a value indicating whether the requested workflow instance exists. + /// + public bool Exists => this.Details != null; + + /// + /// Gets a value indicating whether the requested workflow is in a running state. + /// + public bool IsWorkflowRunning => this.Details?.RuntimeStatus == OrchestrationRuntimeStatus.Running; + + /// + /// Gets the detailed metadata for the requested workflow instance. + /// This value will be null when is false. + /// + public OrchestrationMetadata? Details { get; } + } +} diff --git a/src/Dapr.Workflow/WorkflowRuntimeOptions.cs b/src/Dapr.Workflow/WorkflowRuntimeOptions.cs new file mode 100644 index 000000000..0bc602311 --- /dev/null +++ b/src/Dapr.Workflow/WorkflowRuntimeOptions.cs @@ -0,0 +1,80 @@ +// ------------------------------------------------------------------------ +// Copyright 2022 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Workflow +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Microsoft.DurableTask; + + /// + /// Defines runtime options for workflows. + /// + public sealed class WorkflowRuntimeOptions + { + /// + /// Dictionary to name and register a workflow. + /// + readonly Dictionary> factories = new(); + + /// + /// Registers a workflow as a function that takes a specified input type and returns a specified output type. + /// + /// Workflow name + /// Function implementing the workflow definition + public void RegisterWorkflow(string name, Func> implementation) + { + // Dapr workflows are implemented as specialized Durable Task orchestrations + this.factories.Add(name, (DurableTaskRegistry registry) => + { + registry.AddOrchestratorFunc(name, (innerContext, input) => + { + WorkflowContext workflowContext = new(innerContext); + return implementation(workflowContext, input); + }); + }); + } + + /// + /// Registers a workflow activity as a function that takes a specified input type and returns a specified output type. + /// + /// Activity name + /// Activity implemetation + public void RegisterActivity(string name, Func> implementation) + { + // Dapr activities are implemented as specialized Durable Task activities + this.factories.Add(name, (DurableTaskRegistry registry) => + { + registry.AddActivityFunc(name, (innerContext, input) => + { + WorkflowActivityContext activityContext = new(innerContext); + return implementation(activityContext, input); + }); + }); + } + + /// + /// Method to add workflows and activities to the registry. + /// + /// The registry we will add workflows and activities to + internal void AddWorkflowsAndActivitiesToRegistry(DurableTaskRegistry registry) + { + foreach (Action factory in this.factories.Values) + { + factory.Invoke(registry); // This adds workflows to the registry indirectly. + } + } + } +} + diff --git a/src/Dapr.Workflow/WorkflowServiceCollectionExtensions.cs b/src/Dapr.Workflow/WorkflowServiceCollectionExtensions.cs new file mode 100644 index 000000000..eaee2c670 --- /dev/null +++ b/src/Dapr.Workflow/WorkflowServiceCollectionExtensions.cs @@ -0,0 +1,66 @@ +// ------------------------------------------------------------------------ +// Copyright 2022 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Workflow +{ + using System; + using System.Linq; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.DependencyInjection.Extensions; + using Microsoft.DurableTask.Client; + using Microsoft.DurableTask.Worker; + using System.Collections.Generic; + + /// + /// Contains extension methods for using Dapr Workflow with dependency injection. + /// + public static class WorkflowServiceCollectionExtensions + { + /// + /// Adds Dapr Workflow support to the service collection. + /// + /// The . + /// A delegate used to configure actor options and register workflow functions. + public static IServiceCollection AddDaprWorkflow( + this IServiceCollection serviceCollection, + Action configure) + { + if (serviceCollection == null) + { + throw new ArgumentNullException(nameof(serviceCollection)); + } + + serviceCollection.TryAddSingleton(); + serviceCollection.TryAddSingleton(); + serviceCollection.AddDaprClient(); + + serviceCollection.AddDurableTaskClient(builder => + { + builder.UseGrpc(); + builder.RegisterDirectly(); + }); + + serviceCollection.AddDurableTaskWorker(builder => + { + WorkflowRuntimeOptions options = new(); + configure?.Invoke(options); + + builder.UseGrpc(); + builder.AddTasks(registry => options.AddWorkflowsAndActivitiesToRegistry(registry)); + }); + + return serviceCollection; + } + } +} +