├── .editorconfig
├── .github
└── workflows
│ └── validate-build.yml
├── .gitignore
├── DurableTask.Dapr.sln
├── LICENSE
├── README.md
├── nuget.config
├── samples
└── order-processing-service
│ ├── components
│ ├── pubsub.yaml
│ └── statestore.yaml
│ ├── demo.http
│ ├── inventory-service
│ ├── README.md
│ ├── app.py
│ └── requirements.txt
│ ├── notification-service
│ ├── README.md
│ ├── app.ts
│ ├── package-lock.json
│ ├── package.json
│ └── tsconfig.json
│ ├── payments-service
│ ├── README.md
│ ├── app.ts
│ ├── package-lock.json
│ ├── package.json
│ └── tsconfig.json
│ └── web-api
│ ├── OrderingWebApi.csproj
│ ├── Program.cs
│ ├── Properties
│ └── launchSettings.json
│ └── README.md
├── src
├── Dapr.Workflow
│ ├── Dapr.Workflow.csproj
│ ├── InvokeServiceMethodActivity.cs
│ ├── WorkflowClient.cs
│ ├── WorkflowContext.cs
│ ├── WorkflowMetadata.cs
│ ├── WorkflowRuntimeOptions.cs
│ └── WorkflowServiceCollectionExtensions.cs
├── DaprOrchestrationService.cs
├── DurableTask.Dapr
│ ├── Activities
│ │ ├── ActivityCompletionResponse.cs
│ │ ├── ActivityInvocationRequest.cs
│ │ ├── IActivityActor.cs
│ │ ├── IActivityExecutor.cs
│ │ └── StatelessActivityActor.cs
│ ├── DaprOptions.cs
│ ├── DaprOrchestrationService.cs
│ ├── DurableTask.Dapr.csproj
│ ├── Helpers.cs
│ ├── Logs.cs
│ ├── OrchestrationServiceBase.cs
│ ├── ReliableActor.cs
│ └── Workflows
│ │ ├── IWorkflowActor.cs
│ │ ├── IWorkflowExecutor.cs
│ │ └── WorkflowActor.cs
└── Workflows
│ ├── IWorkflowActor.cs
│ ├── IWorkflowExecutor.cs
│ └── WorkflowActor.cs
└── test
├── DurableTask.Dapr.Tests
└── DurableTask.Dapr.Tests.csproj
└── ManualTesting
├── ManualTesting.csproj
├── Program.cs
└── README.md
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories
2 | root = true
3 |
4 | # C# files
5 | [*.cs]
6 |
7 | #### Core EditorConfig Options ####
8 |
9 | # Indentation and spacing
10 | indent_size = 4
11 | indent_style = space
12 | tab_width = 4
13 |
14 | # New line preferences
15 | end_of_line = crlf
16 | insert_final_newline = false
17 |
18 | #### .NET Coding Conventions ####
19 |
20 | # Organize usings
21 | dotnet_separate_import_directive_groups = false
22 | dotnet_sort_system_directives_first = true
23 |
24 | # this. and Me. preferences
25 | dotnet_style_qualification_for_event = true:warning
26 | dotnet_style_qualification_for_field = true:warning
27 | dotnet_style_qualification_for_method = true:warning
28 | dotnet_style_qualification_for_property = true:warning
29 |
30 | # Language keywords vs BCL types preferences
31 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning
32 | dotnet_style_predefined_type_for_member_access = true:silent
33 |
34 | # Parentheses preferences
35 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
36 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
37 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
38 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
39 |
40 | # Modifier preferences
41 | dotnet_style_require_accessibility_modifiers = omit_if_default:warning
42 |
43 | # Expression-level preferences
44 | dotnet_style_coalesce_expression = true:suggestion
45 | dotnet_style_collection_initializer = true:suggestion
46 | dotnet_style_explicit_tuple_names = true:suggestion
47 | dotnet_style_null_propagation = true:suggestion
48 | dotnet_style_object_initializer = true:suggestion
49 | dotnet_style_prefer_auto_properties = true:silent
50 | dotnet_style_prefer_compound_assignment = true:suggestion
51 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent
52 | dotnet_style_prefer_conditional_expression_over_return = true:silent
53 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
54 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
55 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
56 | dotnet_style_prefer_simplified_interpolation = true:suggestion
57 |
58 | # Field preferences
59 | dotnet_style_readonly_field = true:warning
60 |
61 | # Parameter preferences
62 | dotnet_code_quality_unused_parameters = all:suggestion
63 |
64 | #### C# Coding Conventions ####
65 |
66 | # var preferences
67 | csharp_style_var_elsewhere = false:suggestion
68 | csharp_style_var_for_built_in_types = false:suggestion
69 | csharp_style_var_when_type_is_apparent = false:silent
70 |
71 | # Expression-bodied members
72 | csharp_style_expression_bodied_accessors = true:silent
73 | csharp_style_expression_bodied_constructors = false:silent
74 | csharp_style_expression_bodied_indexers = true:silent
75 | csharp_style_expression_bodied_lambdas = true:silent
76 | csharp_style_expression_bodied_local_functions = false:silent
77 | csharp_style_expression_bodied_methods = false:silent
78 | csharp_style_expression_bodied_operators = false:silent
79 | csharp_style_expression_bodied_properties = true:silent
80 |
81 | # Pattern matching preferences
82 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
83 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
84 | csharp_style_prefer_switch_expression = false:suggestion
85 |
86 | # Null-checking preferences
87 | csharp_style_conditional_delegate_call = true:suggestion
88 |
89 | # Modifier preferences
90 | csharp_prefer_static_local_function = true:suggestion
91 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
92 |
93 | # Code-block preferences
94 | csharp_prefer_braces = true:suggestion
95 | csharp_prefer_simple_using_statement = true:suggestion
96 |
97 | # Expression-level preferences
98 | csharp_prefer_simple_default_expression = true:suggestion
99 | csharp_style_deconstructed_variable_declaration = true:suggestion
100 | csharp_style_inlined_variable_declaration = true:suggestion
101 | csharp_style_pattern_local_over_anonymous_function = true:suggestion
102 | csharp_style_prefer_index_operator = true:suggestion
103 | csharp_style_prefer_range_operator = true:suggestion
104 | csharp_style_throw_expression = true:suggestion
105 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion
106 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent
107 |
108 | # 'using' directive preferences
109 | csharp_using_directive_placement = outside_namespace:silent
110 |
111 | #### C# Formatting Rules ####
112 |
113 | # New line preferences
114 | csharp_new_line_before_catch = true
115 | csharp_new_line_before_else = true
116 | csharp_new_line_before_finally = true
117 | csharp_new_line_before_members_in_anonymous_types = true
118 | csharp_new_line_before_members_in_object_initializers = true
119 | csharp_new_line_before_open_brace = all
120 | csharp_new_line_between_query_expression_clauses = true
121 |
122 | # Indentation preferences
123 | csharp_indent_block_contents = true
124 | csharp_indent_braces = false
125 | csharp_indent_case_contents = true
126 | csharp_indent_case_contents_when_block = true
127 | csharp_indent_labels = one_less_than_current
128 | csharp_indent_switch_labels = true
129 |
130 | # Space preferences
131 | csharp_space_after_cast = false
132 | csharp_space_after_colon_in_inheritance_clause = true
133 | csharp_space_after_comma = true
134 | csharp_space_after_dot = false
135 | csharp_space_after_keywords_in_control_flow_statements = true
136 | csharp_space_after_semicolon_in_for_statement = true
137 | csharp_space_around_binary_operators = before_and_after
138 | csharp_space_around_declaration_statements = false
139 | csharp_space_before_colon_in_inheritance_clause = true
140 | csharp_space_before_comma = false
141 | csharp_space_before_dot = false
142 | csharp_space_before_open_square_brackets = false
143 | csharp_space_before_semicolon_in_for_statement = false
144 | csharp_space_between_empty_square_brackets = false
145 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
146 | csharp_space_between_method_call_name_and_opening_parenthesis = false
147 | csharp_space_between_method_call_parameter_list_parentheses = false
148 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
149 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
150 | csharp_space_between_method_declaration_parameter_list_parentheses = false
151 | csharp_space_between_parentheses = false
152 | csharp_space_between_square_brackets = false
153 |
154 | # Wrapping preferences
155 | csharp_preserve_single_line_blocks = true
156 | csharp_preserve_single_line_statements = true
157 |
158 | #### Naming styles ####
159 |
160 | # Naming rules
161 |
162 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
163 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
164 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
165 |
166 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
167 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types
168 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
169 |
170 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
171 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
172 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
173 |
174 | # Symbol specifications
175 |
176 | dotnet_naming_symbols.interface.applicable_kinds = interface
177 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
178 | dotnet_naming_symbols.interface.required_modifiers =
179 |
180 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
181 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
182 | dotnet_naming_symbols.types.required_modifiers =
183 |
184 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
185 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
186 | dotnet_naming_symbols.non_field_members.required_modifiers =
187 |
188 | # Naming styles
189 |
190 | dotnet_naming_style.pascal_case.required_prefix =
191 | dotnet_naming_style.pascal_case.required_suffix =
192 | dotnet_naming_style.pascal_case.word_separator =
193 | dotnet_naming_style.pascal_case.capitalization = pascal_case
194 |
195 | dotnet_naming_style.begins_with_i.required_prefix = I
196 | dotnet_naming_style.begins_with_i.required_suffix =
197 | dotnet_naming_style.begins_with_i.word_separator =
198 | dotnet_naming_style.begins_with_i.capitalization = pascal_case
199 |
200 | # Other
201 |
202 | file_header_template = Copyright (c) Microsoft Corporation.\nLicensed under the MIT License.
203 | csharp_style_namespace_declarations = file_scoped:silent
204 | csharp_style_prefer_method_group_conversion = true:silent
205 | csharp_style_prefer_null_check_over_type_check = true:suggestion
206 |
207 | [*.{cs,vb}]
208 | dotnet_style_operator_placement_when_wrapping = beginning_of_line
209 | tab_width = 4
210 | indent_size = 4
211 | end_of_line = crlf
212 | dotnet_style_coalesce_expression = true:suggestion
213 | dotnet_style_null_propagation = true:suggestion
214 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
215 | dotnet_style_prefer_auto_properties = true:silent
216 | dotnet_style_object_initializer = true:suggestion
217 | dotnet_style_collection_initializer = true:suggestion
218 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
219 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent
220 | dotnet_style_prefer_conditional_expression_over_return = true:silent
221 | dotnet_style_explicit_tuple_names = true:suggestion
222 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
223 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
224 | dotnet_style_prefer_compound_assignment = true:suggestion
225 | dotnet_style_prefer_simplified_interpolation = true:suggestion
226 | dotnet_style_namespace_match_folder = true:suggestion
227 |
228 | # https://marketplace.visualstudio.com/items?itemName=PaulHarrington.EditorGuidelinesPreview
229 | guidelines = 120
230 |
--------------------------------------------------------------------------------
/.github/workflows/validate-build.yml:
--------------------------------------------------------------------------------
1 | name: .NET
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v3
16 | - name: Setup .NET
17 | uses: actions/setup-dotnet@v2
18 | with:
19 | dotnet-version: 6.0.x
20 | - name: Restore dependencies
21 | run: dotnet restore
22 | - name: Build
23 | run: dotnet build --no-restore
24 |
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
--------------------------------------------------------------------------------
/DurableTask.Dapr.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.32126.317
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5D8C7CC1-3734-460E-B19F-234D9FE8B8AE}"
7 | ProjectSection(SolutionItems) = preProject
8 | .editorconfig = .editorconfig
9 | nuget.config = nuget.config
10 | README.md = README.md
11 | EndProjectSection
12 | EndProject
13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurableTask.Dapr", "src\DurableTask.Dapr\DurableTask.Dapr.csproj", "{0CC77842-4679-413A-8151-1611885F7504}"
14 | EndProject
15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{1019D639-F84B-4218-BA87-5F0EFCA783F8}"
16 | EndProject
17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "order-processing-service", "order-processing-service", "{285E7F62-224D-4D70-AB9B-CFCA615AC5CA}"
18 | EndProject
19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "notification-service", "notification-service", "{C6D345B2-DE6A-42AF-A5CC-E0EE35275305}"
20 | ProjectSection(SolutionItems) = preProject
21 | samples\order-processing-service\notification-service\app.ts = samples\order-processing-service\notification-service\app.ts
22 | samples\order-processing-service\notification-service\index.js = samples\order-processing-service\notification-service\index.js
23 | samples\order-processing-service\notification-service\package.json = samples\order-processing-service\notification-service\package.json
24 | EndProjectSection
25 | EndProject
26 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{48A0D675-C294-4D76-BA3B-BD89961EAF8B}"
27 | EndProject
28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManualTesting", "test\ManualTesting\ManualTesting.csproj", "{3462245F-51CB-4B4F-B41C-309F1DDC7A2B}"
29 | EndProject
30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurableTask.Dapr.Tests", "test\DurableTask.Dapr.Tests\DurableTask.Dapr.Tests.csproj", "{091F3C29-856E-4E2F-9E4A-59EBC6965C86}"
31 | EndProject
32 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "web-api", "web-api", "{76E9C700-415B-47EA-B665-1EB110B551EE}"
33 | EndProject
34 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrderingWebApi", "samples\order-processing-service\web-api\OrderingWebApi.csproj", "{644F7359-4C18-4193-8AA2-BB6BDED6BF95}"
35 | EndProject
36 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.Workflow", "src\Dapr.Workflow\Dapr.Workflow.csproj", "{E293AE6D-1C1A-4E3C-8CD6-0BD0C8A798D8}"
37 | EndProject
38 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "components", "components", "{88AC5DA7-EBD1-47EF-A70E-9302FC3FCEB5}"
39 | ProjectSection(SolutionItems) = preProject
40 | samples\order-processing-service\components\pubsub.yaml = samples\order-processing-service\components\pubsub.yaml
41 | samples\order-processing-service\components\statestore.yaml = samples\order-processing-service\components\statestore.yaml
42 | EndProjectSection
43 | EndProject
44 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "payments-service", "payments-service", "{2FADA62F-F8A2-4EA1-B35A-CB8A570A6062}"
45 | ProjectSection(SolutionItems) = preProject
46 | samples\order-processing-service\payments-service\app.ts = samples\order-processing-service\payments-service\app.ts
47 | samples\order-processing-service\payments-service\package.json = samples\order-processing-service\payments-service\package.json
48 | samples\order-processing-service\payments-service\README.md = samples\order-processing-service\payments-service\README.md
49 | EndProjectSection
50 | EndProject
51 | Global
52 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
53 | Debug|Any CPU = Debug|Any CPU
54 | Release|Any CPU = Release|Any CPU
55 | EndGlobalSection
56 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
57 | {0CC77842-4679-413A-8151-1611885F7504}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
58 | {0CC77842-4679-413A-8151-1611885F7504}.Debug|Any CPU.Build.0 = Debug|Any CPU
59 | {0CC77842-4679-413A-8151-1611885F7504}.Release|Any CPU.ActiveCfg = Release|Any CPU
60 | {0CC77842-4679-413A-8151-1611885F7504}.Release|Any CPU.Build.0 = Release|Any CPU
61 | {3462245F-51CB-4B4F-B41C-309F1DDC7A2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
62 | {3462245F-51CB-4B4F-B41C-309F1DDC7A2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
63 | {3462245F-51CB-4B4F-B41C-309F1DDC7A2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
64 | {3462245F-51CB-4B4F-B41C-309F1DDC7A2B}.Release|Any CPU.Build.0 = Release|Any CPU
65 | {091F3C29-856E-4E2F-9E4A-59EBC6965C86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
66 | {091F3C29-856E-4E2F-9E4A-59EBC6965C86}.Debug|Any CPU.Build.0 = Debug|Any CPU
67 | {091F3C29-856E-4E2F-9E4A-59EBC6965C86}.Release|Any CPU.ActiveCfg = Release|Any CPU
68 | {091F3C29-856E-4E2F-9E4A-59EBC6965C86}.Release|Any CPU.Build.0 = Release|Any CPU
69 | {644F7359-4C18-4193-8AA2-BB6BDED6BF95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
70 | {644F7359-4C18-4193-8AA2-BB6BDED6BF95}.Debug|Any CPU.Build.0 = Debug|Any CPU
71 | {644F7359-4C18-4193-8AA2-BB6BDED6BF95}.Release|Any CPU.ActiveCfg = Release|Any CPU
72 | {644F7359-4C18-4193-8AA2-BB6BDED6BF95}.Release|Any CPU.Build.0 = Release|Any CPU
73 | {E293AE6D-1C1A-4E3C-8CD6-0BD0C8A798D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
74 | {E293AE6D-1C1A-4E3C-8CD6-0BD0C8A798D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
75 | {E293AE6D-1C1A-4E3C-8CD6-0BD0C8A798D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
76 | {E293AE6D-1C1A-4E3C-8CD6-0BD0C8A798D8}.Release|Any CPU.Build.0 = Release|Any CPU
77 | EndGlobalSection
78 | GlobalSection(SolutionProperties) = preSolution
79 | HideSolutionNode = FALSE
80 | EndGlobalSection
81 | GlobalSection(NestedProjects) = preSolution
82 | {285E7F62-224D-4D70-AB9B-CFCA615AC5CA} = {1019D639-F84B-4218-BA87-5F0EFCA783F8}
83 | {C6D345B2-DE6A-42AF-A5CC-E0EE35275305} = {285E7F62-224D-4D70-AB9B-CFCA615AC5CA}
84 | {3462245F-51CB-4B4F-B41C-309F1DDC7A2B} = {48A0D675-C294-4D76-BA3B-BD89961EAF8B}
85 | {091F3C29-856E-4E2F-9E4A-59EBC6965C86} = {48A0D675-C294-4D76-BA3B-BD89961EAF8B}
86 | {76E9C700-415B-47EA-B665-1EB110B551EE} = {285E7F62-224D-4D70-AB9B-CFCA615AC5CA}
87 | {644F7359-4C18-4193-8AA2-BB6BDED6BF95} = {76E9C700-415B-47EA-B665-1EB110B551EE}
88 | {88AC5DA7-EBD1-47EF-A70E-9302FC3FCEB5} = {285E7F62-224D-4D70-AB9B-CFCA615AC5CA}
89 | {2FADA62F-F8A2-4EA1-B35A-CB8A570A6062} = {285E7F62-224D-4D70-AB9B-CFCA615AC5CA}
90 | EndGlobalSection
91 | GlobalSection(ExtensibilityGlobals) = postSolution
92 | SolutionGuid = {C48C815F-B07A-4E95-9BE9-192DB216D52C}
93 | EndGlobalSection
94 | EndGlobal
95 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Chris Gillum
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Dapr Provider for the Durable Task Framework
2 |
3 | This repo contains the code for a [Durable Task Framework](https://github.com/Azure/durabletask) .NET-based storage provider that uses [Dapr Actors](https://docs.dapr.io/developing-applications/building-blocks/actors/actors-overview/) for state storage and scheduling. It's meant to be used as a proof-of-concept / reference implementation for the embedded engine described in the [Dapr Workflow proposal](https://github.com/dapr/dapr/issues/4576).
4 |
5 | This repo also contains a sample Dapr Workflow SDK for .NET that is designed to be used with the Dapr backend.
6 |
7 | > **IMPORTANT** This code is NOT going to be used in any way as the actual Dapr Workflow implementation. The "real" implementation will be done in Go so that it can be embedded into the Dapr sidecar. This project is just a reference implementation to help us tackle some of the design details around running workflows on top of actors.
8 |
9 | ## Getting started
10 |
11 | If you're interested in playing with this reference implementation, please see the README and the code under [test/DaprTesting](test/DaprTesting/). Alternatively, you can find an end-to-end sample under [samples](samples).
12 |
13 | ### Prerequisites
14 |
15 | The following dependencies must be installed on your machine to build and test this reference implementation.
16 |
17 | * [.NET 6 SDK or newer](https://dotnet.microsoft.com/download/dotnet/6.0)
18 | * [Dapr CLI 1.8 or newer](https://docs.dapr.io/getting-started/install-dapr-cli/)
19 |
20 | Other dependencies may be required for building and running the samples.
21 |
22 | ## Contributing
23 |
24 | Contributions via PRs are absolutely welcome! Keep in mind, however, that this is a reference implementation and not meant to be used for actual application development. A repo in the [Dapr GitHub organization](https://github.com/dapr) will be used for the real implementation.
25 |
26 | Feel free to open issues in this repo to ask questions or provide feedback about the reference implementation. If you have questions or feedback about Dapr Workflow generally, please submit comments to https://github.com/dapr/dapr/issues/4576.
27 |
--------------------------------------------------------------------------------
/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/samples/order-processing-service/components/pubsub.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: dapr.io/v1alpha1
2 | kind: Component
3 | metadata:
4 | name: notifications-pubsub
5 | spec:
6 | type: pubsub.redis
7 | version: v1
8 | metadata:
9 | - name: redisHost
10 | value: localhost:6379
11 | - name: redisPassword
12 | value: ""
13 |
--------------------------------------------------------------------------------
/samples/order-processing-service/components/statestore.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: dapr.io/v1alpha1
2 | kind: Component
3 | metadata:
4 | name: statestore
5 | spec:
6 | type: state.redis
7 | version: v1
8 | metadata:
9 | - name: redisHost
10 | value: localhost:6379
11 | - name: redisPassword
12 | value: ""
13 | - name: actorStateStore
14 | value: "true"
15 |
--------------------------------------------------------------------------------
/samples/order-processing-service/demo.http:
--------------------------------------------------------------------------------
1 | ### Create new order (no approval required)
2 | POST http://localhost:8080/orders
3 | Content-Type: application/json
4 |
5 | { "name": "catfood", "quantity": 3, "totalCost": 19.99 }
6 |
7 | ### Query placeholder
8 | GET http://localhost:8080/orders/XXX
9 |
10 |
11 | ### Create new order (approval required)
12 | POST http://localhost:8080/orders
13 | Content-Type: application/json
14 |
15 | { "name": "iphone", "quantity": 1, "totalCost": 1199.99 }
16 |
17 | ### Approve placeholder
18 | POST http://localhost:8080/orders/XXX/approve
19 |
20 | ### Create new order (durability test)
21 | POST http://localhost:8080/orders
22 | Content-Type: application/json
23 |
24 | { "name": "tacos", "quantity": 3, "totalCost": 4.99 }
25 |
26 |
27 | ### Existing workflow - Tesla order
28 | GET http://localhost:8080/orders/9cbf66c4
29 |
30 | ### Existing workflow - BMW order
31 | GET http://localhost:8080/orders/f6142363
--------------------------------------------------------------------------------
/samples/order-processing-service/inventory-service/README.md:
--------------------------------------------------------------------------------
1 | ## Overview
2 |
3 | This is a Python Flask app that represents an inventory service. It's called by the order processing workflow when checking to see if there is sufficient inventory for a particular order. The workflow uses the Dapr service invocation building block to invoke this service.
4 |
5 | ## Prerequisites
6 |
7 | * [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started)
8 | * [Python 3.7+ installed](https://www.python.org/downloads/)
9 | * `PATH` environment variable includes `python` command and maps to Python 3.
10 |
11 | ## Install dependencies
12 |
13 | This will install Flask as well as the Dapr SDK for Python.
14 |
15 | ```bash
16 | pip3 install -r requirements.txt
17 | ```
18 |
19 | ## Running the service
20 |
21 | This will start up both the Dapr sidecar process and the Python app together.
22 |
23 | ```bash
24 | dapr run --app-id inventory --app-port 5006 --dapr-http-port 3506 -- python app.py
25 | ```
26 |
--------------------------------------------------------------------------------
/samples/order-processing-service/inventory-service/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, request
2 | import json
3 | import os
4 |
5 | app = Flask(__name__)
6 |
7 |
8 | @app.route('/reserve-inventory', methods=['POST'])
9 | def reserve_inventory():
10 | data = request.json
11 | print('Request received : ' + json.dumps(data), flush=True)
12 | return json.dumps({'success': True}), 200, {'ContentType': 'application/json'}
13 |
14 |
15 | app.run(port=os.environ.get('APP_PORT', 5006))
--------------------------------------------------------------------------------
/samples/order-processing-service/inventory-service/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask
2 | dapr
--------------------------------------------------------------------------------
/samples/order-processing-service/notification-service/README.md:
--------------------------------------------------------------------------------
1 | ## Overview
2 |
3 | This is a Node.js app that represents the notification service. It's invoked as a pub/sub subscriber and is triggered several times by the order processing workflow as it makes progress.
4 |
5 | Note that the pub/sub configuration can be found in [pubsub.yaml](../components/pubsub.yaml).
6 |
7 | ## Prerequisites
8 |
9 | * [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started)
10 | * [Latest Node.js installed](https://nodejs.org/)
11 | * [ts-node](https://www.npmjs.com/package/ts-node)
12 | * Make sure `npm` and `ts-node` are in the `PATH`
13 |
14 | ## Install dependencies
15 |
16 | This will install TypeScript, express.js, and the Dapr SDK for JavaScript.
17 |
18 | ```bash
19 | npm install
20 | ```
21 |
22 | ## Running the service
23 |
24 | This will start up both the Dapr sidecar process and the Node.js app together.
25 |
26 | ```bash
27 | npm run start:dapr
28 | ```
29 |
--------------------------------------------------------------------------------
/samples/order-processing-service/notification-service/app.ts:
--------------------------------------------------------------------------------
1 | import { DaprServer, CommunicationProtocolEnum } from 'dapr-client';
2 |
3 | const DAPR_HOST = process.env.DAPR_HOST || "http://localhost";
4 | const DAPR_HTTP_PORT = process.env.DAPR_HTTP_PORT || "3502";
5 | const SERVER_HOST = process.env.SERVER_HOST || "127.0.0.1";
6 | const SERVER_PORT = process.env.SERVER_PORT || "5002";
7 | const PUBSUB_NAME = process.env.PUBSUB_NAME || "notifications-pubsub";
8 | const PUBSUB_TOPIC = process.env.PUBSUB_TOPIC || "notifications";
9 |
10 | async function main() {
11 | const server = new DaprServer(
12 | SERVER_HOST,
13 | SERVER_PORT,
14 | DAPR_HOST,
15 | DAPR_HTTP_PORT,
16 | CommunicationProtocolEnum.HTTP);
17 |
18 | console.log(`Listening for subscriptions on ${SERVER_HOST}:${SERVER_PORT}. Pubsub name: '${PUBSUB_NAME}'. Topic: '${PUBSUB_TOPIC}'.`)
19 |
20 | await server.pubsub.subscribe(PUBSUB_NAME, PUBSUB_TOPIC, async (data) => {
21 | console.log("Notification received: " + JSON.stringify(data));
22 | });
23 |
24 | await server.start();
25 | };
26 |
27 | main().catch(e => console.error(e));
--------------------------------------------------------------------------------
/samples/order-processing-service/notification-service/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notification-service",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "start": "ts-node app.ts",
7 | "start:dapr": "dapr run --app-id notifications --app-protocol http --dapr-http-port 3502 --app-port 5002 --components-path ../components -- ts-node app.ts"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "devDependencies": {
13 | "typescript": "^4.7.4"
14 | },
15 | "dependencies": {
16 | "dapr-client": "^2.3.0",
17 | "express": "^4.17.2"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/samples/order-processing-service/notification-service/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "esModuleInterop": true,
5 | "target": "es6",
6 | "moduleResolution": "node",
7 | "sourceMap": true,
8 | "outDir": "dist"
9 | },
10 | "lib": [
11 | "es2015"
12 | ]
13 | }
--------------------------------------------------------------------------------
/samples/order-processing-service/payments-service/README.md:
--------------------------------------------------------------------------------
1 | ## Overview
2 |
3 | This is a Node.js app that represents the payments service. It's called by the order processing workflow when completing the order process. The workflow uses the Dapr service invocation building block to invoke this service.
4 |
5 | ## Prerequisites
6 |
7 | * [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started)
8 | * [Latest Node.js installed](https://nodejs.org/)
9 | * [ts-node](https://www.npmjs.com/package/ts-node)
10 | * Make sure `npm` and `ts-node` are in the `PATH`
11 |
12 | ## Install dependencies
13 |
14 | This will install TypeScript and express.js.
15 |
16 | ```bash
17 | npm install
18 | ```
19 |
20 | ## Running the service
21 |
22 | This will start up both the Dapr sidecar process and the Node.js app together.
23 |
24 | ```bash
25 | npm run start:dapr
26 | ```
27 |
--------------------------------------------------------------------------------
/samples/order-processing-service/payments-service/app.ts:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 |
3 | const port = 5004;
4 |
5 | const app = express();
6 | app.use(express.json());
7 |
8 | app.post('/process-payment', (req, res) => {
9 | console.log(`Payment received: ${req.body.amount} ${req.body.currency}`)
10 | res.send({'message':'payment received!'});
11 | });
12 |
13 | app.listen(port, () => {
14 | return console.log(`Payment Service is listening at http://localhost:${port}`);
15 | });
--------------------------------------------------------------------------------
/samples/order-processing-service/payments-service/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "payments-service",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "start": "ts-node app.ts",
7 | "start:dapr": "dapr run --app-id payments --app-protocol http --dapr-http-port 3504 --app-port 5004 --components-path ../components -- ts-node app.ts"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "devDependencies": {
13 | "@types/express": "^4.17.1",
14 | "typescript": "^4.7.4"
15 | },
16 | "dependencies": {
17 | "express": "^4.17.1"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/samples/order-processing-service/payments-service/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "esModuleInterop": true,
5 | "target": "es6",
6 | "moduleResolution": "node",
7 | "sourceMap": true,
8 | "outDir": "dist"
9 | },
10 | "lib": ["es2015"]
11 | }
--------------------------------------------------------------------------------
/samples/order-processing-service/web-api/OrderingWebApi.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/samples/order-processing-service/web-api/Program.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System.Text.Json.Serialization;
5 | using Dapr.Workflow;
6 | using Microsoft.AspNetCore.Mvc;
7 | using JsonOptions = Microsoft.AspNetCore.Http.Json.JsonOptions;
8 |
9 | // The workflow host is a background service that connects to the sidecar over gRPC
10 | WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
11 |
12 | // Add workflows. NOTE: This could alternatively be placed in another project.
13 | builder.Services.AddWorkflow(options =>
14 | {
15 | // Inline order processing workflow logic
16 | options.RegisterWorkflow("ProcessOrder", implementation: async (context, input) =>
17 | {
18 | ArgumentNullException.ThrowIfNull(input, nameof(input));
19 |
20 | // Notify order received
21 | context.PublishEvent(
22 | pubSubName: "notifications-pubsub",
23 | topic: "notifications",
24 | payload: $"Received order {context.InstanceId} for {input.Name} at {input.TotalCost:c}");
25 |
26 | // Invoke the inventory service to reserve the specified items
27 | InventoryResult? result = await context.InvokeMethodAsync(
28 | httpMethod: HttpMethod.Post,
29 | appId: "inventory",
30 | methodName: "reserve-inventory",
31 | data: new { item = input.Name, quantity = input.Quantity });
32 |
33 | if (result?.success != true)
34 | {
35 | context.SetCustomStatus($"Insufficient inventory for {input.Name}");
36 | return new OrderResult(Processed: false);
37 | }
38 |
39 | // Orders >= $1,000 require an approval to be received within 24 hours
40 | if (input.TotalCost >= 1000.00)
41 | {
42 | // Notify waiting for approval
43 | TimeSpan approvalTimeout = TimeSpan.FromHours(24);
44 | string message = $"Waiting for approval. Deadline = {context.CurrentUtcDateTime.Add(approvalTimeout):s}";
45 | context.SetCustomStatus(message);
46 | context.PublishEvent(pubSubName: "notifications-pubsub", topic: "notifications", payload: message);
47 |
48 | try
49 | {
50 | // Wait up to 24-hours for an "Approval" event to be delivered to this workflow instance
51 | await context.WaitForExternalEventAsync("Approval", approvalTimeout);
52 | }
53 | catch (TaskCanceledException)
54 | {
55 | // Notify approval deadline expired
56 | context.PublishEvent(
57 | pubSubName: "notifications-pubsub",
58 | topic: "notifications",
59 | payload: $"Approval deadline for order {context.InstanceId} expired!");
60 |
61 | return new OrderResult(Processed: false);
62 | }
63 |
64 | // Notify approval received
65 | message = $"Received approval at {context.CurrentUtcDateTime:s}";
66 | context.SetCustomStatus(message);
67 | context.PublishEvent(pubSubName: "notifications-pubsub", topic: "notifications", payload: message);
68 | }
69 |
70 | // Invoke the payment service
71 | await context.InvokeMethodAsync(
72 | httpMethod: HttpMethod.Post,
73 | appId: "payments",
74 | methodName: "process-payment",
75 | data: new { amount = input.TotalCost, currency = "USD" });
76 |
77 | // Notify order processed successfully
78 | context.PublishEvent(
79 | pubSubName: "notifications-pubsub",
80 | topic: "notifications",
81 | payload: $"Order {context.InstanceId} was processed successfully!");
82 |
83 | return new OrderResult(Processed: true);
84 | });
85 | });
86 |
87 | // Configure JSON options.
88 | builder.Services.Configure(options =>
89 | {
90 | options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
91 | options.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
92 | });
93 |
94 | WebApplication app = builder.Build();
95 |
96 | // POST creates new orders
97 | app.MapPost("/orders", async (WorkflowClient client, LinkGenerator linker, [FromBody] OrderPayload? orderInput) =>
98 | {
99 | if (orderInput == null || orderInput.Name == null)
100 | {
101 | return Results.BadRequest(new
102 | {
103 | message = "Order data was missing from the request",
104 | example = new OrderPayload("Paperclips", 99.95),
105 | });
106 | }
107 |
108 | // TODO: Add some content
109 | string orderId = Guid.NewGuid().ToString()[..8];
110 | await client.ScheduleNewWorkflowAsync("ProcessOrder", orderId, orderInput);
111 | return Results.AcceptedAtRoute("GetOrder", new { orderId }, new
112 | {
113 | id = orderId,
114 | orderInput,
115 | });
116 | }).WithName("CreateOrder");
117 |
118 | // GET returns the status of existing orders
119 | app.MapGet("/orders/{orderId}", async (string orderId, WorkflowClient client) =>
120 | {
121 | WorkflowMetadata metadata = await client.GetWorkflowMetadata(orderId, getInputsAndOutputs: true);
122 | if (metadata.Exists)
123 | {
124 | return Results.Ok(metadata);
125 | }
126 | else
127 | {
128 | return Results.NotFound($"No order with ID = '{orderId}' was found.");
129 | }
130 | }).WithName("GetOrder");
131 |
132 | // POST to submit a manual approval to the order workflow
133 | app.MapPost("/orders/{orderId}/approve", async (string orderId, WorkflowClient client) =>
134 | {
135 | WorkflowMetadata metadata = await client.GetWorkflowMetadata(orderId, getInputsAndOutputs: true);
136 | if (!metadata.Exists)
137 | {
138 | return Results.NotFound($"No order with ID = '{orderId}' was found.");
139 | }
140 | else if (!metadata.IsWorkflowRunning)
141 | {
142 | return Results.BadRequest($"This order has already completed processing.");
143 | }
144 |
145 | // Raise the approval event to the running workflow instance
146 | await client.RaiseEventAsync(orderId, "Approval");
147 |
148 | return Results.Accepted();
149 | }).WithName("ApproveOrder");
150 |
151 | // Start the web server
152 | app.Run("http://0.0.0.0:8080");
153 |
154 | record OrderPayload(string Name, double TotalCost, int Quantity = 1);
155 | record OrderResult(bool Processed);
156 | record InventoryResult(bool success);
157 |
--------------------------------------------------------------------------------
/samples/order-processing-service/web-api/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "OrderingWebApi": {
4 | "commandName": "Project",
5 | "launchBrowser": true,
6 | "environmentVariables": {
7 | "ASPNETCORE_ENVIRONMENT": "Development"
8 | },
9 | "applicationUrl": "https://localhost:57899;http://localhost:57900"
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/samples/order-processing-service/web-api/README.md:
--------------------------------------------------------------------------------
1 | ## Prerequisites
2 |
3 | * [.NET 6 SDK](https://dotnet.microsoft.com/download/dotnet/6.0)
4 | * [Docker Desktop](https://www.docker.com/products/docker-desktop)
5 |
6 | ## Building and running
7 |
8 | Use the following commands to build the project.
9 |
10 | ```bash
11 | dotnet build
12 | ```
13 |
14 | You can run the project with the following command:
15 |
16 | ```bash
17 | dotnet run
18 | ```
19 |
20 | ## Durable Task Sidecar with Dapr Workflow support
21 |
22 | This starts the Durable Task sidecar which is preconfigured with the latest DurableTask.Dapr backend.
23 | This process is where the workflow actor code lives, and interacts directly with the Dapr sidecar and assumes
24 | that the gRPC endpoint is on port 50001.
25 | The code in the web-api project will connect to this sidecar on port 4001.
26 |
27 | ```bash
28 | docker run --name durabletask-sidecar-dapr -p 4001:4001 -d cgillum/durabletask-sidecar:0.3.3-dapr --backend Dapr
29 | ```
30 |
31 | ## Dapr CLI
32 |
33 | This starts the Dapr sidecar in standalone mode. It does *not* attempt to start the web API / workflow app.
34 |
35 | ```bash
36 | dapr run --app-id web-api --app-port 5000 --dapr-http-port 3500 --dapr-grpc-port 50001 --components-path ../components
37 | ```
38 |
39 | If you want to run the Dapr sidecar together with the app, run the following command instead:
40 |
41 | ```bash
42 | dapr run --app-id web-api --app-port 5000 --dapr-http-port 3500 --dapr-grpc-port 50001 --components-path ../components -- dotnet run
43 | ```
44 |
45 | ## Examples
46 |
47 | See the [demo.http](../demo.http) file for several examples of starting workflow by invoking the HTTP APIs.
48 | It works best if used with the [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) VS Code extension.
49 |
50 | For example, a purchase order workflow can be started with the following HTTP request:
51 |
52 | ```http
53 | POST http://localhost:8080/orders
54 | Content-Type: application/json
55 |
56 | { "name": "catfood", "quantity": 3, "totalCost": 19.99 }
57 | ```
58 |
59 | The response will contain a `Location` header that looks something like `http://localhost:8080/orders/XYZ`, where `XYZ` is a randomly generated order ID.
60 | Follow this URL to get the workflow status as a JSON response.
61 |
62 | If the workflow requires approval, you can do so by sending a POST request to `http://localhost:8080/orders/XYZ/approve` where `XYZ` is the order ID.
63 |
--------------------------------------------------------------------------------
/src/Dapr.Workflow/Dapr.Workflow.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 | Dapr.Workflow
12 | Dapr Workflow Client SDK
13 | Dapr Workflow client SDK for building workflows as code with Dapr
14 | true
15 | MIT
16 | $(RepositoryUrl)
17 | true
18 | true
19 | https://github.com/cgillum/durabletask-dapr
20 | 0.1.0
21 | alpha
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/Dapr.Workflow/InvokeServiceMethodActivity.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System.Text.Json;
5 | using Dapr.Client;
6 | using Microsoft.DurableTask;
7 | using Newtonsoft.Json;
8 |
9 | namespace Dapr.Workflow;
10 |
11 | [DurableTask("DaprInvoke")]
12 | class InvokeServiceMethodActivity : TaskActivityBase
13 | {
14 | readonly DaprClient daprClient;
15 |
16 | public InvokeServiceMethodActivity(DaprClient daprClient)
17 | {
18 | this.daprClient = daprClient ?? throw new ArgumentNullException(nameof(daprClient));
19 | }
20 |
21 | protected override async Task OnRunAsync(TaskActivityContext context, InvokeArgs? input)
22 | {
23 | ArgumentNullException.ThrowIfNull(input, nameof(input));
24 |
25 | HttpRequestMessage httpRequest = this.daprClient.CreateInvokeMethodRequest(
26 | input.HttpMethod,
27 | input.AppId,
28 | input.MethodName,
29 | input.Data);
30 |
31 | try
32 | {
33 | // TODO: Use HttpClient instead of DaprClient for service invocation.
34 | // See discussion in https://github.com/dapr/dotnet-sdk/issues/907
35 | JsonElement result = await this.daprClient.InvokeMethodAsync(httpRequest);
36 | return result;
37 | }
38 | catch (InvocationException e) when (e.InnerException is JsonReaderException)
39 | {
40 | // TODO: Need a more well-defined mechanism for handling deserialization issues.
41 | return new JsonElement();
42 | }
43 | }
44 | }
45 |
46 | public record InvokeArgs(HttpMethod HttpMethod, string AppId, string MethodName, object Data);
47 |
--------------------------------------------------------------------------------
/src/Dapr.Workflow/WorkflowClient.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Microsoft.DurableTask;
5 | using Microsoft.DurableTask.Grpc;
6 |
7 | namespace Dapr.Workflow;
8 |
9 | public sealed class WorkflowClient : IAsyncDisposable
10 | {
11 | // IMPORTANT: This client is only designed to work with the built-in engine.
12 | // The implementation will need to be updated to use the Dapr
13 | // workflow building block APIs instead so that it can be used
14 | // with alternate workflow components.
15 | readonly DurableTaskClient innerClient;
16 |
17 | internal WorkflowClient(IServiceProvider? services = null)
18 | {
19 | DurableTaskGrpcClient.Builder builder = new();
20 | if (services != null)
21 | {
22 | builder.UseServices(services);
23 | }
24 |
25 | this.innerClient = builder.Build();
26 | }
27 |
28 | public Task ScheduleNewWorkflowAsync(
29 | string name,
30 | string? instanceId = null,
31 | object? input = null,
32 | DateTime? startTime = null)
33 | {
34 | return this.innerClient.ScheduleNewOrchestrationInstanceAsync(name, instanceId, input, startTime);
35 | }
36 |
37 | public async Task GetWorkflowMetadata(string instanceId, bool getInputsAndOutputs = false)
38 | {
39 | OrchestrationMetadata? metadata = await this.innerClient.GetInstanceMetadataAsync(
40 | instanceId,
41 | getInputsAndOutputs);
42 | return new WorkflowMetadata(metadata);
43 | }
44 |
45 | public Task RaiseEventAsync(string instanceId, string eventName, object? eventData = null)
46 | {
47 | return this.innerClient.RaiseEventAsync(instanceId, eventName, eventData);
48 | }
49 |
50 | public Task TerminateWorkflowAsync(string instanceId, string reason)
51 | {
52 | return this.innerClient.TerminateAsync(instanceId, reason);
53 | }
54 |
55 | public ValueTask DisposeAsync()
56 | {
57 | return ((IAsyncDisposable)this.innerClient).DisposeAsync();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Dapr.Workflow/WorkflowContext.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System.Text.Json;
5 | using Microsoft.DurableTask;
6 |
7 | namespace Dapr.Workflow;
8 |
9 | public class WorkflowContext
10 | {
11 | readonly TaskOrchestrationContext innerContext;
12 |
13 | internal WorkflowContext(TaskOrchestrationContext innerContext)
14 | {
15 | this.innerContext = innerContext ?? throw new ArgumentNullException(nameof(innerContext));
16 | }
17 |
18 | // TODO: Expose a "WorkflowId" type, similar to "ActorId"
19 | public string InstanceId => this.innerContext.InstanceId;
20 |
21 | public DateTime CurrentUtcDateTime => this.innerContext.CurrentUtcDateTime;
22 |
23 | public void SetCustomStatus(object? customStatus) => this.innerContext.SetCustomStatus(customStatus);
24 |
25 | // TODO: Should we keep the CreateTimer name to be consistent with the underlying SDK or nah?
26 | public Task DelayAsync(TimeSpan delay, CancellationToken cancellationToken = default)
27 | {
28 | return this.innerContext.CreateTimer(delay, cancellationToken);
29 | }
30 |
31 | public Task WaitForExternalEventAsync(string eventName, TimeSpan timeout)
32 | {
33 | return this.innerContext.WaitForExternalEvent