Workflow Authoring: initial methods (#981)

* Initial workflow sdk implementation

Signed-off-by: Amulya Varote <amulyavarote@microsoft.com>

* Dapr Workflow initial methods for dotnet-sdk

Signed-off-by: Amulya Varote <amulyavarote@microsoft.com>

* Handled mistake in the workflow name

Signed-off-by: Amulya Varote <amulyavarote@microsoft.com>

* Added common license header to .cs files

Signed-off-by: Amulya Varote <amulyavarote@microsoft.com>

* Changed namespace to Dapr.Workflow

Signed-off-by: Amulya Varote <amulyavarote@microsoft.com>

* Addressed docs related review comments

Signed-off-by: Amulya Varote <amulyavarote@microsoft.com>

* Reverted few changes to avoid compile time error

Signed-off-by: Amulya Varote <amulyavarote@microsoft.com>

* Examples changes

Signed-off-by: Amulya Varote <amulyavarote@microsoft.com>

* Added readonly dictionary

Signed-off-by: Amulya Varote <amulyavarote@microsoft.com>

* Added .csproj to sln file with few minor comments

Signed-off-by: Amulya Varote <amulyavarote@microsoft.com>

* Change nuget dependency and update folder structure

This commit re-adds commit d5b9189da5 but with DCO.

Signed-off-by: Tiago Alves Macambira <tmacam@burocrata.org>

* Update Durable Task SDK version & simplify example

Signed-off-by: Chris Gillum <cgillum@microsoft.com>

* Addresses some PR comments.

* Rename ActivityContext to WorkflowActivityContext
* Change example webapp port away from 8080x

Signed-off-by: Tiago Alves Macambira <tmacam@burocrata.org>

* Addressing review comments.

* Rename workflow and activity in example to be more meangniful
* Add parameter documentation to some methods
* Use local project references when appropriate

Signed-off-by: Tiago Alves Macambira <tmacam@burocrata.org>

* Adding more documentation

Signed-off-by: Tiago Alves Macambira <tmacam@burocrata.org>

* Replaces custom AddSingletonIfNotPresent with std. TryAddSingleton

Signed-off-by: Tiago Alves Macambira <tmacam@burocrata.org>

* Renames AddWorkflowsToRegistry to AddActivitiesToRegistry to better match what it does

Signed-off-by: Tiago Alves Macambira <tmacam@burocrata.org>

* Add cancelation token overload for WorkflowContext.WaitForExternalEventAsync

Signed-off-by: Tiago Alves Macambira <tmacam@burocrata.org>

* Renaming AddWorkflowsAndActivitiesToRegistry

Signed-off-by: Tiago Alves Macambira <tmacam@burocrata.org>

* Defer launch URL and port to launchSettings.json

Signed-off-by: Tiago Alves Macambira <tmacam@burocrata.org>

Signed-off-by: Amulya Varote <amulyavarote@microsoft.com>
Signed-off-by: Tiago Alves Macambira <tmacam@burocrata.org>
Signed-off-by: Chris Gillum <cgillum@microsoft.com>
Co-authored-by: Amulya Varote <amulyavarote@microsoft.com>
Co-authored-by: Chris Gillum <cgillum@microsoft.com>
This commit is contained in:
Tiago Alves Macambira 2023-01-12 16:10:21 -08:00 committed by GitHub
parent ab5403ed01
commit 2aa4806c44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 559 additions and 24 deletions

65
all.sln
View File

@ -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}

View File

