-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
197 changed files
with
17,239 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# Orleans.Activities | ||
|
||
Workflow Foundation (.Net 4.x System.Activities workflows) over Orleans framework to provide stable, long-running, extremely scalable processes with XAML designer support. | ||
|
||
__NOTE:__ This project currently is an __experiment__, not production quality! There is no NuGet package for it. | ||
|
||
~~Help Wanted Issues~~ (soon) | ||
|
||
~~Documentation~~ (see [HelloWorld](docs/HelloWorld/HelloWorld.md) sample) | ||
|
||
## Concept | ||
|
||
![Overview](docs/Orleans.Activities-Overview.png) | ||
|
||
This is a very high level view: | ||
|
||
* Each WorkflowGrain is indistinguishable from a normal grain and backed by a WorkflowHost. | ||
* The WorkflowHost is responsible to handle the lifecycle of the WorkflowInstance, mainly recreate it from a previous persisted state when it aborts. | ||
* The communication between the WorkflowGrain and the WorkflowHost is based on 2 developer defined interfaces for the incoming and outgoing requests (TAffector and TEffector). These interfaces' methods can be referenced from the workflow activities to accept incoming or to initiate outgoing requests. | ||
* The methods of the TAffector and TEffector interfaces are independent from the grain's external public interface, you can merge different public requests into one method or vice versa. Or a reentrant grain even can execute (read-only) public interface methods independently from the current running workflow operations. | ||
* The method's signatures are restricted, their parameters and return values are lazy, async delegates with 1 optional parameter/return value. The delegates executed by the workflow activities if/when they accept them (command pattern). | ||
* There are design-, build- and static-run-time checks to keep the interfaces and the workflows in sync. | ||
|
||
A typical workflow grain manages operations in other grain(s) and handles only the process specific data in it's own state. | ||
|
||
The goal, is to keep the C# code in the grain, and use the workflow only to decide what to do next. This way we can avoid a steep learning curve to use workflows: the developer doesn't need to write or to understand anything about activities, he/she can build workflows with the provided activities in a designer. | ||
|
||
## Functionality | ||
|
||
Implemented: | ||
|
||
* Persistence (compatible with legacy workflow extensions), but it can run without any persistence | ||
* Reminders (redirected from System.Activities Timers, 1 min. is the minimum) | ||
* Tracking | ||
* Designer support | ||
|
||
Extra implemented features: | ||
|
||
* TAP async API | ||
* Optionally idempotent request processing for forward recovery | ||
* Automatic reactivation after failure | ||
* Workflow can be persisted during processing an incoming request (ReceiveRequestSendResponseScope is __NOT__ an implicit NoPersistScope) | ||
* Executing code "in the background" after a request returns it's response | ||
* Workflow is informed whether it is running in a reloaded state after failure (to determine necessary recovery) | ||
* Notification participant (to notify extensions when the workflow is idle) | ||
|
||
Not tested, but should work: | ||
|
||
* CancellationScope | ||
* CompensableActivity and CompensationExtension | ||
|
||
Under construction: | ||
|
||
* Tests (currently semi manual, semi automatic MSTest, don't even look at them) | ||
* More elaborate sample with | ||
* DI/Autofac | ||
* Strategy and Humble Object patterns, to show an arhitecture, where the application logic can be tested independently from Orleans and from Orleans.Activities workflows | ||
|
||
Not implemented, help wanted (for design and for implementation): | ||
|
||
* DynamicUpdateMap support (updating loaded workflows to a newer definition version), though the separation of the application logic (the plain C# delegates) and the process (the diagram) results in a very simple workflow diagram, that has a big chance you won't need to update when it runs | ||
* TransactionScope activity support (see https://github.com/dotnet/orleans/issues/1090) | ||
|
||
And there are nearly unlimited issues... | ||
|
||
## Samples | ||
|
||
[HelloWorld](docs/HelloWorld/HelloWorld.md) | ||
|
||
## Details | ||
|
||
This is still an overview, all the details of the classes are hidden. The goal is to give a map to understand the relations between the classes. See the comments in the source! | ||
|
||
![Overview](docs/Orleans.Activities-Details.png) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
# Hello World | ||
|
||
Based on Orleans [Hello World](http://dotnet.github.io/orleans/Samples-Overview/Hello-World) sample. | ||
|
||
## Overview | ||
|
||
![SequenceDiagram](SequenceDiagram-Overview.png) | ||
|
||
## Interface | ||
|
||
IHello is the same. | ||
|
||
```c# | ||
public interface IHello : IGrainWithGuidKey | ||
{ | ||
Task<string> SayHello(string greeting); | ||
} | ||
``` | ||
|
||
## Grain | ||
|
||
Before the grain, you have to define 3 things: State, Affector and Effector interfaces | ||
|
||
### State | ||
|
||
Workflows always have a state. Even if they never persist it. You can use the `WorkflowState` base class or implement the `IWorkflowState` interface. | ||
|
||
```c# | ||
public class HelloGrainState : WorkflowState | ||
{ } | ||
``` | ||
|
||
### Affector interface | ||
|
||
These are the operations that the grain calls on the workflow, these operations should __NOT__ be the same as the public grain interface methods (see `IHello`)! | ||
|
||
There are 2 restrictions on the methods: | ||
|
||
* must have 1 parameter, with type `Func<Task<anything>>` or `Func<Task>` (executed when the workflow accepts the request) | ||
* the return type must be `Task` or `Task<anything>` | ||
|
||
```c# | ||
public interface IHelloAffector | ||
{ | ||
Task<string> GreetClient(Func<Task<string>> clientSaid); | ||
} | ||
``` | ||
|
||
### Effector interface | ||
|
||
These are the operations that the workflow calls back on the grain. | ||
|
||
There are 2 restrictions on the methods: | ||
|
||
* can have max. 1 parameter with any type | ||
* the return type must be `Task<Func<Task<anything>>>` or `Task<Func<Task>>` (executed when the workflow accepts the response) | ||
|
||
```c# | ||
public interface IHelloEffector | ||
{ | ||
Task<Func<Task<string>>> WhatShouldISay(string clientSaid); | ||
} | ||
``` | ||
|
||
### Grain | ||
|
||
The class definition, where we define the TState, TAffector and TEffector type parameters. | ||
|
||
__NOTE:__ The grain must implement (if possible explicitly) the effector interface (see `IHelloEffector`). | ||
|
||
```c# | ||
public sealed class HelloGrain : WorkflowGrain<HelloGrainState, IHelloAffector, IHelloEffector>, IHello, IHelloEffector | ||
``` | ||
|
||
Constructor, in this example without Dependency Injection, just define the activity factory and leave the workflow definition identity null. | ||
|
||
```c# | ||
public HelloGrain() | ||
: base((wi) => new HelloActivity(), null) | ||
{ } | ||
``` | ||
|
||
Optionally see what happens during the workflow execution with tracking, this can be left out, there is a default empty implementation in the base class. | ||
|
||
```c# | ||
protected override IEnumerable<object> CreateExtensions() | ||
{ | ||
yield return new GrainTrackingParticipant(GetLogger()); | ||
} | ||
``` | ||
|
||
A mandatory (boilerplate) implementation of the unhandled exception handler. Because workflows can run in the backround after an incoming call returns the result, we can't propagate back exceptions after this point. Workflow will by default abort in case of unhandled exception, depending on the `Parameters` property. | ||
|
||
```c# | ||
protected override Task OnUnhandledExceptionAsync(Exception exception, Activity source) | ||
{ | ||
GetLogger().Error(0, $"OnUnhandledExceptionAsync: the workflow is going to {Parameters.UnhandledExceptionAction}", exception); | ||
return Task.CompletedTask; | ||
} | ||
``` | ||
|
||
The public grain interface method, that does nothing just calls the workflow's only affector operation. A normal grain can store data from the incoming message in the state, call other grains, closure the necessary data into the parameter delegate. After the await it can build a complex response message based on the value the workflow returned and the grain state, or any other information. | ||
|
||
The parameter delegate executed when the workflow accepts the incoming call. | ||
|
||
It also shows how to implement idempotent responses for the incoming calls. In the repeated case, the parameter delegate won't be executed! | ||
|
||
```c# | ||
public async Task<string> SayHello(string greeting) | ||
{ | ||
try | ||
{ | ||
return await WorkflowAffector.GreetClient(() => | ||
Task.FromResult(greeting)); | ||
} | ||
catch (RepeatedOperationException<string> e) | ||
{ | ||
return e.PreviousResponseParameter; | ||
} | ||
} | ||
``` | ||
|
||
This is the explicit implementation of the effector interface's only method, that does nearly nothing. A normal grain can modify the grain's State, call other grain's operations or do nearly anything a normal grain method can. | ||
|
||
The return value delegate executed when the workflow accepts the outgoing call's response. | ||
|
||
```c# | ||
Task<Func<Task<string>>> IHelloEffector.WhatShouldISay(string clientSaid) => | ||
Task.FromResult<Func<Task<string>>>(() => | ||
Task.FromResult(string.IsNullOrEmpty(clientSaid) ? "Who are you?" : "Hello!")); | ||
``` | ||
|
||
And see the Workflow. It calls back the grain, and returns the response to the grain at the end. | ||
|
||
![HelloActivity.xaml](HelloActivity.png) | ||
|
||
That's all. Ctrl+F5, and it works. | ||
|
||
## Details | ||
|
||
If you want to dig deep into the source and understand the detailed events in the background, this sequence diagram can help (this is not a completely valid diagram, but displaying every asnyc details, even the AsyncAutoResetEvent idle-queue, this would be 2 times bigger). | ||
|
||
![SequenceDiagram](SequenceDiagram-Details.png) |
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using System.Reflection; | ||
|
||
[assembly: AssemblyCompany("https://github.com/lmagyar")] | ||
[assembly: AssemblyProduct("Orleans.Activities - https://github.com/lmagyar/Orleans.Activities")] | ||
[assembly: AssemblyCopyright("Copyright © https://github.com/lmagyar 2016")] | ||
|
||
[assembly: AssemblyVersion("0.1.0.0")] | ||
[assembly: AssemblyInformationalVersion("0.1.0")] |
10 changes: 10 additions & 0 deletions
10
src/Orleans.Activities.Sample.HelloWorld.GrainInterfaces/IHello.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using System.Threading.Tasks; | ||
using Orleans; | ||
|
||
namespace Orleans.Activities.Sample.HelloWorld.GrainInterfaces | ||
{ | ||
public interface IHello : IGrainWithGuidKey | ||
{ | ||
Task<string> SayHello(string greeting); | ||
} | ||
} |
108 changes: 108 additions & 0 deletions
108
...le.HelloWorld.GrainInterfaces/Orleans.Activities.Sample.HelloWorld.GrainInterfaces.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
<Import Project="..\packages\Microsoft.Orleans.Templates.Interfaces.1.1.1\build\Microsoft.Orleans.Templates.Interfaces.props" Condition="Exists('..\packages\Microsoft.Orleans.Templates.Interfaces.1.1.1\build\Microsoft.Orleans.Templates.Interfaces.props')" /> | ||
<Import Project="$(SolutionDir)packages\Microsoft.Orleans.Templates.Interfaces.1.1.0\build\Microsoft.Orleans.Templates.Interfaces.props" Condition="Exists('$(SolutionDir)packages\Microsoft.Orleans.Templates.Interfaces.1.1.0\build\Microsoft.Orleans.Templates.Interfaces.props')" /> | ||
<PropertyGroup> | ||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | ||
<Platform>AnyCPU</Platform> | ||
<SchemaVersion>2.0</SchemaVersion> | ||
<ProjectGuid>{2A2508A0-34A7-467C-9AA7-EA2B319DA474}</ProjectGuid> | ||
<OutputType>Library</OutputType> | ||
<AppDesignerFolder>Properties</AppDesignerFolder> | ||
<RootNamespace>Orleans.Activities.Sample.HelloWorld.GrainInterfaces</RootNamespace> | ||
<AssemblyName>Orleans.Activities.Sample.HelloWorld.GrainInterfaces</AssemblyName> | ||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion> | ||
<FileAlignment>512</FileAlignment> | ||
<NuGetPackageImportStamp> | ||
</NuGetPackageImportStamp> | ||
<TargetFrameworkProfile /> | ||
</PropertyGroup> | ||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | ||
<DebugSymbols>true</DebugSymbols> | ||
<DebugType>full</DebugType> | ||
<Optimize>false</Optimize> | ||
<OutputPath>bin\Debug\</OutputPath> | ||
<DefineConstants>DEBUG;TRACE</DefineConstants> | ||
<ErrorReport>prompt</ErrorReport> | ||
<WarningLevel>4</WarningLevel> | ||
<Prefer32Bit>false</Prefer32Bit> | ||
<NoWarn>CSE0001 CSE0003</NoWarn> | ||
</PropertyGroup> | ||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | ||
<DebugType>pdbonly</DebugType> | ||
<Optimize>true</Optimize> | ||
<OutputPath>bin\Release\</OutputPath> | ||
<DefineConstants>TRACE</DefineConstants> | ||
<ErrorReport>prompt</ErrorReport> | ||
<WarningLevel>4</WarningLevel> | ||
<Prefer32Bit>false</Prefer32Bit> | ||
<NoWarn>CSE0001 CSE0003</NoWarn> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<Reference Include="Microsoft.CodeAnalysis, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | ||
<HintPath>..\packages\Microsoft.CodeAnalysis.Common.1.1.1\lib\net45\Microsoft.CodeAnalysis.dll</HintPath> | ||
<Private>True</Private> | ||
</Reference> | ||
<Reference Include="Microsoft.CodeAnalysis.CSharp, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | ||
<HintPath>..\packages\Microsoft.CodeAnalysis.CSharp.1.1.1\lib\net45\Microsoft.CodeAnalysis.CSharp.dll</HintPath> | ||
<Private>True</Private> | ||
</Reference> | ||
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> | ||
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath> | ||
<Private>True</Private> | ||
</Reference> | ||
<Reference Include="Orleans, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> | ||
<HintPath>..\packages\Microsoft.Orleans.Core.1.1.1\lib\net451\Orleans.dll</HintPath> | ||
<Private>True</Private> | ||
</Reference> | ||
<Reference Include="OrleansCodeGenerator, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> | ||
<HintPath>..\packages\Microsoft.Orleans.OrleansCodeGenerator.1.1.1\lib\net451\OrleansCodeGenerator.dll</HintPath> | ||
<Private>True</Private> | ||
</Reference> | ||
<Reference Include="System" /> | ||
<Reference Include="Microsoft.CSharp" /> | ||
<Reference Include="System.Collections.Immutable, Version=1.1.37.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | ||
<HintPath>..\packages\System.Collections.Immutable.1.1.37\lib\dotnet\System.Collections.Immutable.dll</HintPath> | ||
<Private>True</Private> | ||
</Reference> | ||
<Reference Include="System.Core" /> | ||
<Reference Include="System.Data" /> | ||
<Reference Include="System.Data.DataSetExtensions" /> | ||
<Reference Include="System.Reflection.Metadata, Version=1.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | ||
<HintPath>..\packages\System.Reflection.Metadata.1.1.0\lib\dotnet5.2\System.Reflection.Metadata.dll</HintPath> | ||
<Private>True</Private> | ||
</Reference> | ||
<Reference Include="System.Xml" /> | ||
<Reference Include="System.Xml.Linq" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<Compile Include="IHello.cs" /> | ||
<Compile Include="Properties\AssemblyInfo.cs" /> | ||
<Compile Include="Properties\orleans.codegen.cs" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<None Include="app.config" /> | ||
<None Include="packages.config" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.Analyzers.dll" /> | ||
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.Analyzers.dll" /> | ||
</ItemGroup> | ||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | ||
<Import Project="$(SolutionDir)packages\Microsoft.Orleans.Templates.Interfaces.1.1.0\build\Microsoft.Orleans.Templates.Interfaces.targets" Condition="Exists('$(SolutionDir)packages\Microsoft.Orleans.Templates.Interfaces.1.1.0\build\Microsoft.Orleans.Templates.Interfaces.targets')" /> | ||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> | ||
<PropertyGroup> | ||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> | ||
</PropertyGroup> | ||
<Error Condition="!Exists('..\packages\Microsoft.Orleans.Templates.Interfaces.1.1.1\build\Microsoft.Orleans.Templates.Interfaces.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Orleans.Templates.Interfaces.1.1.1\build\Microsoft.Orleans.Templates.Interfaces.props'))" /> | ||
<Error Condition="!Exists('..\packages\Microsoft.Orleans.Templates.Interfaces.1.1.1\build\Microsoft.Orleans.Templates.Interfaces.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Orleans.Templates.Interfaces.1.1.1\build\Microsoft.Orleans.Templates.Interfaces.targets'))" /> | ||
</Target> | ||
<Import Project="..\packages\Microsoft.Orleans.Templates.Interfaces.1.1.1\build\Microsoft.Orleans.Templates.Interfaces.targets" Condition="Exists('..\packages\Microsoft.Orleans.Templates.Interfaces.1.1.1\build\Microsoft.Orleans.Templates.Interfaces.targets')" /> | ||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. | ||
Other similar extension points exist, see Microsoft.Common.targets. | ||
<Target Name="BeforeBuild"> | ||
</Target> | ||
<Target Name="AfterBuild"> | ||
</Target> | ||
--> | ||
</Project> |
Oops, something went wrong.