├── .gitattributes
├── .gitignore
├── FakeSource
├── FakeSource.cs
├── FakeSource.csproj
├── PackageRoot
│ ├── Config
│ │ └── Settings.xml
│ └── ServiceManifest.xml
├── Program.cs
└── ServiceEventSource.cs
├── IdempotencyTools
├── IdempotencyFilter.cs
├── IdempotencyTools.csproj
└── IdempotentMessage.cs
├── Interactions
├── ILogStore.cs
├── Interactions.csproj
└── PurchaseInfo.cs
├── LogStore
├── ComputeStatistics.cs
├── LogStore.cs
├── LogStore.csproj
├── PackageRoot
│ ├── Config
│ │ └── Settings.xml
│ └── ServiceManifest.xml
├── Program.cs
├── RunningTotal.cs
└── ServiceEventSource.cs
├── PurchaseLogging.sln
└── PurchaseLogging
├── ApplicationPackageRoot
└── ApplicationManifest.xml
├── ApplicationParameters
├── Cloud.xml
├── Local.1Node.xml
└── Local.5Node.xml
├── PublishProfiles
├── Cloud.xml
├── Local.1Node.xml
└── Local.5Node.xml
├── PurchaseLogging.sfproj
├── Scripts
└── Deploy-FabricApplication.ps1
└── packages.config
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.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 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
--------------------------------------------------------------------------------
/FakeSource/FakeSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Fabric;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using IdempotencyTools;
7 | using Interactions;
8 | using Microsoft.ServiceFabric.Services.Client;
9 | using Microsoft.ServiceFabric.Services.Communication.Runtime;
10 | using Microsoft.ServiceFabric.Services.Remoting.Client;
11 | using Microsoft.ServiceFabric.Services.Runtime;
12 |
13 | namespace FakeSource
14 | {
15 | ///
16 | /// An instance of this class is created for each service instance by the Service Fabric runtime.
17 | ///
18 | internal sealed class FakeSource : StatelessService
19 | {
20 | private string[] locations = new string[] { "Florence", "London", "New York", "Paris" };
21 |
22 | public FakeSource(StatelessServiceContext context)
23 | : base(context)
24 | {
25 | }
26 |
27 | ///
28 | /// Optional override to create listeners (e.g., TCP, HTTP) for this service replica to handle client or user requests.
29 | ///
30 | /// A collection of listeners.
31 | protected override IEnumerable CreateServiceInstanceListeners()
32 | {
33 | return new ServiceInstanceListener[0];
34 | }
35 |
36 | ///
37 | /// This is the main entry point for your service instance.
38 | ///
39 | /// Canceled when Service Fabric needs to shut down this service instance.
40 | protected override async Task RunAsync(CancellationToken cancellationToken)
41 | {
42 | long iterations = 0;
43 | var random = new Random();
44 | while (true)
45 | {
46 | cancellationToken.ThrowIfCancellationRequested();
47 |
48 | var message = new PurchaseInfo
49 | {
50 | Time = DateTimeOffset.UtcNow,
51 | Location = locations[random.Next(0, locations.Length)],
52 | Cost = 200m * random.Next(1, 4)
53 | };
54 | var partition = new ServicePartitionKey(Math.Abs(message.Location.GetHashCode()) % 1000);
55 | var client = ServiceProxy.Create(
56 | new Uri("fabric:/PurchaseLogging/LogStore"), partition);
57 | try
58 | {
59 | while (!await client.LogPurchase(new IdempotentMessage(message)))
60 | await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
61 | }
62 | catch
63 | {
64 | }
65 |
66 | ServiceEventSource.Current.ServiceMessage(Context, "Working-{0}", ++iterations);
67 |
68 | await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
69 | }
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/FakeSource/FakeSource.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net5.0
6 | True
7 | True
8 | win7-x64
9 | False
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/FakeSource/PackageRoot/Config/Settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
11 |
--------------------------------------------------------------------------------
/FakeSource/PackageRoot/ServiceManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | FakeSource.exe
19 |
20 |
21 |
22 |
23 |
25 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/FakeSource/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Threading;
4 | using Microsoft.ServiceFabric.Services.Runtime;
5 |
6 | namespace FakeSource
7 | {
8 | internal static class Program
9 | {
10 | ///
11 | /// This is the entry point of the service host process.
12 | ///
13 | private static void Main()
14 | {
15 | try
16 | {
17 | // The ServiceManifest.XML file defines one or more service type names.
18 | // Registering a service maps a service type name to a .NET type.
19 | // When Service Fabric creates an instance of this service type,
20 | // an instance of the class is created in this host process.
21 |
22 | ServiceRuntime.RegisterServiceAsync("FakeSourceType",
23 | context => new FakeSource(context)).GetAwaiter().GetResult();
24 |
25 | ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id,
26 | nameof(FakeSource));
27 |
28 | // Prevents this host process from terminating so services keep running.
29 | Thread.Sleep(Timeout.Infinite);
30 | }
31 | catch (Exception e)
32 | {
33 | ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString());
34 | throw;
35 | }
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/FakeSource/ServiceEventSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics.Tracing;
3 | using System.Fabric;
4 |
5 | namespace FakeSource
6 | {
7 | [EventSource(Name = "MyCompany-PurchaseLogging-FakeSource")]
8 | internal sealed class ServiceEventSource : EventSource
9 | {
10 | public static readonly ServiceEventSource Current = new();
11 |
12 | // Instance constructor is private to enforce singleton semantics
13 | private ServiceEventSource() : base()
14 | {
15 | }
16 |
17 | #region Keywords
18 |
19 | // Event keywords can be used to categorize events.
20 | // Each keyword is a bit flag. A single event can be associated with multiple keywords (via EventAttribute.Keywords property).
21 | // Keywords must be defined as a public class named 'Keywords' inside EventSource that uses them.
22 | public static class Keywords
23 | {
24 | public const EventKeywords Requests = (EventKeywords)0x1L;
25 | public const EventKeywords ServiceInitialization = (EventKeywords)0x2L;
26 | }
27 |
28 | #endregion
29 |
30 | #region Events
31 |
32 | // Define an instance method for each event you want to record and apply an [Event] attribute to it.
33 | // The method name is the name of the event.
34 | // Pass any parameters you want to record with the event (only primitive integer types, DateTime, Guid & string are allowed).
35 | // Each event method implementation should check whether the event source is enabled, and if it is, call WriteEvent() method to raise the event.
36 | // The number and types of arguments passed to every event method must exactly match what is passed to WriteEvent().
37 | // Put [NonEvent] attribute on all methods that do not define an event.
38 | // For more information see https://msdn.microsoft.com/en-us/library/system.diagnostics.tracing.eventsource.aspx
39 |
40 | [NonEvent]
41 | public void Message(string message, params object[] args)
42 | {
43 | if (IsEnabled())
44 | {
45 | var finalMessage = string.Format(message, args);
46 | Message(finalMessage);
47 | }
48 | }
49 |
50 | private const int MessageEventId = 1;
51 |
52 | [Event(MessageEventId, Level = EventLevel.Informational, Message = "{0}")]
53 | public void Message(string message)
54 | {
55 | if (IsEnabled()) WriteEvent(MessageEventId, message);
56 | }
57 |
58 | [NonEvent]
59 | public void ServiceMessage(StatelessServiceContext serviceContext, string message, params object[] args)
60 | {
61 | if (IsEnabled())
62 | {
63 | var finalMessage = string.Format(message, args);
64 | ServiceMessage(
65 | serviceContext.ServiceName.ToString(),
66 | serviceContext.ServiceTypeName,
67 | serviceContext.InstanceId,
68 | serviceContext.PartitionId,
69 | serviceContext.CodePackageActivationContext.ApplicationName,
70 | serviceContext.CodePackageActivationContext.ApplicationTypeName,
71 | serviceContext.NodeContext.NodeName,
72 | finalMessage);
73 | }
74 | }
75 |
76 | // For very high-frequency events it might be advantageous to raise events using WriteEventCore API.
77 | // This results in more efficient parameter handling, but requires explicit allocation of EventData structure and unsafe code.
78 | // To enable this code path, define UNSAFE conditional compilation symbol and turn on unsafe code support in project properties.
79 | private const int ServiceMessageEventId = 2;
80 |
81 | [Event(ServiceMessageEventId, Level = EventLevel.Informational, Message = "{7}")]
82 | private
83 | #if UNSAFE
84 | unsafe
85 | #endif
86 | void ServiceMessage(
87 | string serviceName,
88 | string serviceTypeName,
89 | long replicaOrInstanceId,
90 | Guid partitionId,
91 | string applicationName,
92 | string applicationTypeName,
93 | string nodeName,
94 | string message)
95 | {
96 | #if !UNSAFE
97 | WriteEvent(ServiceMessageEventId, serviceName, serviceTypeName, replicaOrInstanceId, partitionId,
98 | applicationName, applicationTypeName, nodeName, message);
99 | #else
100 | const int numArgs = 8;
101 | fixed (char* pServiceName = serviceName, pServiceTypeName = serviceTypeName, pApplicationName =
102 | applicationName, pApplicationTypeName = applicationTypeName, pNodeName = nodeName, pMessage = message)
103 | {
104 | EventData* eventData = stackalloc EventData[numArgs];
105 | eventData[0] = new EventData { DataPointer = (IntPtr) pServiceName, Size = SizeInBytes(serviceName) };
106 | eventData[1] = new EventData { DataPointer = (IntPtr) pServiceTypeName, Size =
107 | SizeInBytes(serviceTypeName) };
108 | eventData[2] = new EventData { DataPointer = (IntPtr) (&replicaOrInstanceId), Size = sizeof(long) };
109 | eventData[3] = new EventData { DataPointer = (IntPtr) (&partitionId), Size = sizeof(Guid) };
110 | eventData[4] = new EventData { DataPointer = (IntPtr) pApplicationName, Size =
111 | SizeInBytes(applicationName) };
112 | eventData[5] = new EventData { DataPointer = (IntPtr) pApplicationTypeName, Size =
113 | SizeInBytes(applicationTypeName) };
114 | eventData[6] = new EventData { DataPointer = (IntPtr) pNodeName, Size = SizeInBytes(nodeName) };
115 | eventData[7] = new EventData { DataPointer = (IntPtr) pMessage, Size = SizeInBytes(message) };
116 |
117 | WriteEventCore(ServiceMessageEventId, numArgs, eventData);
118 | }
119 | #endif
120 | }
121 |
122 | private const int ServiceTypeRegisteredEventId = 3;
123 |
124 | [Event(ServiceTypeRegisteredEventId, Level = EventLevel.Informational,
125 | Message = "Service host process {0} registered service type {1}",
126 | Keywords = Keywords.ServiceInitialization)]
127 | public void ServiceTypeRegistered(int hostProcessId, string serviceType)
128 | {
129 | WriteEvent(ServiceTypeRegisteredEventId, hostProcessId, serviceType);
130 | }
131 |
132 | private const int ServiceHostInitializationFailedEventId = 4;
133 |
134 | [Event(ServiceHostInitializationFailedEventId, Level = EventLevel.Error,
135 | Message = "Service host initialization failed", Keywords = Keywords.ServiceInitialization)]
136 | public void ServiceHostInitializationFailed(string exception)
137 | {
138 | WriteEvent(ServiceHostInitializationFailedEventId, exception);
139 | }
140 |
141 | // A pair of events sharing the same name prefix with a "Start"/"Stop" suffix implicitly marks boundaries of an event tracing activity.
142 | // These activities can be automatically picked up by debugging and profiling tools, which can compute their execution time, child activities,
143 | // and other statistics.
144 | private const int ServiceRequestStartEventId = 5;
145 |
146 | [Event(ServiceRequestStartEventId, Level = EventLevel.Informational, Message = "Service request '{0}' started",
147 | Keywords = Keywords.Requests)]
148 | public void ServiceRequestStart(string requestTypeName)
149 | {
150 | WriteEvent(ServiceRequestStartEventId, requestTypeName);
151 | }
152 |
153 | private const int ServiceRequestStopEventId = 6;
154 |
155 | [Event(ServiceRequestStopEventId, Level = EventLevel.Informational, Message = "Service request '{0}' finished",
156 | Keywords = Keywords.Requests)]
157 | public void ServiceRequestStop(string requestTypeName, string exception = "")
158 | {
159 | WriteEvent(ServiceRequestStopEventId, requestTypeName, exception);
160 | }
161 |
162 | #endregion
163 |
164 | #region Private methods
165 |
166 | #if UNSAFE
167 | private int SizeInBytes(string s)
168 | {
169 | if (s == null)
170 | {
171 | return 0;
172 | }
173 | else
174 | {
175 | return (s.Length + 1) * sizeof(char);
176 | }
177 | }
178 | #endif
179 |
180 | #endregion
181 | }
182 | }
--------------------------------------------------------------------------------
/IdempotencyTools/IdempotencyFilter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Microsoft.ServiceFabric.Data;
6 | using Microsoft.ServiceFabric.Data.Collections;
7 |
8 | namespace IdempotencyTools
9 | {
10 | public class IdempotencyFilter
11 | {
12 | private IReliableDictionary dictionary;
13 | private DateTimeOffset lastClear;
14 | private int maxDelaySeconds;
15 | private IReliableStateManager sm;
16 |
17 | private IdempotencyFilter()
18 | {
19 | }
20 |
21 | public static async Task NewIdempotencyFilterAsync(
22 | string name,
23 | int maxDelaySeconds,
24 | IReliableStateManager sm)
25 | {
26 | return new IdempotencyFilter()
27 | {
28 | dictionary = await sm.GetOrAddAsync>(name),
29 | maxDelaySeconds = maxDelaySeconds,
30 | lastClear = DateTimeOffset.UtcNow,
31 | sm = sm
32 | };
33 | }
34 |
35 | public async Task NewMessage(IdempotentMessage message)
36 | {
37 | var now = DateTimeOffset.Now;
38 | if ((now - lastClear).TotalSeconds > 1.5 * maxDelaySeconds) await Clear();
39 | if ((now - message.Time).TotalSeconds > maxDelaySeconds)
40 | return default;
41 | using (var tx = sm.CreateTransaction())
42 | {
43 | if (await dictionary.TryAddAsync(tx, message.Id, message.Time))
44 | {
45 | await tx.CommitAsync();
46 | return message.Value;
47 | }
48 | else
49 | {
50 | return default;
51 | }
52 | }
53 | }
54 |
55 | public async Task Clear()
56 | {
57 | var now = DateTimeOffset.Now;
58 | var toKeep = new List>();
59 | using (var tx = sm.CreateTransaction())
60 | {
61 | var asyncEnumerable = await dictionary.CreateEnumerableAsync(tx);
62 | using (var asyncEnumerator = asyncEnumerable.GetAsyncEnumerator())
63 | {
64 | while (await asyncEnumerator.MoveNextAsync(CancellationToken.None))
65 | if ((now - asyncEnumerator.Current.Value).TotalSeconds < maxDelaySeconds)
66 | toKeep.Add(asyncEnumerator.Current);
67 | }
68 |
69 | await dictionary.ClearAsync();
70 | foreach (var pair in toKeep)
71 | await dictionary.TryAddAsync(tx, pair.Key, pair.Value);
72 | await tx.CommitAsync();
73 | lastClear = DateTimeOffset.Now;
74 | }
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/IdempotencyTools/IdempotencyTools.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/IdempotencyTools/IdempotentMessage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.Serialization;
3 |
4 | namespace IdempotencyTools
5 | {
6 | [DataContract]
7 | public class IdempotentMessage
8 | {
9 | [DataMember] public T Value { get; protected set; }
10 | [DataMember] public DateTimeOffset Time { get; protected set; }
11 | [DataMember] public Guid Id { get; protected set; }
12 |
13 | public IdempotentMessage(T originalMessage)
14 | {
15 | Value = originalMessage;
16 | Time = DateTimeOffset.Now;
17 | Id = Guid.NewGuid();
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/Interactions/ILogStore.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using IdempotencyTools;
3 | using Microsoft.ServiceFabric.Services.Remoting;
4 |
5 | namespace Interactions
6 | {
7 | public interface ILogStore : IService
8 | {
9 | Task LogPurchase(IdempotentMessage idempotentMessage);
10 | }
11 | }
--------------------------------------------------------------------------------
/Interactions/Interactions.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Interactions/PurchaseInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.Serialization;
3 |
4 | namespace Interactions
5 | {
6 | [DataContract]
7 | public class PurchaseInfo
8 | {
9 | [DataMember] public string Location { get; set; }
10 | [DataMember] public decimal Cost { get; set; }
11 | [DataMember] public DateTimeOffset Time { get; set; }
12 | }
13 | }
--------------------------------------------------------------------------------
/LogStore/ComputeStatistics.cs:
--------------------------------------------------------------------------------
1 | using System.Fabric;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using IdempotencyTools;
5 | using Interactions;
6 | using Microsoft.Extensions.Hosting;
7 | using Microsoft.ServiceFabric.Data;
8 | using Microsoft.ServiceFabric.Data.Collections;
9 |
10 | namespace LogStore
11 | {
12 | public class ComputeStatistics : BackgroundService
13 | {
14 | private ConfigurationPackage configurationPackage;
15 | private IReliableQueue> queue;
16 | private IReliableStateManager stateManager;
17 |
18 | public ComputeStatistics(
19 | IReliableQueue> queue,
20 | IReliableStateManager stateManager,
21 | ConfigurationPackage configurationPackage)
22 | {
23 | this.queue = queue;
24 | this.stateManager = stateManager;
25 | this.configurationPackage = configurationPackage;
26 | }
27 |
28 | protected override async Task ExecuteAsync(CancellationToken stoppingToken)
29 | {
30 | var queueEmpty = false;
31 | var delayString = configurationPackage.Settings.Sections["Timing"]
32 | .Parameters["MessageMaxDelaySeconds"].Value;
33 | var delay = int.Parse(delayString);
34 | var filter = await IdempotencyFilter.NewIdempotencyFilterAsync(
35 | "logMessages", delay, stateManager);
36 | var store = await
37 | stateManager.GetOrAddAsync>("partialCount");
38 | while (!stoppingToken.IsCancellationRequested)
39 | {
40 | while (!queueEmpty && !stoppingToken.IsCancellationRequested)
41 | {
42 | RunningTotal finalDayTotal = null;
43 | using (var tx = stateManager.CreateTransaction())
44 | {
45 | var result = await queue.TryDequeueAsync(tx);
46 | if (!result.HasValue)
47 | {
48 | queueEmpty = true;
49 | }
50 | else
51 | {
52 | var item = await filter.NewMessage(result.Value);
53 | if (item != null)
54 | {
55 | var counter = await store.TryGetValueAsync(tx, item.Location);
56 | var newCounter = counter.HasValue
57 | ? new RunningTotal
58 | {
59 | Count = counter.Value.Count,
60 | Day = counter.Value.Day
61 | }
62 | : new RunningTotal();
63 | finalDayTotal = newCounter.Update(item.Time, item.Cost);
64 | if (counter.HasValue)
65 | await store.TryUpdateAsync(tx, item.Location,
66 | newCounter, counter.Value);
67 | else
68 | await store.TryAddAsync(tx, item.Location, newCounter);
69 | }
70 |
71 | await tx.CommitAsync();
72 | if (finalDayTotal != null) await SendTotal(finalDayTotal, item.Location);
73 | }
74 | }
75 | }
76 |
77 | await Task.Delay(100, stoppingToken);
78 | queueEmpty = false;
79 | }
80 | }
81 |
82 | protected async Task SendTotal(RunningTotal total, string location)
83 | {
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/LogStore/LogStore.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Fabric;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using IdempotencyTools;
6 | using Interactions;
7 | using Microsoft.Extensions.DependencyInjection;
8 | using Microsoft.Extensions.Hosting;
9 | using Microsoft.ServiceFabric.Data.Collections;
10 | using Microsoft.ServiceFabric.Services.Communication.Runtime;
11 | using Microsoft.ServiceFabric.Services.Remoting.Runtime;
12 | using Microsoft.ServiceFabric.Services.Runtime;
13 |
14 | namespace LogStore
15 | {
16 | ///
17 | /// An instance of this class is created for each service replica by the Service Fabric runtime.
18 | ///
19 | internal sealed class LogStore : StatefulService, ILogStore
20 | {
21 | private IReliableQueue> LogQueue;
22 |
23 | public LogStore(StatefulServiceContext context)
24 | : base(context)
25 | {
26 | }
27 |
28 | public async Task LogPurchase(IdempotentMessage idempotentMessage)
29 | {
30 | if (LogQueue == null) return false;
31 | using (var tx = StateManager.CreateTransaction())
32 | {
33 | await LogQueue.EnqueueAsync(tx, idempotentMessage);
34 | await tx.CommitAsync();
35 | return true;
36 | }
37 | }
38 |
39 | ///
40 | /// Optional override to create listeners (e.g., HTTP, Service Remoting, WCF, etc.) for this service replica to handle client or user requests.
41 | ///
42 | ///
43 | /// For more information on service communication, see https://aka.ms/servicefabricservicecommunication
44 | ///
45 | /// A collection of listeners.
46 | protected override IEnumerable CreateServiceReplicaListeners()
47 | {
48 | return this.CreateServiceRemotingReplicaListeners();
49 | }
50 |
51 | ///
52 | /// This is the main entry point for your service replica.
53 | /// This method executes when this replica of your service becomes primary and has write status.
54 | ///
55 | /// Canceled when Service Fabric needs to shut down this service replica.
56 | protected override async Task RunAsync(CancellationToken cancellationToken)
57 | {
58 | cancellationToken.ThrowIfCancellationRequested();
59 | LogQueue = await
60 | StateManager
61 | .GetOrAddAsync>>("logQueue");
62 | var configurationPackage = Context
63 | .CodePackageActivationContext
64 | .GetConfigurationPackageObject("Config");
65 |
66 | var host = new HostBuilder()
67 | .ConfigureServices((hostContext, services) =>
68 | {
69 | services.AddSingleton(StateManager);
70 | services.AddSingleton(LogQueue);
71 | services.AddSingleton(configurationPackage);
72 | services.AddHostedService();
73 | })
74 | .Build();
75 | await host.RunAsync(cancellationToken);
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/LogStore/LogStore.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net5.0
6 | True
7 | True
8 | win7-x64
9 | False
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/LogStore/PackageRoot/Config/Settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/LogStore/PackageRoot/ServiceManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | LogStore.exe
19 |
20 |
21 |
22 |
23 |
25 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/LogStore/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Threading;
4 | using Microsoft.ServiceFabric.Services.Runtime;
5 |
6 | namespace LogStore
7 | {
8 | internal static class Program
9 | {
10 | ///
11 | /// This is the entry point of the service host process.
12 | ///
13 | private static void Main()
14 | {
15 | try
16 | {
17 | // The ServiceManifest.XML file defines one or more service type names.
18 | // Registering a service maps a service type name to a .NET type.
19 | // When Service Fabric creates an instance of this service type,
20 | // an instance of the class is created in this host process.
21 |
22 | ServiceRuntime.RegisterServiceAsync("LogStoreType",
23 | context => new LogStore(context)).GetAwaiter().GetResult();
24 |
25 | ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(LogStore).Name);
26 |
27 | // Prevents this host process from terminating so services keep running.
28 | Thread.Sleep(Timeout.Infinite);
29 | }
30 | catch (Exception e)
31 | {
32 | ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString());
33 | throw;
34 | }
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/LogStore/RunningTotal.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace LogStore
4 | {
5 | public class RunningTotal
6 | {
7 | public DateTime Day { get; set; }
8 | public decimal Count { get; set; }
9 |
10 | public RunningTotal Update(DateTimeOffset time, decimal value)
11 | {
12 | var normalizedTime = time.ToUniversalTime();
13 | var newDay = normalizedTime.Date;
14 |
15 | var result = newDay > Day && Day != DateTime.MinValue
16 | ? new RunningTotal
17 | {
18 | Day = Day,
19 | Count = Count
20 | }
21 | : null;
22 | if (newDay > Day) Day = newDay;
23 | if (result != null) Count = value;
24 | else Count += value;
25 | return result;
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/LogStore/ServiceEventSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics.Tracing;
3 | using System.Fabric;
4 |
5 | namespace LogStore
6 | {
7 | [EventSource(Name = "MyCompany-PurchaseLogging-LogStore")]
8 | internal sealed class ServiceEventSource : EventSource
9 | {
10 | public static readonly ServiceEventSource Current = new();
11 |
12 | // Instance constructor is private to enforce singleton semantics
13 | private ServiceEventSource() : base()
14 | {
15 | }
16 |
17 | #region Keywords
18 |
19 | // Event keywords can be used to categorize events.
20 | // Each keyword is a bit flag. A single event can be associated with multiple keywords (via EventAttribute.Keywords property).
21 | // Keywords must be defined as a public class named 'Keywords' inside EventSource that uses them.
22 | public static class Keywords
23 | {
24 | public const EventKeywords Requests = (EventKeywords)0x1L;
25 | public const EventKeywords ServiceInitialization = (EventKeywords)0x2L;
26 | }
27 |
28 | #endregion
29 |
30 | #region Events
31 |
32 | // Define an instance method for each event you want to record and apply an [Event] attribute to it.
33 | // The method name is the name of the event.
34 | // Pass any parameters you want to record with the event (only primitive integer types, DateTime, Guid & string are allowed).
35 | // Each event method implementation should check whether the event source is enabled, and if it is, call WriteEvent() method to raise the event.
36 | // The number and types of arguments passed to every event method must exactly match what is passed to WriteEvent().
37 | // Put [NonEvent] attribute on all methods that do not define an event.
38 | // For more information see https://msdn.microsoft.com/en-us/library/system.diagnostics.tracing.eventsource.aspx
39 |
40 | [NonEvent]
41 | public void Message(string message, params object[] args)
42 | {
43 | if (IsEnabled())
44 | {
45 | var finalMessage = string.Format(message, args);
46 | Message(finalMessage);
47 | }
48 | }
49 |
50 | private const int MessageEventId = 1;
51 |
52 | [Event(MessageEventId, Level = EventLevel.Informational, Message = "{0}")]
53 | public void Message(string message)
54 | {
55 | if (IsEnabled()) WriteEvent(MessageEventId, message);
56 | }
57 |
58 | [NonEvent]
59 | public void ServiceMessage(StatefulServiceContext serviceContext, string message, params object[] args)
60 | {
61 | if (IsEnabled())
62 | {
63 | var finalMessage = string.Format(message, args);
64 | ServiceMessage(
65 | serviceContext.ServiceName.ToString(),
66 | serviceContext.ServiceTypeName,
67 | serviceContext.ReplicaId,
68 | serviceContext.PartitionId,
69 | serviceContext.CodePackageActivationContext.ApplicationName,
70 | serviceContext.CodePackageActivationContext.ApplicationTypeName,
71 | serviceContext.NodeContext.NodeName,
72 | finalMessage);
73 | }
74 | }
75 |
76 | // For very high-frequency events it might be advantageous to raise events using WriteEventCore API.
77 | // This results in more efficient parameter handling, but requires explicit allocation of EventData structure and unsafe code.
78 | // To enable this code path, define UNSAFE conditional compilation symbol and turn on unsafe code support in project properties.
79 | private const int ServiceMessageEventId = 2;
80 |
81 | [Event(ServiceMessageEventId, Level = EventLevel.Informational, Message = "{7}")]
82 | private
83 | #if UNSAFE
84 | unsafe
85 | #endif
86 | void ServiceMessage(
87 | string serviceName,
88 | string serviceTypeName,
89 | long replicaOrInstanceId,
90 | Guid partitionId,
91 | string applicationName,
92 | string applicationTypeName,
93 | string nodeName,
94 | string message)
95 | {
96 | #if !UNSAFE
97 | WriteEvent(ServiceMessageEventId, serviceName, serviceTypeName, replicaOrInstanceId, partitionId,
98 | applicationName, applicationTypeName, nodeName, message);
99 | #else
100 | const int numArgs = 8;
101 | fixed (char* pServiceName = serviceName, pServiceTypeName = serviceTypeName, pApplicationName =
102 | applicationName, pApplicationTypeName = applicationTypeName, pNodeName = nodeName, pMessage = message)
103 | {
104 | EventData* eventData = stackalloc EventData[numArgs];
105 | eventData[0] = new EventData { DataPointer = (IntPtr) pServiceName, Size = SizeInBytes(serviceName) };
106 | eventData[1] = new EventData { DataPointer = (IntPtr) pServiceTypeName, Size =
107 | SizeInBytes(serviceTypeName) };
108 | eventData[2] = new EventData { DataPointer = (IntPtr) (&replicaOrInstanceId), Size = sizeof(long) };
109 | eventData[3] = new EventData { DataPointer = (IntPtr) (&partitionId), Size = sizeof(Guid) };
110 | eventData[4] = new EventData { DataPointer = (IntPtr) pApplicationName, Size =
111 | SizeInBytes(applicationName) };
112 | eventData[5] = new EventData { DataPointer = (IntPtr) pApplicationTypeName, Size =
113 | SizeInBytes(applicationTypeName) };
114 | eventData[6] = new EventData { DataPointer = (IntPtr) pNodeName, Size = SizeInBytes(nodeName) };
115 | eventData[7] = new EventData { DataPointer = (IntPtr) pMessage, Size = SizeInBytes(message) };
116 |
117 | WriteEventCore(ServiceMessageEventId, numArgs, eventData);
118 | }
119 | #endif
120 | }
121 |
122 | private const int ServiceTypeRegisteredEventId = 3;
123 |
124 | [Event(ServiceTypeRegisteredEventId, Level = EventLevel.Informational,
125 | Message = "Service host process {0} registered service type {1}",
126 | Keywords = Keywords.ServiceInitialization)]
127 | public void ServiceTypeRegistered(int hostProcessId, string serviceType)
128 | {
129 | WriteEvent(ServiceTypeRegisteredEventId, hostProcessId, serviceType);
130 | }
131 |
132 | private const int ServiceHostInitializationFailedEventId = 4;
133 |
134 | [Event(ServiceHostInitializationFailedEventId, Level = EventLevel.Error,
135 | Message = "Service host initialization failed", Keywords = Keywords.ServiceInitialization)]
136 | public void ServiceHostInitializationFailed(string exception)
137 | {
138 | WriteEvent(ServiceHostInitializationFailedEventId, exception);
139 | }
140 |
141 | // A pair of events sharing the same name prefix with a "Start"/"Stop" suffix implicitly marks boundaries of an event tracing activity.
142 | // These activities can be automatically picked up by debugging and profiling tools, which can compute their execution time, child activities,
143 | // and other statistics.
144 | private const int ServiceRequestStartEventId = 5;
145 |
146 | [Event(ServiceRequestStartEventId, Level = EventLevel.Informational, Message = "Service request '{0}' started",
147 | Keywords = Keywords.Requests)]
148 | public void ServiceRequestStart(string requestTypeName)
149 | {
150 | WriteEvent(ServiceRequestStartEventId, requestTypeName);
151 | }
152 |
153 | private const int ServiceRequestStopEventId = 6;
154 |
155 | [Event(ServiceRequestStopEventId, Level = EventLevel.Informational, Message = "Service request '{0}' finished",
156 | Keywords = Keywords.Requests)]
157 | public void ServiceRequestStop(string requestTypeName, string exception = "")
158 | {
159 | WriteEvent(ServiceRequestStopEventId, requestTypeName, exception);
160 | }
161 |
162 | #endregion
163 |
164 | #region Private methods
165 |
166 | #if UNSAFE
167 | private int SizeInBytes(string s)
168 | {
169 | if (s == null)
170 | {
171 | return 0;
172 | }
173 | else
174 | {
175 | return (s.Length + 1) * sizeof(char);
176 | }
177 | }
178 | #endif
179 |
180 | #endregion
181 | }
182 | }
--------------------------------------------------------------------------------
/PurchaseLogging.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29424.173
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{A07B5EB6-E848-4116-A8D0-A826331D98C6}") = "PurchaseLogging", "PurchaseLogging\PurchaseLogging.sfproj", "{984D8300-4561-47B4-9132-C442B32149B5}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LogStore", "LogStore\LogStore.csproj", "{378CD96D-D85B-45BD-B5C5-1E6DF77ABC31}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdempotencyTools", "IdempotencyTools\IdempotencyTools.csproj", "{2F82CAB9-BDCB-4A3C-98A4-0D9108B49852}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Interactions", "Interactions\Interactions.csproj", "{DDD68273-A094-4C5A-9CAF-0BCC7631BE78}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FakeSource", "FakeSource\FakeSource.csproj", "{88F127A8-F8DF-4995-9924-3A945648C21A}"
15 | EndProject
16 | Global
17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 | Debug|Any CPU = Debug|Any CPU
19 | Debug|x64 = Debug|x64
20 | Release|Any CPU = Release|Any CPU
21 | Release|x64 = Release|x64
22 | EndGlobalSection
23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
24 | {984D8300-4561-47B4-9132-C442B32149B5}.Debug|Any CPU.ActiveCfg = Debug|x64
25 | {984D8300-4561-47B4-9132-C442B32149B5}.Debug|x64.ActiveCfg = Debug|x64
26 | {984D8300-4561-47B4-9132-C442B32149B5}.Debug|x64.Build.0 = Debug|x64
27 | {984D8300-4561-47B4-9132-C442B32149B5}.Debug|x64.Deploy.0 = Debug|x64
28 | {984D8300-4561-47B4-9132-C442B32149B5}.Release|Any CPU.ActiveCfg = Release|x64
29 | {984D8300-4561-47B4-9132-C442B32149B5}.Release|x64.ActiveCfg = Release|x64
30 | {984D8300-4561-47B4-9132-C442B32149B5}.Release|x64.Build.0 = Release|x64
31 | {984D8300-4561-47B4-9132-C442B32149B5}.Release|x64.Deploy.0 = Release|x64
32 | {378CD96D-D85B-45BD-B5C5-1E6DF77ABC31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {378CD96D-D85B-45BD-B5C5-1E6DF77ABC31}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {378CD96D-D85B-45BD-B5C5-1E6DF77ABC31}.Debug|x64.ActiveCfg = Debug|Any CPU
35 | {378CD96D-D85B-45BD-B5C5-1E6DF77ABC31}.Debug|x64.Build.0 = Debug|Any CPU
36 | {378CD96D-D85B-45BD-B5C5-1E6DF77ABC31}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {378CD96D-D85B-45BD-B5C5-1E6DF77ABC31}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {378CD96D-D85B-45BD-B5C5-1E6DF77ABC31}.Release|x64.ActiveCfg = Release|Any CPU
39 | {378CD96D-D85B-45BD-B5C5-1E6DF77ABC31}.Release|x64.Build.0 = Release|Any CPU
40 | {2F82CAB9-BDCB-4A3C-98A4-0D9108B49852}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {2F82CAB9-BDCB-4A3C-98A4-0D9108B49852}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {2F82CAB9-BDCB-4A3C-98A4-0D9108B49852}.Debug|x64.ActiveCfg = Debug|Any CPU
43 | {2F82CAB9-BDCB-4A3C-98A4-0D9108B49852}.Debug|x64.Build.0 = Debug|Any CPU
44 | {2F82CAB9-BDCB-4A3C-98A4-0D9108B49852}.Release|Any CPU.ActiveCfg = Release|Any CPU
45 | {2F82CAB9-BDCB-4A3C-98A4-0D9108B49852}.Release|Any CPU.Build.0 = Release|Any CPU
46 | {2F82CAB9-BDCB-4A3C-98A4-0D9108B49852}.Release|x64.ActiveCfg = Release|Any CPU
47 | {2F82CAB9-BDCB-4A3C-98A4-0D9108B49852}.Release|x64.Build.0 = Release|Any CPU
48 | {DDD68273-A094-4C5A-9CAF-0BCC7631BE78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49 | {DDD68273-A094-4C5A-9CAF-0BCC7631BE78}.Debug|Any CPU.Build.0 = Debug|Any CPU
50 | {DDD68273-A094-4C5A-9CAF-0BCC7631BE78}.Debug|x64.ActiveCfg = Debug|Any CPU
51 | {DDD68273-A094-4C5A-9CAF-0BCC7631BE78}.Debug|x64.Build.0 = Debug|Any CPU
52 | {DDD68273-A094-4C5A-9CAF-0BCC7631BE78}.Release|Any CPU.ActiveCfg = Release|Any CPU
53 | {DDD68273-A094-4C5A-9CAF-0BCC7631BE78}.Release|Any CPU.Build.0 = Release|Any CPU
54 | {DDD68273-A094-4C5A-9CAF-0BCC7631BE78}.Release|x64.ActiveCfg = Release|Any CPU
55 | {DDD68273-A094-4C5A-9CAF-0BCC7631BE78}.Release|x64.Build.0 = Release|Any CPU
56 | {88F127A8-F8DF-4995-9924-3A945648C21A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
57 | {88F127A8-F8DF-4995-9924-3A945648C21A}.Debug|Any CPU.Build.0 = Debug|Any CPU
58 | {88F127A8-F8DF-4995-9924-3A945648C21A}.Debug|x64.ActiveCfg = Debug|Any CPU
59 | {88F127A8-F8DF-4995-9924-3A945648C21A}.Debug|x64.Build.0 = Debug|Any CPU
60 | {88F127A8-F8DF-4995-9924-3A945648C21A}.Release|Any CPU.ActiveCfg = Release|Any CPU
61 | {88F127A8-F8DF-4995-9924-3A945648C21A}.Release|Any CPU.Build.0 = Release|Any CPU
62 | {88F127A8-F8DF-4995-9924-3A945648C21A}.Release|x64.ActiveCfg = Release|Any CPU
63 | {88F127A8-F8DF-4995-9924-3A945648C21A}.Release|x64.Build.0 = Release|Any CPU
64 | EndGlobalSection
65 | GlobalSection(SolutionProperties) = preSolution
66 | HideSolutionNode = FALSE
67 | EndGlobalSection
68 | GlobalSection(ExtensibilityGlobals) = postSolution
69 | SolutionGuid = {925409AF-D623-4054-894E-E101A2C341E9}
70 | EndGlobalSection
71 | EndGlobal
72 |
--------------------------------------------------------------------------------
/PurchaseLogging/ApplicationPackageRoot/ApplicationManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/PurchaseLogging/ApplicationParameters/Cloud.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/PurchaseLogging/ApplicationParameters/Local.1Node.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/PurchaseLogging/ApplicationParameters/Local.5Node.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/PurchaseLogging/PublishProfiles/Cloud.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/PurchaseLogging/PublishProfiles/Local.1Node.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/PurchaseLogging/PublishProfiles/Local.5Node.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/PurchaseLogging/PurchaseLogging.sfproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 984d8300-4561-47b4-9132-c442b32149b5
6 | 2.5
7 | 1.5
8 | 1.6.9
9 | v4.7.2
10 |
11 |
12 |
13 | Debug
14 | x64
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Service Fabric Tools\Microsoft.VisualStudio.Azure.Fabric.ApplicationProject.targets
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/PurchaseLogging/Scripts/Deploy-FabricApplication.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Deploys a Service Fabric application type to a cluster.
4 |
5 | .DESCRIPTION
6 | This script deploys a Service Fabric application type to a cluster. It is invoked by Visual Studio when deploying a Service Fabric Application project.
7 |
8 | .NOTES
9 | WARNING: This script file is invoked by Visual Studio. Its parameters must not be altered but its logic can be customized as necessary.
10 |
11 | .PARAMETER PublishProfileFile
12 | Path to the file containing the publish profile.
13 |
14 | .PARAMETER ApplicationPackagePath
15 | Path to the folder of the packaged Service Fabric application.
16 |
17 | .PARAMETER DeployOnly
18 | Indicates that the Service Fabric application should not be created or upgraded after registering the application type.
19 |
20 | .PARAMETER ApplicationParameter
21 | Hashtable of the Service Fabric application parameters to be used for the application.
22 |
23 | .PARAMETER UnregisterUnusedApplicationVersionsAfterUpgrade
24 | Indicates whether to unregister any unused application versions that exist after an upgrade is finished.
25 |
26 | .PARAMETER OverrideUpgradeBehavior
27 | Indicates the behavior used to override the upgrade settings specified by the publish profile.
28 | 'None' indicates that the upgrade settings will not be overridden.
29 | 'ForceUpgrade' indicates that an upgrade will occur with default settings, regardless of what is specified in the publish profile.
30 | 'VetoUpgrade' indicates that an upgrade will not occur, regardless of what is specified in the publish profile.
31 |
32 | .PARAMETER UseExistingClusterConnection
33 | Indicates that the script should make use of an existing cluster connection that has already been established in the PowerShell session. The cluster connection parameters configured in the publish profile are ignored.
34 |
35 | .PARAMETER OverwriteBehavior
36 | Overwrite Behavior if an application exists in the cluster with the same name. Available Options are Never, Always, SameAppTypeAndVersion. This setting is not applicable when upgrading an application.
37 | 'Never' will not remove the existing application. This is the default behavior.
38 | 'Always' will remove the existing application even if its Application type and Version is different from the application being created.
39 | 'SameAppTypeAndVersion' will remove the existing application only if its Application type and Version is same as the application being created.
40 |
41 | .PARAMETER SkipPackageValidation
42 | Switch signaling whether the package should be validated or not before deployment.
43 |
44 | .PARAMETER SecurityToken
45 | A security token for authentication to cluster management endpoints. Used for silent authentication to clusters that are protected by Azure Active Directory.
46 |
47 | .PARAMETER CopyPackageTimeoutSec
48 | Timeout in seconds for copying application package to image store.
49 |
50 | .EXAMPLE
51 | . Scripts\Deploy-FabricApplication.ps1 -ApplicationPackagePath 'pkg\Debug'
52 |
53 | Deploy the application using the default package location for a Debug build.
54 |
55 | .EXAMPLE
56 | . Scripts\Deploy-FabricApplication.ps1 -ApplicationPackagePath 'pkg\Debug' -DoNotCreateApplication
57 |
58 | Deploy the application but do not create the application instance.
59 |
60 | .EXAMPLE
61 | . Scripts\Deploy-FabricApplication.ps1 -ApplicationPackagePath 'pkg\Debug' -ApplicationParameter @{CustomParameter1='MyValue'; CustomParameter2='MyValue'}
62 |
63 | Deploy the application by providing values for parameters that are defined in the application manifest.
64 | #>
65 |
66 | Param
67 | (
68 | [String]
69 | $PublishProfileFile,
70 |
71 | [String]
72 | $ApplicationPackagePath,
73 |
74 | [Switch]
75 | $DeployOnly,
76 |
77 | [Hashtable]
78 | $ApplicationParameter,
79 |
80 | [Boolean]
81 | $UnregisterUnusedApplicationVersionsAfterUpgrade,
82 |
83 | [String]
84 | [ValidateSet('None', 'ForceUpgrade', 'VetoUpgrade')]
85 | $OverrideUpgradeBehavior = 'None',
86 |
87 | [Switch]
88 | $UseExistingClusterConnection,
89 |
90 | [String]
91 | [ValidateSet('Never','Always','SameAppTypeAndVersion')]
92 | $OverwriteBehavior = 'Never',
93 |
94 | [Switch]
95 | $SkipPackageValidation,
96 |
97 | [String]
98 | $SecurityToken,
99 |
100 | [int]
101 | $CopyPackageTimeoutSec,
102 |
103 | [int]
104 | $RegisterApplicationTypeTimeoutSec
105 | )
106 |
107 | function Read-XmlElementAsHashtable
108 | {
109 | Param (
110 | [System.Xml.XmlElement]
111 | $Element
112 | )
113 |
114 | $hashtable = @{}
115 | if ($Element.Attributes)
116 | {
117 | $Element.Attributes |
118 | ForEach-Object {
119 | $boolVal = $null
120 | if ([bool]::TryParse($_.Value, [ref]$boolVal)) {
121 | $hashtable[$_.Name] = $boolVal
122 | }
123 | else {
124 | $hashtable[$_.Name] = $_.Value
125 | }
126 | }
127 | }
128 |
129 | return $hashtable
130 | }
131 |
132 | function Read-PublishProfile
133 | {
134 | Param (
135 | [ValidateScript({Test-Path $_ -PathType Leaf})]
136 | [String]
137 | $PublishProfileFile
138 | )
139 |
140 | $publishProfileXml = [Xml] (Get-Content $PublishProfileFile)
141 | $publishProfile = @{}
142 |
143 | $publishProfile.ClusterConnectionParameters = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("ClusterConnectionParameters")
144 | $publishProfile.UpgradeDeployment = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("UpgradeDeployment")
145 | $publishProfile.CopyPackageParameters = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("CopyPackageParameters")
146 | $publishProfile.RegisterApplicationParameters = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("RegisterApplicationParameters")
147 |
148 | if ($publishProfileXml.PublishProfile.Item("UpgradeDeployment"))
149 | {
150 | $publishProfile.UpgradeDeployment.Parameters = Read-XmlElementAsHashtable $publishProfileXml.PublishProfile.Item("UpgradeDeployment").Item("Parameters")
151 | if ($publishProfile.UpgradeDeployment["Mode"])
152 | {
153 | $publishProfile.UpgradeDeployment.Parameters[$publishProfile.UpgradeDeployment["Mode"]] = $true
154 | }
155 | }
156 |
157 | $publishProfileFolder = (Split-Path $PublishProfileFile)
158 | $publishProfile.ApplicationParameterFile = [System.IO.Path]::Combine($PublishProfileFolder, $publishProfileXml.PublishProfile.ApplicationParameterFile.Path)
159 |
160 | return $publishProfile
161 | }
162 |
163 | $LocalFolder = (Split-Path $MyInvocation.MyCommand.Path)
164 |
165 | if (!$PublishProfileFile)
166 | {
167 | $PublishProfileFile = "$LocalFolder\..\PublishProfiles\Local.xml"
168 | }
169 |
170 | if (!$ApplicationPackagePath)
171 | {
172 | $ApplicationPackagePath = "$LocalFolder\..\pkg\Release"
173 | }
174 |
175 | $ApplicationPackagePath = Resolve-Path $ApplicationPackagePath
176 |
177 | $publishProfile = Read-PublishProfile $PublishProfileFile
178 |
179 | if (-not $UseExistingClusterConnection)
180 | {
181 | $ClusterConnectionParameters = $publishProfile.ClusterConnectionParameters
182 | if ($SecurityToken)
183 | {
184 | $ClusterConnectionParameters["SecurityToken"] = $SecurityToken
185 | }
186 |
187 | try
188 | {
189 | [void](Connect-ServiceFabricCluster @ClusterConnectionParameters)
190 | }
191 | catch [System.Fabric.FabricObjectClosedException]
192 | {
193 | Write-Warning "Service Fabric cluster may not be connected."
194 | throw
195 | }
196 | }
197 |
198 | $RegKey = "HKLM:\SOFTWARE\Microsoft\Service Fabric SDK"
199 | $ModuleFolderPath = (Get-ItemProperty -Path $RegKey -Name FabricSDKPSModulePath).FabricSDKPSModulePath
200 | Import-Module "$ModuleFolderPath\ServiceFabricSDK.psm1"
201 |
202 | $IsUpgrade = ($publishProfile.UpgradeDeployment -and $publishProfile.UpgradeDeployment.Enabled -and $OverrideUpgradeBehavior -ne 'VetoUpgrade') -or $OverrideUpgradeBehavior -eq 'ForceUpgrade'
203 |
204 | $PublishParameters = @{
205 | 'ApplicationPackagePath' = $ApplicationPackagePath
206 | 'ApplicationParameterFilePath' = $publishProfile.ApplicationParameterFile
207 | 'ApplicationParameter' = $ApplicationParameter
208 | 'ErrorAction' = 'Stop'
209 | }
210 |
211 | if ($publishProfile.CopyPackageParameters.CopyPackageTimeoutSec)
212 | {
213 | $PublishParameters['CopyPackageTimeoutSec'] = $publishProfile.CopyPackageParameters.CopyPackageTimeoutSec
214 | }
215 |
216 | if ($publishProfile.CopyPackageParameters.CompressPackage)
217 | {
218 | $PublishParameters['CompressPackage'] = $publishProfile.CopyPackageParameters.CompressPackage
219 | }
220 |
221 | if ($publishProfile.RegisterApplicationParameters.RegisterApplicationTypeTimeoutSec)
222 | {
223 | $PublishParameters['RegisterApplicationTypeTimeoutSec'] = $publishProfile.RegisterApplicationParameters.RegisterApplicationTypeTimeoutSec
224 | }
225 |
226 | # CopyPackageTimeoutSec parameter overrides the value from the publish profile
227 | if ($CopyPackageTimeoutSec)
228 | {
229 | $PublishParameters['CopyPackageTimeoutSec'] = $CopyPackageTimeoutSec
230 | }
231 |
232 | # RegisterApplicationTypeTimeoutSec parameter overrides the value from the publish profile
233 | if ($RegisterApplicationTypeTimeoutSec)
234 | {
235 | $PublishParameters['RegisterApplicationTypeTimeoutSec'] = $RegisterApplicationTypeTimeoutSec
236 | }
237 |
238 | if ($IsUpgrade)
239 | {
240 | $Action = "RegisterAndUpgrade"
241 | if ($DeployOnly)
242 | {
243 | $Action = "Register"
244 | }
245 |
246 | $UpgradeParameters = $publishProfile.UpgradeDeployment.Parameters
247 |
248 | if ($OverrideUpgradeBehavior -eq 'ForceUpgrade')
249 | {
250 | # Warning: Do not alter these upgrade parameters. It will create an inconsistency with Visual Studio's behavior.
251 | $UpgradeParameters = @{ UnmonitoredAuto = $true; Force = $true }
252 | }
253 |
254 | $PublishParameters['Action'] = $Action
255 | $PublishParameters['UpgradeParameters'] = $UpgradeParameters
256 | $PublishParameters['UnregisterUnusedVersions'] = $UnregisterUnusedApplicationVersionsAfterUpgrade
257 |
258 | Publish-UpgradedServiceFabricApplication @PublishParameters
259 | }
260 | else
261 | {
262 | $Action = "RegisterAndCreate"
263 | if ($DeployOnly)
264 | {
265 | $Action = "Register"
266 | }
267 |
268 | $PublishParameters['Action'] = $Action
269 | $PublishParameters['OverwriteBehavior'] = $OverwriteBehavior
270 | $PublishParameters['SkipPackageValidation'] = $SkipPackageValidation
271 |
272 | Publish-NewServiceFabricApplication @PublishParameters
273 | }
--------------------------------------------------------------------------------
/PurchaseLogging/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------