@ -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<string, string>("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<string>("ShipProduct", "Coffee Beans");
});
// Example of registering a "ShipProduct" workflow activity function
options.RegisterActivity<string, string>("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();

View File

@ -0,0 +1,12 @@
{
"profiles": {
"OrderingWebApi": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:10080"
}
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<ItemGroup>
<ProjectReference Include="..\..\..\src\Dapr.Workflow\Dapr.Workflow.csproj" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net6</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,6 @@
### Create new order
POST http://localhost:8080/workflow
Content-Type: application/json
### Query placeholder
GET http://localhost:8080/workflow/XXX

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- NuGet configuration -->
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net5;net6</TargetFrameworks>
<Nullable>enable</Nullable>
<PackageId>Dapr.Workflow</PackageId>
<Title>Dapr Workflow Authoring SDK</Title>
<Description>Dapr Workflow SDK for building workflows as code with Dapr</Description>
<VersionPrefix>0.1.0</VersionPrefix>
<VersionSuffix>alpha</VersionSuffix>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DurableTask.Client.Grpc" Version="1.0.0-rc.1" />
<PackageReference Include="Microsoft.DurableTask.Worker.Grpc" Version="1.0.0-rc.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dapr.Client\Dapr.Client.csproj" />
<ProjectReference Include="..\Dapr.AspNetCore\Dapr.AspNetCore.csproj" />
</ItemGroup>
</Project>

View File

@ -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;
/// <summary>
/// Defines properties and methods for task activity context objects.
/// </summary>
public class WorkflowActivityContext
{
readonly TaskActivityContext innerContext;
internal WorkflowActivityContext(TaskActivityContext innerContext)
{
this.innerContext = innerContext ?? throw new ArgumentNullException(nameof(innerContext));
}
/// <summary>
/// Gets the name of the activity.
/// </summary>
public TaskName Name => this.innerContext.Name;
/// <summary>
/// Gets the unique ID of the current workflow instance.
/// </summary>
public string InstanceId => this.innerContext.InstanceId;
}
}

View File

@ -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.
/// <summary>
/// Defines client operations for managing Dapr Workflow instances.
/// </summary>
public sealed class WorkflowClient : IAsyncDisposable
{
readonly DurableTaskClient innerClient;
/// <summary>
/// Initializes a new instance of the <see cref="WorkflowClient"/> class.
/// </summary>
/// <param name="innerClient">The Durable Task client used to communicate with the Dapr sidecar.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="innerClient"/> is <c>null</c>.</exception>
public WorkflowClient(DurableTaskClient innerClient)
{
this.innerClient = innerClient ?? throw new ArgumentNullException(nameof(innerClient));
}
/// <summary>
/// Schedules a new workflow instance for execution.
/// </summary>
/// <param name="name">The name of the orchestrator to schedule.</param>
/// <param name="instanceId">
/// The unique ID of the orchestration instance to schedule. If not specified, a new GUID value is used.
/// </param>
/// <param name="startTime">
/// 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.
/// </param>
/// <param name="input">
/// The optional input to pass to the scheduled orchestration instance. This must be a serializable value.
/// </param>
public Task<string> ScheduleNewWorkflowAsync(
string name,
string? instanceId = null,
object? input = null,
DateTime? startTime = null)
{
StartOrchestrationOptions options = new(instanceId, startTime);
return this.innerClient.ScheduleNewOrchestrationInstanceAsync(name, input, options);
}
/// <summary>
/// Fetches runtime metadata for the specified workflow instance.
/// </summary>
/// <param name="instanceId">The unique ID of the orchestration instance to fetch.</param>
/// <param name="getInputsAndOutputs">
/// Specify <c>true</c> to fetch the orchestration instance's inputs, outputs, and custom status, or <c>false</c> to
/// omit them. Defaults to false.
/// </param>
public async Task<WorkflowMetadata> GetWorkflowMetadataAsync(string instanceId, bool getInputsAndOutputs = false)
{
OrchestrationMetadata? metadata = await this.innerClient.GetInstanceMetadataAsync(
instanceId,
getInputsAndOutputs);
return new WorkflowMetadata(metadata);
}
/// <summary>
/// Disposes any unmanaged resources associated with this client.
/// </summary>
public ValueTask DisposeAsync()
{
return ((IAsyncDisposable)this.innerClient).DisposeAsync();
}
}
}

View File

@ -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;
/// <summary>
/// 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.
/// </summary>
public class WorkflowContext
{
readonly TaskOrchestrationContext innerContext;
internal WorkflowContext(TaskOrchestrationContext innerContext)
{
this.innerContext = innerContext ?? throw new ArgumentNullException(nameof(innerContext));
}
/// <summary>
/// Gets the name of the current workflow.
/// </summary>
public TaskName Name => this.innerContext.Name;
/// <summary>
/// Gets the instance ID of the current workflow.
/// </summary>
public string InstanceId => this.innerContext.InstanceId;
/// <summary>
/// Gets the current workflow time in UTC.
/// </summary>
public DateTime CurrentUtcDateTime => this.innerContext.CurrentUtcDateTime;
/// <summary>
/// Assigns a custom status value to the current workflow.
/// </summary>
public void SetCustomStatus(object? customStatus) => this.innerContext.SetCustomStatus(customStatus);
/// <summary>
/// Creates a durable timer that expires after the specified delay.
/// </summary>
/// <inheritdoc cref="TaskOrchestrationContext.CreateTimer(TimeSpan, CancellationToken)"/>
public Task CreateTimer(TimeSpan delay, CancellationToken cancellationToken = default)
{
return this.innerContext.CreateTimer(delay, cancellationToken);
}
/// <summary>
/// Waits for an event to be raised with name <paramref name="eventName"/> and returns the event data.
/// </summary>
/// <inheritdoc cref="TaskOrchestrationContext.WaitForExternalEvent{T}(string, TimeSpan)"/>
public Task<T> WaitForExternalEventAsync<T>(string eventName, TimeSpan timeout)
{
return this.innerContext.WaitForExternalEvent<T>(eventName, timeout);
}
/// <summary>
/// Waits for an event to be raised with name <paramref name="eventName"/> and returns the event data.
/// </summary>
/// <inheritdoc cref="TaskOrchestrationContext.WaitForExternalEvent{T}(string, CancellationToken)"/>
public Task<T> WaitForExternalEventAsync<T>(string eventName, CancellationToken cancellationToken = default)
{
return this.innerContext.WaitForExternalEvent<T>(eventName, cancellationToken);
}
/// <summary>
/// Asynchronously invokes an activity by name and with the specified input value.
/// </summary>
/// <inheritdoc cref="TaskOrchestrationContext.CallActivityAsync{T}(TaskName, object?, TaskOptions?)"/>
public Task<T> CallActivityAsync<T>(TaskName name, object? input = null, TaskOptions? options = null)
{
return this.innerContext.CallActivityAsync<T>(name, input, options);
}
}
}

View File

@ -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
{
/// <summary>
/// Represents a snapshot of a workflow instance's current state, including metadata.
/// </summary>
public class WorkflowMetadata
{
internal WorkflowMetadata(OrchestrationMetadata? metadata)
{
this.Details = metadata;
}
/// <summary>
/// Gets a value indicating whether the requested workflow instance exists.
/// </summary>
public bool Exists => this.Details != null;
/// <summary>
/// Gets a value indicating whether the requested workflow is in a running state.
/// </summary>
public bool IsWorkflowRunning => this.Details?.RuntimeStatus == OrchestrationRuntimeStatus.Running;
/// <summary>
/// Gets the detailed metadata for the requested workflow instance.
/// This value will be <c>null</c> when <see cref="Exists" /> is <c>false</c>.
/// </summary>
public OrchestrationMetadata? Details { get; }
}
}

View File

@ -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;
/// <summary>
/// Defines runtime options for workflows.
/// </summary>
public sealed class WorkflowRuntimeOptions
{
/// <summary>
/// Dictionary to name and register a workflow.
/// </summary>
readonly Dictionary<string, Action<DurableTaskRegistry>> factories = new();
/// <summary>
/// Registers a workflow as a function that takes a specified input type and returns a specified output type.
/// </summary>
/// <param name="name">Workflow name</param>
/// <param name="implementation">Function implementing the workflow definition</param>
public void RegisterWorkflow<TInput, TOutput>(string name, Func<WorkflowContext, TInput, Task<TOutput>> implementation)
{
// Dapr workflows are implemented as specialized Durable Task orchestrations
this.factories.Add(name, (DurableTaskRegistry registry) =>
{
registry.AddOrchestratorFunc<TInput, TOutput>(name, (innerContext, input) =>
{
WorkflowContext workflowContext = new(innerContext);
return implementation(workflowContext, input);
});
});
}
/// <summary>
/// Registers a workflow activity as a function that takes a specified input type and returns a specified output type.
/// </summary>
/// <param name="name">Activity name</param>
/// <param name="implementation">Activity implemetation</param>
public void RegisterActivity<TInput, TOutput>(string name, Func<WorkflowActivityContext, TInput, Task<TOutput>> implementation)
{
// Dapr activities are implemented as specialized Durable Task activities
this.factories.Add(name, (DurableTaskRegistry registry) =>
{
registry.AddActivityFunc<TInput, TOutput>(name, (innerContext, input) =>
{
WorkflowActivityContext activityContext = new(innerContext);
return implementation(activityContext, input);
});
});
}
/// <summary>
/// Method to add workflows and activities to the registry.
/// </summary>
/// <param name="registry">The registry we will add workflows and activities to</param>
internal void AddWorkflowsAndActivitiesToRegistry(DurableTaskRegistry registry)
{
foreach (Action<DurableTaskRegistry> factory in this.factories.Values)
{
factory.Invoke(registry); // This adds workflows to the registry indirectly.
}
}
}
}

View File

@ -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;
/// <summary>
/// Contains extension methods for using Dapr Workflow with dependency injection.
/// </summary>
public static class WorkflowServiceCollectionExtensions
{
/// <summary>
/// Adds Dapr Workflow support to the service collection.
/// </summary>
/// <param name="serviceCollection">The <see cref="IServiceCollection"/>.</param>
/// <param name="configure">A delegate used to configure actor options and register workflow functions.</param>
public static IServiceCollection AddDaprWorkflow(
this IServiceCollection serviceCollection,
Action<WorkflowRuntimeOptions> configure)
{
if (serviceCollection == null)
{
throw new ArgumentNullException(nameof(serviceCollection));
}
serviceCollection.TryAddSingleton<WorkflowRuntimeOptions>();
serviceCollection.TryAddSingleton<WorkflowClient>();
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;
}
}
}