Repository: Azure-Samples/service-fabric-dotnet-web-reference-app
Branch: master
Commit: c6ebbb208e1d
Files: 146
Total size: 414.5 KB
Directory structure:
gitextract_lulu55kd/
├── .gitignore
├── CONTRIBUTING.md
├── Docs/
│ └── architecture.md
├── LICENSE
├── README.md
└── ReferenceApp/
├── Common/
│ ├── ActorMessageId.cs
│ ├── Common.csproj
│ ├── HashUtil.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── ServiceUriBuilder.cs
│ └── packages.config
├── CustomerOrder.Actor/
│ ├── ActorEventSource.cs
│ ├── App.config
│ ├── CustomerOrder.Actor.csproj
│ ├── CustomerOrderActor.cs
│ ├── CustomerOrderReminderNames.cs
│ ├── PackageRoot/
│ │ ├── Config/
│ │ │ └── Settings.xml
│ │ └── ServiceManifest.xml
│ ├── Program.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ └── packages.config
├── CustomerOrder.Domain/
│ ├── CustomerOrder.Domain.csproj
│ ├── CustomerOrderItem.cs
│ ├── CustomerOrderStatus.cs
│ ├── ICustomerOrderActor.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── app.config
│ └── packages.config
├── CustomerOrder.UnitTests/
│ ├── CustomerOrder.UnitTests.csproj
│ ├── CustomerOrderActorTests.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── app.config
│ └── packages.config
├── Inventory.Domain/
│ ├── IInventoryService.cs
│ ├── Inventory.Domain.csproj
│ ├── InventoryItem.cs
│ ├── InventoryItemId.cs
│ ├── InventoryItemView.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ └── packages.config
├── Inventory.Service/
│ ├── App.config
│ ├── AzureBackupStore.cs
│ ├── IBackupStore.cs
│ ├── Inventory.Service.csproj
│ ├── InventoryService.cs
│ ├── LocalBackupStore.cs
│ ├── PackageRoot/
│ │ ├── Config/
│ │ │ └── Settings.xml
│ │ └── ServiceManifest.xml
│ ├── Program.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── ServiceEventSource.cs
│ ├── StatefulServiceParameters.cs
│ └── packages.config
├── Inventory.UnitTests/
│ ├── Inventory.UnitTests.csproj
│ ├── InventoryServiceTests.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── app.config
│ └── packages.config
├── Mocks/
│ ├── MockActorStateManager.cs
│ ├── MockAsyncEnumerable.cs
│ ├── MockCodePackageActivationContext.cs
│ ├── MockInventoryService.cs
│ ├── MockReliableDictionary.cs
│ ├── MockReliableQueue.cs
│ ├── MockReliableStateManager.cs
│ ├── MockServiceProxy.cs
│ ├── MockServiceProxyFactory.cs
│ ├── MockTransaction.cs
│ ├── Mocks.csproj
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── app.config
│ └── packages.config
├── Nuget.Config
├── RestockRequest.Actor/
│ ├── ActorEventSource.cs
│ ├── App.config
│ ├── PackageRoot/
│ │ ├── Config/
│ │ │ └── Settings.xml
│ │ └── ServiceManifest.xml
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── RestockRequest.Actor.csproj
│ ├── RestockRequestActor.cs
│ ├── RestockRequestActorState.cs
│ ├── RestockRequestReminderNames.cs
│ ├── ServiceHost.cs
│ └── packages.config
├── RestockRequest.Domain/
│ ├── IRestockRequestActor.cs
│ ├── IRestockRequestEvents.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── RestockRequest.Domain.csproj
│ ├── RestockRequest.cs
│ ├── RestockRequestStatus.cs
│ ├── app.config
│ └── packages.config
├── RestockRequestManager.Domain/
│ ├── IRestockRequestManager.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── RestockRequestManager.Domain.csproj
│ ├── app.config
│ └── packages.config
├── RestockRequestManager.Service/
│ ├── App.config
│ ├── PackageRoot/
│ │ ├── Config/
│ │ │ └── Settings.xml
│ │ └── ServiceManifest.xml
│ ├── Program.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── RestockRequestManager.Service.csproj
│ ├── RestockRequestManagerService.cs
│ ├── ServiceEventSource.cs
│ └── packages.config
├── Web.Service/
│ ├── .bowerrc
│ ├── App.config
│ ├── Controllers/
│ │ ├── HomeController.cs
│ │ ├── InventoryController.cs
│ │ ├── OrdersController.cs
│ │ └── StoreController.cs
│ ├── PackageRoot/
│ │ ├── Config/
│ │ │ └── Settings.xml
│ │ └── ServiceManifest.xml
│ ├── Program.cs
│ ├── Properties/
│ │ └── launchSettings.json
│ ├── ServiceEventSource.cs
│ ├── Startup.cs
│ ├── Views/
│ │ ├── Home/
│ │ │ ├── Admin.cshtml
│ │ │ ├── Index.cshtml
│ │ │ └── OrderConfirmation.cshtml
│ │ ├── Shared/
│ │ │ ├── Error.cshtml
│ │ │ ├── _Layout.cshtml
│ │ │ └── _ValidationScriptsPartial.cshtml
│ │ ├── _ViewImports.cshtml
│ │ └── _ViewStart.cshtml
│ ├── Web.Service.csproj
│ ├── WebService.cs
│ ├── appsettings.Development.json
│ ├── appsettings.json
│ ├── bower.json
│ ├── bundleconfig.json
│ └── wwwroot/
│ ├── css/
│ │ └── site.css
│ └── js/
│ └── angular-index.js
├── WebReferenceApp.sln
└── WebReferenceApplication/
├── ApplicationPackageRoot/
│ └── ApplicationManifest.xml
├── ApplicationParameters/
│ ├── Cloud.xml
│ ├── Local.1Node.xml
│ └── Local.5Node.xml
├── PublishProfiles/
│ ├── Cloud.xml
│ ├── Local.1Node.xml
│ └── Local.5Node.xml
├── Scripts/
│ └── Deploy-FabricApplication.ps1
├── WebReferenceApplication.sfproj
├── app.config
└── packages.config
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
[Bb]ackup*/
[Oo]bj/
[Bb]in/
pkg/
objd/
TestResults/
.nuget/
*.sln.ide/
_ReSharper.*/
packages/
.vs/
*.user
*.suo
*.cache
*.docstates
_ReSharper.*
nuget.exe
.settings
*.sln.ide
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Azure samples
Thank you for your interest in contributing to Azure samples!
## Ways to contribute
You can contribute to [Azure samples](https://azure.microsoft.com/documentation/samples/) in a few different ways:
- Submit feedback on [this sample page](https://azure.microsoft.com/documentation/samples/service-fabric-dotnet-web-reference-app/) whether it was helpful or not.
- Submit issues through [issue tracker](https://github.com/Azure-Samples/service-fabric-dotnet-web-reference-app/issues) on GitHub. We are actively monitoring the issues and improving our samples.
- If you wish to make code changes to samples, or contribute something new, please follow the [GitHub Forks / Pull requests model](https://help.github.com/articles/fork-a-repo/): Fork the sample repo, make the change and propose it back by submitting a pull request.
================================================
FILE: Docs/architecture.md
================================================
# Application architecture
The application is composed of individual services to perform the major functions of the application:
- Web Service
- A stateless front-end service that hosts the web UI and HTTP API for interacting with the store.
- Customer Order Actor
- An actor-based service that handles customer orders. A stateful actor is activated for each new order that's placed. The actor represents the lifetime of the customer order, from placement to fulfillment.
- Inventory Service
- A stateful service that maintains the store's inventory. This service is partitioned, where each partition of the service holds a subset of the store's entire inventory. With a large number of partitions, this service can scale out to meet data capacity and inventory request throughput.
- RestockRequest Actor
- A stateful actor that manages the lifetime of a restock request from the inventory service. Each time the inventory runs low on stock for an item, makes a request to refill the inventory. The restock request actor itself would send a request to a supplier for more items, however this is simply simulated within the actor.
- RestockRequest Manager
- This is a stateful service that manages requests from the inventory service for item restocking. It logs restock requests made by the Inventory Service and activates a RestockRequest Actor to fulfill the requst. It then receives notifications from the actors when a restock request has been fulfilled. These notifications are placed in a ReliableQueue as they come in. The notifications are periodically dequeued and sent back to the Inventory Service.
# Data flow
When a user makes a purchase, data flows the through the system as follows:

1. Client sends HTTP POST to /api/orders with order in JSON payload:
[{"ItemId":"1d6abc91-ebe7-41ff-b868-4086501903fc","Quantity":1},{"ItemId":"a833f7d0-2f76-4c9f-9353-8c728252da4a","Quantity":2}]
Response from service is a tracking ID:
68bf3f53-dc74-4ef9-8f60-89d68508247b
2. Web Service creates a new Actor (by specifying a new ActorId) to track the order by calling SubmitOrderAsync() on the Customer Order Actor service.
3. Customer Order Actor saves the order in its state, registers a reminder for itself to complete the order, and returns to the caller. Order processing has a number of steps and may need to retry in case of failure, thus the reminder serves as a queued work item within the Actor in order to prioritize saving the order reliably and returning to the caller as quickly as possible.
4. Customer Order Actor processes the order by requesting stock for the order to be removed from the Inventory Service. If there is not enough stock available to fulfill the order, the actor adds the back ordered items to its list of backordered state and registers another reminder on itself to try fulfilling the order again later. If this process completes successfully (either by completing the order or by tracking backorder items), the reminder for this method is removed, which has the effect of removing the queue work item. If at any point the method throws or the service crashes, the reminder will remain and will be executed again automatically to retry fulfilling the order. NOTE: the retry mechanism is not fully implemented yet.
5. Inventory Service checks to see if available stock is below the restock threshold after removing stock as requested by a CustomerOrder Actor. If the stock for a certain item is below the restock threshold and it's not already being reordered, it calls AddRestockRequestAsync on the Restock Request Manager.
6. When the Restock Request Manager receives a restock request, it creates a new Actor to track the request and adds the Actor to its own Reliable Dictionary so that it may query the restock request Actors later. Upon adding the Actor, it subscribes to event notifications from the Actor. This will be used later to notify the Restock Request Manager when the restock request is complete.
7. The Restock Request Actor registers a reminder on itself to go through the restock processing pipeline. This pipeline is normally where restocking logic would live, but currently it is simply faked by progressing through each step of restocking every time the reminder fires. When it reaches the last step in the pipeline, it signals the completed event (for which the Restock Request Manager is listening), and unregisters the reminder when that completes.
8. When the Restock Request Actor completes the restock request, it signals the complete event which sends the completed RestockRequest back to the Restock Request Manager. The Manager queues the completed Restock Request in a Reliable Queue for later processing and unsubscribes from receiving events from the Actor, which is now complete and will eventually be garbage-collected.
9. Restock Request Manager's RunAsync method periodically drains the queue of Restock Requests and sends those Restock Requests to the Inventory Service for restocking.
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 Microsoft Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
---
languages:
- csharp
products:
- azure
- azure-service-fabric
page_type: sample
description: "The web reference application shows how to build an end-to-end Service Fabric application with multiple types of services."
---
# Service Fabric Web Reference Application
The web reference application shows how to build an end-to-end Service Fabric application with multiple types of services, combining Reliable Services and Reliable Actors to construct a complete solution.
## Scenario
The context of this sample is a web-based store with a customer order and inventory management back-end. Logical parts of the management back-end are represented by individual services, allowing loose coupling of functionality and independently-upgradeable components:
- Customer Order Service
- Inventory Service
- Restocking Service
- Web front-end Service
The customer order and inventory management system tracks user orders, removes items from the inventory to fulfill orders, and requests restocking of inventory items when an item's stock goes below a certain threshold. If a user requests items that are out of stock, the order is placed on back-order until the inventory is replenished, at which point the order is completed.
Using Service Fabric's stateful services, each of these services can maintain its own data, rather than relying a shared monolithic data base. This allows each service to scale independently using Service Fabric's stateful partitioning to meet its unique requirements for data capacity and throughput.
## Running this sample
The majority of this application is self-contained. The only external dependency is to Azure storage for [backup & restore](https://azure.microsoft.com/en-us/documentation/articles/service-fabric-reliable-services-backup-restore/) purposes. There are no other dependencies on external services or databases to manage related to request processing or data persistence. This makes running the complete application pretty easy:
1. Open the .sln solution file in Visual Studio 2015.
2. Edit the Inventory.Service\PackageRoot\Config\Settings.xml file to contain the connection details for your Azure storage account.
2. Press F5 to run.
This deploys the entire web store application on your local machine.
There are two web endpoints to begin interacting with the application:
1. **http://localhost:8505/fabrikam/admin.html** - a very basic admin portal where you can add items into the inventory. When you first launch the application, the inventory is empty.
2. **http://localhost:8505/fabrikam/** - a basic store front-end. This shows the current inventory and your shopping cart where you can add and purchase items to see the flow of data through the system.
## Deploy this sample to Azure
The application can be deployed to Azure by right-clicking the application project in Visual Studio and selecting "Publish".

In the publish dialog box, select the Cloud profile and a connection endpoint. Selecting a connection endpoint from this dialog requires an Azure subscription. If you want to publish to a known cluster without an Azure subscription, you can simply edit the Cloud publish profile XML under PublishProfiles in the application project and specify a cluster connection endpoint there:
``` XML
```
## Unit Tests
This application also contains unit tests to show the recommended pattern to create tests against a Service Fabric application.
Below are the steps to run or debug a test (using Visual studio 2015 on a 64 bit windows):
- Open the WebReferenceApp solution in Visual studio 2015
- Select menus "Test" / "Test Setting" / "Default processor architecture" -> x64
- Rebuild the solution
- Select menus "Test" / "Windows" / "Test Explorer" you should see now the text explorer window with the list of available tests
- Choose one or more tests (for example "TestAddStock" under InventoryServiceTests)
- Right click the test of your choice and select run the test, you will see after a while a green check mark of test passed
- You can try also to debug the test to understand better its logic
## Next steps
- [Learn about the application architecture and data flow.](https://github.com/Azure-Samples/service-fabric-dotnet-web-reference-app/blob/master/Docs/architecture.md "Learn about the application architecture and data flow.")
## MSFT OSS Code Of Conduct Notice
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
================================================
FILE: ReferenceApp/Common/ActorMessageId.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Common
{
using System;
using System.Runtime.Serialization;
using Microsoft.ServiceFabric.Actors;
[DataContract]
public class CustomerOrderActorMessageId : IFormattable, IComparable, IComparable, IEquatable
{
public CustomerOrderActorMessageId(ActorId sendingActorId, long messageId)
{
this.sendingActorId = sendingActorId;
this.messageId = messageId;
}
[DataMember]
public ActorId sendingActorId { get; private set; }
[DataMember]
public long messageId { get; private set; }
int IComparable.CompareTo(object obj)
{
return this.CompareTo((CustomerOrderActorMessageId) obj);
}
public int CompareTo(CustomerOrderActorMessageId other)
{
if (this.sendingActorId.ToString().CompareTo(other.sendingActorId.ToString()) > 1)
{
return 1;
}
else if (this.sendingActorId.ToString().CompareTo(other.sendingActorId.ToString()) < 1)
{
return -1;
}
else if (this.messageId > other.messageId)
{
return 1;
}
else if (this.messageId < other.messageId)
{
return -1;
}
return 0;
}
public bool Equals(CustomerOrderActorMessageId other)
{
return (this.sendingActorId.Equals(other.sendingActorId) && this.messageId == other.messageId);
}
public string ToString(string format, IFormatProvider formatProvider)
{
return string.Format("{0}|{1}", this.sendingActorId.ToString(), this.messageId);
}
public static CustomerOrderActorMessageId GetRandom()
{
ActorId id = new ActorId(Guid.NewGuid());
Random r = new Random();
return new CustomerOrderActorMessageId(id, r.Next());
}
public static bool operator ==(CustomerOrderActorMessageId item1, CustomerOrderActorMessageId item2)
{
return item1.Equals(item2);
}
public static bool operator !=(CustomerOrderActorMessageId item1, CustomerOrderActorMessageId item2)
{
return !item1.Equals(item2);
}
public static bool operator >(CustomerOrderActorMessageId item1, CustomerOrderActorMessageId item2)
{
int result = item1.CompareTo(item2);
return (result == 0 | result == -1);
}
public static bool operator <(CustomerOrderActorMessageId item1, CustomerOrderActorMessageId item2)
{
int result = item1.CompareTo(item2);
return (result == 0 | result == 1);
}
public override bool Equals(object obj)
{
return (this.CompareTo(obj as CustomerOrderActorMessageId) == 0);
}
public override int GetHashCode()
{
return this.ToString().GetHashCode();
}
}
}
================================================
FILE: ReferenceApp/Common/Common.csproj
================================================
Debug
x64
{9EC0063F-489E-43FE-94B5-BF5F89977CD3}
Library
Properties
Common
Common
v4.5.2
512
true
bin\x64\Debug\
DEBUG;TRACE
full
x64
prompt
MinimumRecommendedRules.ruleset
bin\x64\Release\
TRACE
true
pdbonly
x64
prompt
MinimumRecommendedRules.ruleset
..\packages\Microsoft.ServiceFabric.Actors.2.6.204\lib\net45\Microsoft.ServiceFabric.Actors.dll
..\packages\Microsoft.ServiceFabric.Data.2.6.204\lib\net45\Microsoft.ServiceFabric.Data.dll
..\packages\Microsoft.ServiceFabric.Data.2.6.204\lib\net45\Microsoft.ServiceFabric.Data.Interfaces.dll
True
..\packages\Microsoft.ServiceFabric.FabricTransport.Internal.2.6.204\lib\net45\Microsoft.ServiceFabric.FabricTransport.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\Microsoft.ServiceFabric.Internal.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\Microsoft.ServiceFabric.Internal.Strings.dll
..\packages\Microsoft.ServiceFabric.Services.2.6.204\lib\net45\Microsoft.ServiceFabric.Services.dll
..\packages\Microsoft.ServiceFabric.Services.Remoting.2.6.204\lib\net45\Microsoft.ServiceFabric.Services.Remoting.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.dll
True
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Management.ServiceModel.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Management.ServiceModel.XmlSerializers.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Strings.dll
True
This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
================================================
FILE: ReferenceApp/Common/HashUtil.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Common
{
using System;
using System.Security.Cryptography;
using System.Text;
public class HashUtil
{
public static long getLongHashCode(string stringInput)
{
byte[] byteContents = Encoding.Unicode.GetBytes(stringInput);
MD5CryptoServiceProvider hash = new MD5CryptoServiceProvider();
byte[] hashText = hash.ComputeHash(byteContents);
return BitConverter.ToInt64(hashText, 0) ^ BitConverter.ToInt64(hashText, 7);
}
public static int getIntHashCode(string stringInput)
{
return (int) getLongHashCode(stringInput);
}
}
}
================================================
FILE: ReferenceApp/Common/Properties/AssemblyInfo.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Common")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Common")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("9ec0063f-489e-43fe-94b5-bf5f89977cd3")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
================================================
FILE: ReferenceApp/Common/ServiceUriBuilder.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Common
{
using System;
using System.Fabric;
public class ServiceUriBuilder
{
public ServiceUriBuilder(string serviceInstance)
{
this.ActivationContext = FabricRuntime.GetActivationContext();
this.ServiceInstance = serviceInstance;
}
public ServiceUriBuilder(ICodePackageActivationContext context, string serviceInstance)
{
this.ActivationContext = context;
this.ServiceInstance = serviceInstance;
}
public ServiceUriBuilder(ICodePackageActivationContext context, string applicationInstance, string serviceInstance)
{
this.ActivationContext = context;
this.ApplicationInstance = applicationInstance;
this.ServiceInstance = serviceInstance;
}
///
/// The name of the application instance that contains he service.
///
public string ApplicationInstance { get; set; }
///
/// The name of the service instance.
///
public string ServiceInstance { get; set; }
///
/// The local activation context
///
public ICodePackageActivationContext ActivationContext { get; set; }
public Uri ToUri()
{
string applicationInstance = this.ApplicationInstance;
if (String.IsNullOrEmpty(applicationInstance))
{
// the ApplicationName property here automatically prepends "fabric:/" for us
applicationInstance = this.ActivationContext.ApplicationName.Replace("fabric:/", String.Empty);
}
return new Uri("fabric:/" + applicationInstance + "/" + this.ServiceInstance);
}
}
}
================================================
FILE: ReferenceApp/Common/packages.config
================================================
================================================
FILE: ReferenceApp/CustomerOrder.Actor/ActorEventSource.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace CustomerOrder.Actor
{
using System;
using System.Diagnostics.Tracing;
using System.Threading.Tasks;
using Microsoft.ServiceFabric.Actors.Runtime;
[EventSource(Name = "MyCompany-Web_UIApplication-CustomerOrder")]
internal sealed class ActorEventSource : EventSource
{
private const int MessageEventId = 1;
// For very high-frequency events it might be advantageous to raise events using WriteEventCore API.
// This results in more efficient parameter handling, but requires explicit allocation of EventData structure and unsafe code.
// To enable this code path, define UNSAFE conditional compilation symbol and turn on unsafe code support in project properties.
private const int ActorMessageEventId = 2;
private const int ActorHostInitializationFailedEventId = 3;
public static readonly ActorEventSource Current = new ActorEventSource();
static ActorEventSource()
{
// A workaround for the problem where ETW activities do not get tracked until Tasks infrastructure is initialized.
// This problem will be fixed in .NET Framework 4.6.2.
Task.Run(() => { }).Wait();
}
// Instance constructor is private to enforce singleton semantics
private ActorEventSource() : base()
{
}
// Define an instance method for each event you want to record and apply an [Event] attribute to it.
// The method name is the name of the event.
// Pass any parameters you want to record with the event (only primitive integer types, DateTime, Guid & string are allowed).
// Each event method implementation should check whether the event source is enabled, and if it is, call WriteEvent() method to raise the event.
// The number and types of arguments passed to every event method must exactly match what is passed to WriteEvent().
// Put [NonEvent] attribute on all methods that do not define an event.
// For more information see https://msdn.microsoft.com/en-us/library/system.diagnostics.tracing.eventsource.aspx
[NonEvent]
public void Message(string message, params object[] args)
{
if (this.IsEnabled())
{
string finalMessage = string.Format(message, args);
this.Message(finalMessage);
}
}
[Event(MessageEventId, Level = EventLevel.Informational, Message = "{0}")]
public void Message(string message)
{
if (this.IsEnabled())
{
this.WriteEvent(MessageEventId, message);
}
}
[NonEvent]
public void ActorMessage(Actor actor, string message, params object[] args)
{
if (this.IsEnabled()
&& actor.Id != null
&& actor.ActorService != null
&& actor.ActorService.Context != null
&& actor.ActorService.Context.CodePackageActivationContext != null)
{
string finalMessage = string.Format(message, args);
this.ActorMessage(
actor.GetType().ToString(),
actor.Id.ToString(),
actor.ActorService.Context.CodePackageActivationContext.ApplicationTypeName,
actor.ActorService.Context.CodePackageActivationContext.ApplicationName,
actor.ActorService.Context.ServiceTypeName,
actor.ActorService.Context.ServiceName.ToString(),
actor.ActorService.Context.PartitionId,
actor.ActorService.Context.ReplicaId,
actor.ActorService.Context.NodeContext.NodeName,
finalMessage);
}
}
[Event(ActorHostInitializationFailedEventId, Level = EventLevel.Error, Message = "Actor host initialization failed",
Keywords = Keywords.HostInitialization)]
public void ActorHostInitializationFailed(string exception)
{
this.WriteEvent(ActorHostInitializationFailedEventId, exception);
}
[Event(ActorMessageEventId, Level = EventLevel.Informational, Message = "{9}")]
private
void ActorMessage(
string actorType,
string actorId,
string applicationTypeName,
string applicationName,
string serviceTypeName,
string serviceName,
Guid partitionId,
long replicaOrInstanceId,
string nodeName,
string message)
{
this.WriteEvent(
ActorMessageEventId,
actorType,
actorId,
applicationTypeName,
applicationName,
serviceTypeName,
serviceName,
partitionId,
replicaOrInstanceId,
nodeName,
message);
}
// Event keywords can be used to categorize events.
// Each keyword is a bit flag. A single event can be associated with multiple keywords (via EventAttribute.Keywords property).
// Keywords must be defined as a public class named 'Keywords' inside EventSource that uses them.
public static class Keywords
{
public const EventKeywords HostInitialization = (EventKeywords) 0x1L;
}
}
}
================================================
FILE: ReferenceApp/CustomerOrder.Actor/App.config
================================================
================================================
FILE: ReferenceApp/CustomerOrder.Actor/CustomerOrder.Actor.csproj
================================================
Debug
x64
{5F0C7805-C91D-47E9-AEDE-946CADEA1C8F}
Exe
Properties
CustomerOrder.Actor
CustomerOrder.Actor
v4.5.2
512
true
True
true
PackageRoot
$(MSBuildProjectName)
x64
true
full
false
bin\Debug\
DEBUG;TRACE
prompt
4
x64
pdbonly
true
bin\Release\
TRACE
prompt
4
..\packages\Microsoft.ServiceFabric.Actors.2.6.204\lib\net45\Microsoft.ServiceFabric.Actors.dll
..\packages\Microsoft.ServiceFabric.Data.2.6.204\lib\net45\Microsoft.ServiceFabric.Data.dll
..\packages\Microsoft.ServiceFabric.Data.2.6.204\lib\net45\Microsoft.ServiceFabric.Data.Interfaces.dll
True
..\packages\Microsoft.ServiceFabric.FabricTransport.Internal.2.6.204\lib\net45\Microsoft.ServiceFabric.FabricTransport.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\Microsoft.ServiceFabric.Internal.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\Microsoft.ServiceFabric.Internal.Strings.dll
..\packages\Microsoft.ServiceFabric.Services.2.6.204\lib\net45\Microsoft.ServiceFabric.Services.dll
..\packages\Microsoft.ServiceFabric.Services.Remoting.2.6.204\lib\net45\Microsoft.ServiceFabric.Services.Remoting.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.dll
True
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Management.ServiceModel.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Management.ServiceModel.XmlSerializers.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Strings.dll
True
Designer
{1E7E813F-43D3-4D0B-8546-5E1023873F28}
CustomerOrder.Domain
{9ec0063f-489e-43fe-94b5-bf5f89977cd3}
Common
{7e9c2dfd-71a5-496d-aa4d-5ec53eaeb9ae}
Inventory.Domain
{00E00484-BD00-40CD-B2CC-1CFA8E893972}
Mocks
This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
================================================
FILE: ReferenceApp/CustomerOrder.Actor/CustomerOrderActor.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace CustomerOrder.Actor
{
using Common;
using CustomerOrder.Domain;
using Inventory.Domain;
using Microsoft.ServiceFabric.Actors;
using Microsoft.ServiceFabric.Actors.Runtime;
using Microsoft.ServiceFabric.Data;
using Microsoft.ServiceFabric.Services.Remoting.Client;
using Mocks;
using System;
using System.Collections.Generic;
using System.Fabric;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
internal class CustomerOrderActor : Actor, ICustomerOrderActor, IRemindable
{
private const string InventoryServiceName = "InventoryService";
private const string OrderItemListPropertyName = "OrderList";
private const string OrderStatusPropertyName = "CustomerOrderStatus";
private const string RequestIdPropertyName = "RequestId";
private IServiceProxyFactory ServiceProxyFactory;
private ServiceUriBuilder builder;
private CancellationTokenSource tokenSource = null;
public CustomerOrderActor(ActorService actorService, ActorId actorId)
: base (actorService, actorId)
{ }
///
/// This method accepts a list of CustomerOrderItems, representing a customer order, and sets the actor's state
/// to reflect the status and contents of the order. Then, the order is fulfilled with a private FulfillOrder call
/// that abstracts away the entire backorder process from the user.
///
///
///
public async Task SubmitOrderAsync(IEnumerable orderList)
{
try
{
await this.StateManager.SetStateAsync>(OrderItemListPropertyName, new List(orderList));
await this.StateManager.SetStateAsync(OrderStatusPropertyName, CustomerOrderStatus.Submitted);
await this.RegisterReminderAsync(
CustomerOrderReminderNames.FulfillOrderReminder,
null,
TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(10));
}
catch (Exception e)
{
ActorEventSource.Current.Message(e.ToString());
}
ActorEventSource.Current.Message("Order submitted with {0} items", orderList.Count());
return;
}
///
/// Returns the status of the Customer Order.
///
///
public async Task GetOrderStatusAsStringAsync()
{
return (await this.GetOrderStatusAsync()).ToString();
}
public async Task ReceiveReminderAsync(string reminderName, byte[] context, TimeSpan dueTime, TimeSpan period)
{
switch (reminderName)
{
case CustomerOrderReminderNames.FulfillOrderReminder:
await this.FulfillOrderAsync();
CustomerOrderStatus orderStatus = await this.GetOrderStatusAsync();
if (orderStatus == CustomerOrderStatus.Shipped || orderStatus == CustomerOrderStatus.Canceled)
{
//Remove fulfill order reminder so Actor can be gargabe collected.
IActorReminder orderReminder = this.GetReminder(CustomerOrderReminderNames.FulfillOrderReminder);
await this.UnregisterReminderAsync(orderReminder);
}
break;
default:
// We should never arrive here normally. The system won't call reminders that don't exist.
// But for our own sake in case we add a new reminder somewhere and forget to handle it, this will remind us.
throw new InvalidOperationException("Unknown reminder: " + reminderName);
}
}
///
/// Initializes CustomerOrderActor state. Because an order actor will only be activated
/// once in this scenario and never used again, when we initiate the actor's state we
/// change the order's status to "Confirmed," and do not need to check if the actor's
/// state was already set to this.
///
///
protected override async Task OnActivateAsync()
{
await InternalActivateAsync(this.ActorService.Context.CodePackageActivationContext, new ServiceProxyFactory());
CustomerOrderStatus orderStatusResult = await this.GetOrderStatusAsync();
if (orderStatusResult == CustomerOrderStatus.Unknown)
{
await this.StateManager.SetStateAsync>(OrderItemListPropertyName, new List());
await this.StateManager.SetStateAsync(RequestIdPropertyName, 0);
await this.SetOrderStatusAsync(CustomerOrderStatus.New);
}
return;
}
///
/// Adding this method to support DI/Testing
/// We need to do some work to create the actor object and make sure it is constructed completely
/// In local testing we can inject the components we need, but in a real cluster
/// those items are not established until the actor object is activated. Thus we need to
/// have this method so that the tests can have the same init path as the actor would in prod
///
///
public async Task InternalActivateAsync(ICodePackageActivationContext context, IServiceProxyFactory proxyFactory)
{
this.tokenSource = new CancellationTokenSource();
this.builder = new ServiceUriBuilder(context, InventoryServiceName);
this.ServiceProxyFactory = proxyFactory;
}
///
/// Deactivates the actor object
///
///
protected override Task OnDeactivateAsync()
{
this.tokenSource.Cancel();
this.tokenSource.Dispose();
return Task.FromResult(true);
}
///
/// This method takes in a list of CustomerOrderItem objects. Using a Service Proxy to access the Inventory Service,
/// the method iterates onces through the order and tries to remove the quantity specified in the order from inventory.
/// If the inventory has insufficient stock to remove the requested amount for a particular item, the entire order is
/// marked as backordered and the item in question is added to a "backordered" item list, which is fulfilled in a separate
/// method.
///
/// In its current form, this application addresses the question of race conditions to remove the same item by making a rule
/// that no order ever fails. While an item that is displayed in the store may not be available any longer by the time an order is placed,
/// the automatic restock policy instituted in the Inventory Service means that our FulfillOrder method and its sub-methods can continue to
/// query the Inventory Service on repeat (with a timer in between each cycle) until the order is fulfilled.
///
///
/// The number of items put on backorder after fulfilling the order.
internal async Task FulfillOrderAsync()
{
await this.SetOrderStatusAsync(CustomerOrderStatus.InProcess);
IList orderedItems = await this.StateManager.GetStateAsync>(OrderItemListPropertyName);
ActorEventSource.Current.ActorMessage(this, "Fullfilling customer order. ID: {0}. Items: {1}", this.Id.GetGuidId(), orderedItems.Count);
foreach (CustomerOrderItem tempitem in orderedItems)
{
ActorEventSource.Current.Message("OrderContains:{0}", tempitem);
}
//We loop through the customer order list.
//For every item that cannot be fulfilled, we add to backordered.
foreach (CustomerOrderItem item in orderedItems.Where(x => x.FulfillmentRemaining > 0))
{
IInventoryService inventoryService = this.ServiceProxyFactory.CreateServiceProxy(this.builder.ToUri(), item.ItemId.GetPartitionKey());
//First, check the item is listed in inventory.
//This will avoid infinite backorder status.
if ((await inventoryService.IsItemInInventoryAsync(item.ItemId, this.tokenSource.Token)) == false)
{
await this.SetOrderStatusAsync(CustomerOrderStatus.Canceled);
return;
}
int numberItemsRemoved =
await
inventoryService.RemoveStockAsync(
item.ItemId,
item.Quantity,
new CustomerOrderActorMessageId(
new ActorId(this.Id.GetGuidId()),
await this.StateManager.GetStateAsync(RequestIdPropertyName)));
item.FulfillmentRemaining -= numberItemsRemoved;
}
IList items = await this.StateManager.GetStateAsync>(OrderItemListPropertyName);
bool backordered = false;
// Set the status appropriately
foreach (CustomerOrderItem item in items)
{
if (item.FulfillmentRemaining > 0)
{
backordered = true;
break;
}
}
if (backordered)
{
await this.SetOrderStatusAsync(CustomerOrderStatus.Backordered);
}
else
{
await this.SetOrderStatusAsync(CustomerOrderStatus.Shipped);
}
ActorEventSource.Current.ActorMessage(
this,
"{0}; Fulfilled: {1}. Backordered: {2}",
await this.GetOrderStatusAsStringAsync(),
items.Count(x => x.FulfillmentRemaining == 0),
items.Count(x => x.FulfillmentRemaining > 0));
long messageRequestId = await this.StateManager.GetStateAsync(RequestIdPropertyName);
await this.StateManager.SetStateAsync(RequestIdPropertyName, ++messageRequestId);
}
private async Task GetOrderStatusAsync()
{
ConditionalValue orderStatusResult = await this.StateManager.TryGetStateAsync(OrderStatusPropertyName);
if (orderStatusResult.HasValue)
{
return orderStatusResult.Value;
}
else
{
return CustomerOrderStatus.Unknown;
}
}
private async Task SetOrderStatusAsync(CustomerOrderStatus orderStatus)
{
await this.StateManager.SetStateAsync(OrderStatusPropertyName, orderStatus);
}
}
}
================================================
FILE: ReferenceApp/CustomerOrder.Actor/CustomerOrderReminderNames.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace CustomerOrder.Actor
{
internal static class CustomerOrderReminderNames
{
public const string FulfillOrderReminder = "FulfillOrderReminder";
}
}
================================================
FILE: ReferenceApp/CustomerOrder.Actor/PackageRoot/Config/Settings.xml
================================================
================================================
FILE: ReferenceApp/CustomerOrder.Actor/PackageRoot/ServiceManifest.xml
================================================
CustomerOrder.Actor.exe
================================================
FILE: ReferenceApp/CustomerOrder.Actor/Program.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace CustomerOrder.Actor
{
using System;
using System.Threading;
using Microsoft.ServiceFabric.Actors.Runtime;
public class Program
{
public static void Main(string[] args)
{
try
{
ActorRuntime.RegisterActorAsync();
Thread.Sleep(Timeout.Infinite);
}
catch (Exception e)
{
ActorEventSource.Current.ActorHostInitializationFailed(e.ToString());
throw;
}
}
}
}
================================================
FILE: ReferenceApp/CustomerOrder.Actor/Properties/AssemblyInfo.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("CustomerOrder")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("CustomerOrder")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("5f0c7805-c91d-47e9-aede-946cadea1c8f")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: InternalsVisibleTo("CustomerOrder.UnitTests")]
================================================
FILE: ReferenceApp/CustomerOrder.Actor/packages.config
================================================
================================================
FILE: ReferenceApp/CustomerOrder.Domain/CustomerOrder.Domain.csproj
================================================
Debug
x64
{1E7E813F-43D3-4D0B-8546-5E1023873F28}
Library
Properties
CustomerOrder.Domain
CustomerOrder.Domain
v4.5.2
512
true
bin\x64\Debug\
DEBUG;TRACE
full
x64
prompt
MinimumRecommendedRules.ruleset
bin\x64\Release\
TRACE
true
pdbonly
x64
prompt
MinimumRecommendedRules.ruleset
..\packages\Microsoft.ServiceFabric.Actors.2.6.204\lib\net45\Microsoft.ServiceFabric.Actors.dll
..\packages\Microsoft.ServiceFabric.Data.2.6.204\lib\net45\Microsoft.ServiceFabric.Data.dll
..\packages\Microsoft.ServiceFabric.Data.2.6.204\lib\net45\Microsoft.ServiceFabric.Data.Interfaces.dll
True
..\packages\Microsoft.ServiceFabric.FabricTransport.Internal.2.6.204\lib\net45\Microsoft.ServiceFabric.FabricTransport.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\Microsoft.ServiceFabric.Internal.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\Microsoft.ServiceFabric.Internal.Strings.dll
..\packages\Microsoft.ServiceFabric.Services.2.6.204\lib\net45\Microsoft.ServiceFabric.Services.dll
..\packages\Microsoft.ServiceFabric.Services.Remoting.2.6.204\lib\net45\Microsoft.ServiceFabric.Services.Remoting.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.dll
True
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Management.ServiceModel.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Management.ServiceModel.XmlSerializers.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Strings.dll
True
{9ec0063f-489e-43fe-94b5-bf5f89977cd3}
Common
{7e9c2dfd-71a5-496d-aa4d-5ec53eaeb9ae}
Inventory.Domain
This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
================================================
FILE: ReferenceApp/CustomerOrder.Domain/CustomerOrderItem.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace CustomerOrder.Domain
{
using System;
using System.Runtime.Serialization;
using Inventory.Domain;
[DataContract]
public sealed class CustomerOrderItem
{
public CustomerOrderItem(InventoryItemId itemId, int quantity)
{
this.ItemId = itemId;
this.Quantity = quantity;
this.FulfillmentRemaining = quantity;
}
[DataMember]
public InventoryItemId ItemId { get; set; }
[DataMember]
public int Quantity { get; set; }
[DataMember]
public int FulfillmentRemaining { get; set; }
public override string ToString()
{
return String.Format("ID: {0}, Quantity: {1}, Fulfillment Remaing: {2}", this.ItemId, this.Quantity, this.FulfillmentRemaining);
}
}
}
================================================
FILE: ReferenceApp/CustomerOrder.Domain/CustomerOrderStatus.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace CustomerOrder.Domain
{
public enum CustomerOrderStatus
{
Unknown,
New,
Submitted,
InProcess,
Backordered,
Shipped,
Canceled,
}
}
================================================
FILE: ReferenceApp/CustomerOrder.Domain/ICustomerOrderActor.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace CustomerOrder.Domain
{
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.ServiceFabric.Actors;
public interface ICustomerOrderActor : IActor
{
Task GetOrderStatusAsStringAsync();
Task SubmitOrderAsync(IEnumerable orderList);
}
}
================================================
FILE: ReferenceApp/CustomerOrder.Domain/Properties/AssemblyInfo.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("CustomerOrder.Interfaces")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("CustomerOrder.Interfaces")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("1e7e813f-43d3-4d0b-8546-5e1023873f28")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
================================================
FILE: ReferenceApp/CustomerOrder.Domain/app.config
================================================
================================================
FILE: ReferenceApp/CustomerOrder.Domain/packages.config
================================================
================================================
FILE: ReferenceApp/CustomerOrder.UnitTests/CustomerOrder.UnitTests.csproj
================================================
Debug
AnyCPU
{226B8D34-99CF-4172-87DC-E20B39030CC0}
Library
Properties
CustomerOrder.UnitTests
CustomerOrder.UnitTests
v4.5.2
512
{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
10.0
$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
False
UnitTest
true
bin\x64\Debug\
DEBUG;TRACE
full
x64
prompt
MinimumRecommendedRules.ruleset
bin\x64\Release\
TRACE
true
pdbonly
x64
prompt
MinimumRecommendedRules.ruleset
{9ec0063f-489e-43fe-94b5-bf5f89977cd3}
Common
{5f0c7805-c91d-47e9-aede-946cadea1c8f}
CustomerOrder.Actor
{1e7e813f-43d3-4d0b-8546-5e1023873f28}
CustomerOrder.Domain
{7e9c2dfd-71a5-496d-aa4d-5ec53eaeb9ae}
Inventory.Domain
{00e00484-bd00-40cd-b2cc-1cfa8e893972}
Mocks
..\packages\Microsoft.ServiceFabric.Actors.2.6.204\lib\net45\Microsoft.ServiceFabric.Actors.dll
..\packages\Microsoft.ServiceFabric.Data.2.6.204\lib\net45\Microsoft.ServiceFabric.Data.dll
..\packages\Microsoft.ServiceFabric.Data.2.6.204\lib\net45\Microsoft.ServiceFabric.Data.Interfaces.dll
True
..\packages\Microsoft.ServiceFabric.FabricTransport.Internal.2.6.204\lib\net45\Microsoft.ServiceFabric.FabricTransport.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\Microsoft.ServiceFabric.Internal.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\Microsoft.ServiceFabric.Internal.Strings.dll
..\packages\Microsoft.ServiceFabric.Services.2.6.204\lib\net45\Microsoft.ServiceFabric.Services.dll
..\packages\Microsoft.ServiceFabric.Services.Remoting.2.6.204\lib\net45\Microsoft.ServiceFabric.Services.Remoting.dll
False
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.dll
True
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Management.ServiceModel.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Management.ServiceModel.XmlSerializers.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Strings.dll
True
False
False
False
False
This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
================================================
FILE: ReferenceApp/CustomerOrder.UnitTests/CustomerOrderActorTests.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace CustomerOrder.UnitTests
{
using CustomerOrder.Actor;
using CustomerOrder.Domain;
using Inventory.Domain;
using Microsoft.ServiceFabric.Actors;
using Microsoft.ServiceFabric.Actors.Runtime;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Mocks;
using System;
using System.Collections.Generic;
using System.Fabric;
using System.Numerics;
using System.Reflection;
using System.Threading.Tasks;
[TestClass]
public class CustomerOrderActorTests
{
private const string OrderItemListPropertyName = "OrderList";
private const string OrderStatusPropertyName = "CustomerOrderStatus";
private const string RequestIdPropertyName = "RequestId";
private const string InventoryServiceName = "InventoryService";
private static ICodePackageActivationContext codePackageContext = new MockCodePackageActivationContext(
"fabric:/someapp",
"SomeAppType",
"Code",
"1.0.0.0",
Guid.NewGuid().ToString(),
@"C:\Log",
@"C:\Temp",
@"C:\Work",
"ServiceManifest",
"1.0.0.0"
);
private static StatefulServiceContext statefulServiceContext = new StatefulServiceContext(
new NodeContext("Test", new NodeId(new BigInteger(0), new BigInteger(0)), new BigInteger(0), "TestType", "localhost"),
codePackageContext,
"",
new Uri("fabric:/testapp/testservice"),
new byte[0],
Guid.NewGuid(),
0);
///
/// Tests FulfillOrder ships an order when all items are available from the InventoryService.
///
///
[TestMethod]
public async Task TestFulfillOrderSimple()
{
// The default mock inventory service behavior is to always complete an order.
MockInventoryService inventoryService = new MockInventoryService();
MockServiceProxyFactory serviceProxyFactory = new MockServiceProxyFactory();
serviceProxyFactory.AssociateMockServiceAndName(new Uri("fabric:/someapp/" + InventoryServiceName), inventoryService);
CustomerOrderActor target = await CreateCustomerOrderActor(serviceProxyFactory);
await target.StateManager.SetStateAsync(RequestIdPropertyName, CustomerOrderStatus.Submitted);
await target.StateManager.SetStateAsync(RequestIdPropertyName, 0);
await target.StateManager.SetStateAsync>(OrderItemListPropertyName, new List()
{
new CustomerOrderItem(new InventoryItemId(), 4)
});
await target.FulfillOrderAsync();
Assert.AreEqual(CustomerOrderStatus.Shipped, await target.StateManager.GetStateAsync(OrderStatusPropertyName));
}
///
/// Tests FulfillOrder does not ship when not all items could be fulfilled by InventoryService.
///
///
[TestMethod]
public async Task TestFulfillOrderWithBackorder()
{
// instruct the mock inventory service to always return less quantity than requested
// so that FulfillOrder always ends up in backordered status.
MockInventoryService inventoryService = new MockInventoryService()
{
RemoveStockAsyncFunc = (itemId, quantity, cmid) => Task.FromResult(quantity - 1)
};
MockServiceProxy serviceProxy = new MockServiceProxy();
serviceProxy.Supports(serviceUri => inventoryService);
MockServiceProxyFactory serviceProxyFactory = new MockServiceProxyFactory();
serviceProxyFactory.AssociateMockServiceAndName(new Uri("fabric:/someapp/" + InventoryServiceName), inventoryService);
CustomerOrderActor target = await CreateCustomerOrderActor(serviceProxyFactory);
await target.StateManager.SetStateAsync(RequestIdPropertyName, CustomerOrderStatus.Submitted);
await target.StateManager.SetStateAsync(RequestIdPropertyName, 0);
await target.StateManager.SetStateAsync>(OrderItemListPropertyName, new List()
{
new CustomerOrderItem(new InventoryItemId(), 4)
});
await target.FulfillOrderAsync();
Assert.AreEqual(CustomerOrderStatus.Backordered, await target.StateManager.GetStateAsync(OrderStatusPropertyName));
}
///
/// Tests FulfillOrder completes a shipment after multiple iterations when a limited quantity is available from InventoryService.
///
///
[TestMethod]
public async Task TestFulfillOrderToCompletion()
{
int itemCount = 5;
// instruct the mock inventory service to only fulfill one item each time
// so that FulfillOrder has to make multiple iterations to complete an order
MockInventoryService inventoryService = new MockInventoryService()
{
RemoveStockAsyncFunc = (itemId, quantity, cmid) => Task.FromResult(1)
};
MockServiceProxy serviceProxy = new MockServiceProxy();
serviceProxy.Supports(serviceUri => inventoryService);
MockServiceProxyFactory serviceProxyFactory = new MockServiceProxyFactory();
serviceProxyFactory.AssociateMockServiceAndName(new Uri("fabric:/someapp/" + InventoryServiceName), inventoryService);
CustomerOrderActor target = await CreateCustomerOrderActor(serviceProxyFactory);
await target.StateManager.SetStateAsync(RequestIdPropertyName, CustomerOrderStatus.Submitted);
await target.StateManager.SetStateAsync(RequestIdPropertyName, 0);
await target.StateManager.SetStateAsync>(OrderItemListPropertyName, new List()
{
new CustomerOrderItem(new InventoryItemId(), 5)
});
for (int i = 0; i < itemCount - 1; ++i)
{
await target.FulfillOrderAsync();
Assert.AreEqual(CustomerOrderStatus.Backordered, await target.StateManager.GetStateAsync(OrderStatusPropertyName));
}
await target.FulfillOrderAsync();
Assert.AreEqual(CustomerOrderStatus.Shipped, await target.StateManager.GetStateAsync(OrderStatusPropertyName));
}
[TestMethod]
public async Task TestFulfillOrderCancelled()
{
// instruct the mock inventory service to return 0 for all items to simulate items that don't exist.
// and have it return false when asked if an item exists to make sure FulfillOrder doesn't get into
// an infinite backorder loop.
MockInventoryService inventoryService = new MockInventoryService()
{
IsItemInInventoryAsyncFunc = itemId => Task.FromResult(false),
RemoveStockAsyncFunc = (itemId, quantity, cmid) => Task.FromResult(0)
};
MockServiceProxy serviceProxy = new MockServiceProxy();
serviceProxy.Supports(serviceUri => inventoryService);
MockServiceProxyFactory serviceProxyFactory = new MockServiceProxyFactory();
serviceProxyFactory.AssociateMockServiceAndName(new Uri("fabric:/someapp/" + InventoryServiceName), inventoryService);
CustomerOrderActor target = await CreateCustomerOrderActor(serviceProxyFactory);
await target.StateManager.SetStateAsync(RequestIdPropertyName, CustomerOrderStatus.Submitted);
await target.StateManager.SetStateAsync(RequestIdPropertyName, 0);
await target.StateManager.SetStateAsync>(OrderItemListPropertyName, new List()
{
new CustomerOrderItem(new InventoryItemId(), 5)
});
await target.FulfillOrderAsync();
CustomerOrderStatus status = await target.StateManager.GetStateAsync(OrderStatusPropertyName);
Assert.AreEqual(CustomerOrderStatus.Canceled, status);
}
private static async Task CreateCustomerOrderActor(MockServiceProxyFactory serviceProxyFactory)
{
try
{
CustomerOrderActor target = new CustomerOrderActor(
new ActorService(
context: statefulServiceContext,
actorTypeInfo: ActorTypeInformation.Get(typeof(CustomerOrderActor)),
stateManagerFactory: (actorBase, stateProvider) => new MockActorStateManager()),
new ActorId(Guid.NewGuid()));
await target.InternalActivateAsync(codePackageContext, serviceProxyFactory);
return target;
}
catch (Exception e)
{
throw;
}
}
}
}
================================================
FILE: ReferenceApp/CustomerOrder.UnitTests/Properties/AssemblyInfo.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("CustomerOrder.UnitTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("CustomerOrder.UnitTests")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("226b8d34-99cf-4172-87dc-e20b39030cc0")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
================================================
FILE: ReferenceApp/CustomerOrder.UnitTests/app.config
================================================
================================================
FILE: ReferenceApp/CustomerOrder.UnitTests/packages.config
================================================
================================================
FILE: ReferenceApp/Inventory.Domain/IInventoryService.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Inventory.Domain
{
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Common;
using Microsoft.ServiceFabric.Services.Remoting;
public interface IInventoryService : IService
{
Task AddStockAsync(InventoryItemId itemId, int quantity);
Task RemoveStockAsync(InventoryItemId itemId, int quantity, CustomerOrderActorMessageId messageId);
Task IsItemInInventoryAsync(InventoryItemId itemId, CancellationToken cancellationToken);
Task> GetCustomerInventoryAsync(CancellationToken cancellationToken);
Task CreateInventoryItemAsync(InventoryItem item);
}
}
================================================
FILE: ReferenceApp/Inventory.Domain/Inventory.Domain.csproj
================================================
Debug
x64
{7E9C2DFD-71A5-496D-AA4D-5EC53EAEB9AE}
Library
Properties
Inventory.Domain
Inventory.Domain
v4.5.2
512
true
bin\x64\Debug\
DEBUG;TRACE
full
x64
prompt
MinimumRecommendedRules.ruleset
bin\x64\Release\
TRACE
true
pdbonly
x64
prompt
MinimumRecommendedRules.ruleset
..\packages\Microsoft.ServiceFabric.Actors.2.6.204\lib\net45\Microsoft.ServiceFabric.Actors.dll
..\packages\Microsoft.ServiceFabric.Data.2.6.204\lib\net45\Microsoft.ServiceFabric.Data.dll
..\packages\Microsoft.ServiceFabric.Data.2.6.204\lib\net45\Microsoft.ServiceFabric.Data.Interfaces.dll
True
..\packages\Microsoft.ServiceFabric.FabricTransport.Internal.2.6.204\lib\net45\Microsoft.ServiceFabric.FabricTransport.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\Microsoft.ServiceFabric.Internal.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\Microsoft.ServiceFabric.Internal.Strings.dll
..\packages\Microsoft.ServiceFabric.Services.2.6.204\lib\net45\Microsoft.ServiceFabric.Services.dll
..\packages\Microsoft.ServiceFabric.Services.Remoting.2.6.204\lib\net45\Microsoft.ServiceFabric.Services.Remoting.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.dll
True
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Management.ServiceModel.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Management.ServiceModel.XmlSerializers.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Strings.dll
True
{9ec0063f-489e-43fe-94b5-bf5f89977cd3}
Common
This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
================================================
FILE: ReferenceApp/Inventory.Domain/InventoryItem.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Inventory.Domain
{
using System;
[Serializable]
public sealed class InventoryItem
{
public InventoryItem(
string description, decimal price, int availableStock, int restockThreshold, int maxStockThreshold, InventoryItemId id = null,
bool onReorder = false)
{
this.Id = id ?? new InventoryItemId();
this.Description = description;
this.Price = price;
this.AvailableStock = availableStock;
this.RestockThreshold = restockThreshold;
this.MaxStockThreshold = maxStockThreshold;
this.OnReorder = onReorder;
}
///
/// Unique identifier for each item style
///
public InventoryItemId Id { get; }
///
/// Quantity in stock
///
public int AvailableStock { get; private set; }
///
/// Price
///
public decimal Price { get; }
///
/// Brief description of product for display on website
///
public string Description { get; }
///
/// Available stock at which we should reorder
///
public int RestockThreshold { get; }
///
/// Maximum number of units that can be in-stock at any time (due to physicial/logistical constraints in warehouses)
///
public int MaxStockThreshold { get; }
///
/// True if item is on reorder
///
public bool OnReorder { get; set; }
///
/// Returns an InventoryItemView object, which contains only external, customer-facing data about an item in inventory.
///
///
public static implicit operator InventoryItemView(InventoryItem item)
{
return new InventoryItemView
{
Id = item.Id,
Price = item.Price,
Description = item.Description,
CustomerAvailableStock = item.AvailableStock - item.RestockThreshold //Business logic: constraint to reduce overordering.
};
}
public override string ToString()
{
return string.Format(
"Item {0}: {1} at a price of {2} with {3} available items at a restock threshold of {4} and with max stocking threshold of {5}.",
this.Id,
this.Description,
this.Price,
this.AvailableStock.ToString(),
this.RestockThreshold.ToString(),
this.MaxStockThreshold.ToString());
}
///
/// Increments the quantity of a particular item in inventory.
///
/// int: Returns the quantity that has been added to stock
///
public int AddStock(int quantity)
{
int original = this.AvailableStock;
// The quantity that the client is trying to add to stock is greater than what can be physically accommodated in a Fabrikam Warehouse
if ((this.AvailableStock + quantity) > this.MaxStockThreshold)
{
// For now, this method only adds new units up maximum stock threshold. In an expanded version of this application, we
//could include tracking for the remaining units and store information about overstock elsewhere.
this.AvailableStock += (this.MaxStockThreshold - this.AvailableStock);
}
else
{
this.AvailableStock += quantity;
}
this.OnReorder = false;
return this.AvailableStock - original;
}
///
/// Decrements the quantity of a particular item in inventory and ensures the restockThreshold hasn't
/// been breached. If so, a RestockRequest is generated in CheckThreshold.
///
/// If there is sufficient stock of an item, then the integer returned at the end of this call should be the same as quantityDesired.
/// In the event that there is not sufficient stock available, the method will remove whatever stock is available and return that quantity to the client.
/// In this case, it is the responsibility of the client to determine if the amount that is returned is the same as quantityDesired.
/// It is invalid to pass in a negative number.
///
///
/// int: Returns the number actually removed from stock.
///
public int RemoveStock(int quantityDesired)
{
int removed = Math.Min(quantityDesired, this.AvailableStock); //Assumes quantityDesired is a positive integer
this.AvailableStock -= removed;
return removed;
}
}
}
================================================
FILE: ReferenceApp/Inventory.Domain/InventoryItemId.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Inventory.Domain
{
using System;
using System.Runtime.Serialization;
using Common;
using Microsoft.ServiceFabric.Services.Client;
[DataContract]
public class InventoryItemId : IFormattable, IComparable, IComparable, IEquatable
{
[DataMember] private Guid id;
public InventoryItemId()
{
this.id = Guid.NewGuid();
}
public int CompareTo(object obj)
{
return this.id.CompareTo(((InventoryItemId) obj).id);
}
public int CompareTo(InventoryItemId other)
{
return this.id.CompareTo(other.id);
}
public bool Equals(InventoryItemId other)
{
return this.id.Equals(other.id);
}
public string ToString(string format, IFormatProvider formatProvider)
{
return this.id.ToString(format, formatProvider);
}
public ServicePartitionKey GetPartitionKey()
{
return new ServicePartitionKey(HashUtil.getLongHashCode(this.id.ToString()));
}
public static bool operator ==(InventoryItemId item1, InventoryItemId item2)
{
return item1.Equals(item2);
}
public static bool operator !=(InventoryItemId item1, InventoryItemId item2)
{
return !item1.Equals(item2);
}
public override bool Equals(object obj)
{
return (obj is InventoryItemId) ? this.id.Equals(((InventoryItemId) obj).id) : false;
}
public override int GetHashCode()
{
return this.id.GetHashCode();
}
public override string ToString()
{
return this.id.ToString();
}
public string ToString(string format)
{
return this.id.ToString(format);
}
}
}
================================================
FILE: ReferenceApp/Inventory.Domain/InventoryItemView.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Inventory.Domain
{
using System.Runtime.Serialization;
//Guid will always be key to this value pair
[DataContract]
public sealed class InventoryItemView
{
[DataMember]
public InventoryItemId Id { get; set; }
[DataMember]
public string Description { get; set; }
[DataMember]
public decimal Price { get; set; }
[DataMember]
public int CustomerAvailableStock { get; set; }
}
}
================================================
FILE: ReferenceApp/Inventory.Domain/Properties/AssemblyInfo.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("InventoryService.Domain")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("InventoryService.Domain")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("7e9c2dfd-71a5-496d-aa4d-5ec53eaeb9ae")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
================================================
FILE: ReferenceApp/Inventory.Domain/packages.config
================================================
================================================
FILE: ReferenceApp/Inventory.Service/App.config
================================================
================================================
FILE: ReferenceApp/Inventory.Service/AzureBackupStore.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Inventory.Service
{
using System;
using System.Collections.Generic;
using System.Fabric.Description;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.ServiceFabric.Data;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;
public class AzureBlobBackupManager : IBackupStore
{
private readonly CloudBlobClient cloudBlobClient;
private CloudBlobContainer backupBlobContainer;
private int MaxBackupsToKeep;
private string PartitionTempDirectory;
private string partitionId;
private long backupFrequencyInSeconds;
private long keyMin;
private long keyMax;
public AzureBlobBackupManager(ConfigurationSection configSection, string partitionId, long keymin, long keymax, string codePackageTempDirectory)
{
this.keyMin = keymin;
this.keyMax = keymax;
string backupAccountName = configSection.Parameters["BackupAccountName"].Value;
string backupAccountKey = configSection.Parameters["PrimaryKeyForBackupTestAccount"].Value;
string blobEndpointAddress = configSection.Parameters["BlobServiceEndpointAddress"].Value;
this.backupFrequencyInSeconds = long.Parse(configSection.Parameters["BackupFrequencyInSeconds"].Value);
this.MaxBackupsToKeep = int.Parse(configSection.Parameters["MaxBackupsToKeep"].Value);
this.partitionId = partitionId;
this.PartitionTempDirectory = Path.Combine(codePackageTempDirectory, partitionId);
StorageCredentials storageCredentials = new StorageCredentials(backupAccountName, backupAccountKey);
this.cloudBlobClient = new CloudBlobClient(new Uri(blobEndpointAddress), storageCredentials);
this.backupBlobContainer = this.cloudBlobClient.GetContainerReference(this.partitionId);
this.backupBlobContainer.CreateIfNotExists();
}
long IBackupStore.backupFrequencyInSeconds
{
get { return this.backupFrequencyInSeconds; }
}
public async Task ArchiveBackupAsync(BackupInfo backupInfo, CancellationToken cancellationToken)
{
ServiceEventSource.Current.Message("AzureBlobBackupManager: Archive Called.");
string fullArchiveDirectory = Path.Combine(this.PartitionTempDirectory, Guid.NewGuid().ToString("N"));
DirectoryInfo fullArchiveDirectoryInfo = new DirectoryInfo(fullArchiveDirectory);
fullArchiveDirectoryInfo.Create();
string blobName = string.Format("{0}_{1}_{2}_{3}", Guid.NewGuid().ToString("N"), this.keyMin, this.keyMax, "Backup.zip");
string fullArchivePath = Path.Combine(fullArchiveDirectory, "Backup.zip");
ZipFile.CreateFromDirectory(backupInfo.Directory, fullArchivePath, CompressionLevel.Fastest, false);
DirectoryInfo backupDirectory = new DirectoryInfo(backupInfo.Directory);
backupDirectory.Delete(true);
CloudBlockBlob blob = this.backupBlobContainer.GetBlockBlobReference(blobName);
await blob.UploadFromFileAsync(fullArchivePath, CancellationToken.None);
DirectoryInfo tempDirectory = new DirectoryInfo(fullArchiveDirectory);
tempDirectory.Delete(true);
ServiceEventSource.Current.Message("AzureBlobBackupManager: UploadBackupFolderAsync: success.");
}
public async Task RestoreLatestBackupToTempLocation(CancellationToken cancellationToken)
{
ServiceEventSource.Current.Message("AzureBlobBackupManager: Download backup async called.");
CloudBlockBlob lastBackupBlob = (await this.GetBackupBlobs(true)).First();
ServiceEventSource.Current.Message("AzureBlobBackupManager: Downloading {0}", lastBackupBlob.Name);
string downloadId = Guid.NewGuid().ToString("N");
string zipPath = Path.Combine(this.PartitionTempDirectory, string.Format("{0}_Backup.zip", downloadId));
lastBackupBlob.DownloadToFile(zipPath, FileMode.CreateNew);
string restorePath = Path.Combine(this.PartitionTempDirectory, downloadId);
ZipFile.ExtractToDirectory(zipPath, restorePath);
FileInfo zipInfo = new FileInfo(zipPath);
zipInfo.Delete();
ServiceEventSource.Current.Message("AzureBlobBackupManager: Downloaded {0} in to {1}", lastBackupBlob.Name, restorePath);
return restorePath;
}
public async Task DeleteBackupsAsync(CancellationToken cancellationToken)
{
if (this.backupBlobContainer.Exists())
{
ServiceEventSource.Current.Message("AzureBlobBackupManager: Deleting old backups");
IEnumerable oldBackups = (await this.GetBackupBlobs(true)).Skip(this.MaxBackupsToKeep);
foreach (CloudBlockBlob backup in oldBackups)
{
ServiceEventSource.Current.Message("AzureBlobBackupManager: Deleting {0}", backup.Name);
await backup.DeleteAsync(cancellationToken);
}
}
}
private async Task> GetBackupBlobs(bool sorted)
{
IEnumerable blobs = this.backupBlobContainer.ListBlobs();
ServiceEventSource.Current.Message("AzureBlobBackupManager: Got {0} blobs", blobs.Count());
List itemizedBlobs = new List();
foreach (CloudBlockBlob cbb in blobs)
{
await cbb.FetchAttributesAsync();
itemizedBlobs.Add(cbb);
}
if (sorted)
{
return itemizedBlobs.OrderByDescending(x => x.Properties.LastModified);
}
else
{
return itemizedBlobs;
}
}
}
}
================================================
FILE: ReferenceApp/Inventory.Service/IBackupStore.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Inventory.Service
{
using System.Threading;
using System.Threading.Tasks;
using Microsoft.ServiceFabric.Data;
public interface IBackupStore
{
long backupFrequencyInSeconds { get; }
Task ArchiveBackupAsync(BackupInfo backupInfo, CancellationToken cancellationToken);
Task RestoreLatestBackupToTempLocation(CancellationToken cancellationToken);
Task DeleteBackupsAsync(CancellationToken cancellationToken);
}
}
================================================
FILE: ReferenceApp/Inventory.Service/Inventory.Service.csproj
================================================
Debug
x64
{714B1C61-FFA8-4A5E-8DD2-D0A290677293}
Exe
Properties
Inventory.Service
Inventory.Service
v4.5.2
512
true
True
x64
true
full
false
bin\Debug\
DEBUG;TRACE
prompt
4
x64
pdbonly
true
bin\Release\
TRACE
prompt
4
$(AdditionalFileItemNames);None
..\packages\Microsoft.Azure.KeyVault.Core.2.0.4\lib\net45\Microsoft.Azure.KeyVault.Core.dll
True
..\packages\Microsoft.Data.Edm.5.8.2\lib\net40\Microsoft.Data.Edm.dll
True
..\packages\Microsoft.Data.OData.5.8.2\lib\net40\Microsoft.Data.OData.dll
True
..\packages\Microsoft.Data.Services.Client.5.8.2\lib\net40\Microsoft.Data.Services.Client.dll
True
..\packages\Microsoft.ServiceFabric.Data.2.6.204\lib\net45\Microsoft.ServiceFabric.Data.dll
..\packages\Microsoft.ServiceFabric.Data.2.6.204\lib\net45\Microsoft.ServiceFabric.Data.Interfaces.dll
True
..\packages\Microsoft.ServiceFabric.FabricTransport.Internal.2.6.204\lib\net45\Microsoft.ServiceFabric.FabricTransport.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\Microsoft.ServiceFabric.Internal.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\Microsoft.ServiceFabric.Internal.Strings.dll
..\packages\Microsoft.ServiceFabric.Services.2.6.204\lib\net45\Microsoft.ServiceFabric.Services.dll
..\packages\Microsoft.ServiceFabric.Services.Remoting.2.6.204\lib\net45\Microsoft.ServiceFabric.Services.Remoting.dll
..\packages\WindowsAzure.Storage.8.1.1\lib\net45\Microsoft.WindowsAzure.Storage.dll
..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.dll
True
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Management.ServiceModel.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Management.ServiceModel.XmlSerializers.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Strings.dll
True
..\packages\System.Spatial.5.8.2\lib\net40\System.Spatial.dll
True
Designer
{9ec0063f-489e-43fe-94b5-bf5f89977cd3}
Common
{7e9c2dfd-71a5-496d-aa4d-5ec53eaeb9ae}
Inventory.Domain
{a44fc2c5-6781-447e-80f5-7265b960b36a}
RestockRequest.Domain
{4162f266-d657-4143-aa62-626c10d23561}
RestockRequestManager.Domain
================================================
FILE: ReferenceApp/Inventory.Service/InventoryService.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Inventory.Service
{
using System;
using System.Collections.Generic;
using System.Fabric;
using System.Fabric.Description;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Common;
using Inventory.Domain;
using Microsoft.ServiceFabric.Data;
using Microsoft.ServiceFabric.Data.Collections;
using Microsoft.ServiceFabric.Services.Client;
using Microsoft.ServiceFabric.Services.Communication.Runtime;
using Microsoft.ServiceFabric.Services.Remoting.Client;
using Microsoft.ServiceFabric.Services.Remoting.Runtime;
using Microsoft.ServiceFabric.Services.Runtime;
using RestockRequest.Domain;
using RestockRequestManager.Domain;
internal class InventoryService : StatefulService, IInventoryService
{
internal const string InventoryServiceType = "InventoryServiceType";
private const string InventoryItemDictionaryName = "inventoryItems";
private const string ActorMessageDictionaryName = "incomingMessages";
private const string RestockRequestManagerServiceName = "RestockRequestManager";
private const string RequestHistoryDictionaryName = "RequestHistory";
private const string BackupCountDictionaryName = "BackupCountingDictionary";
//private IReliableStateManager stateManager;
private IBackupStore backupManager;
//Set local or cloud backup, or none. Disabled is the default. Overridden by config.
private BackupManagerType backupStorageType;
///
/// This constructor is used in unit tests to inject a different state manager for unit testing.
///
///
public InventoryService(StatefulServiceContext serviceContext) : this(serviceContext, (new ReliableStateManager(serviceContext)))
{
}
public InventoryService(StatefulServiceContext context, IReliableStateManagerReplica stateManagerReplica) : base(context, stateManagerReplica)
{
}
///
/// Used internally to generate inventory items and adds them to the ReliableDict we have.
///
///
///
public async Task CreateInventoryItemAsync(InventoryItem item)
{
IReliableDictionary inventoryItems =
await this.StateManager.GetOrAddAsync>(InventoryItemDictionaryName);
using (ITransaction tx = this.StateManager.CreateTransaction())
{
await inventoryItems.AddAsync(tx, item.Id, item);
await tx.CommitAsync();
ServiceEventSource.Current.ServiceMessage(this, "Created inventory item: {0}", item);
}
return true;
}
///
/// Tries to add the given quantity to the inventory item with the given ID without going over the maximum quantity allowed for an item.
///
///
///
/// The quantity actually added to the item.
public async Task AddStockAsync(InventoryItemId itemId, int quantity)
{
IReliableDictionary inventoryItems =
await this.StateManager.GetOrAddAsync>(InventoryItemDictionaryName);
int quantityAdded = 0;
ServiceEventSource.Current.ServiceMessage(this, "Received add stock request. Item: {0}. Quantity: {1}.", itemId, quantity);
using (ITransaction tx = this.StateManager.CreateTransaction())
{
// Try to get the InventoryItem for the ID in the request.
ConditionalValue item = await inventoryItems.TryGetValueAsync(tx, itemId);
// We can only update the stock for InventoryItems in the system - we are not adding new items here.
if (item.HasValue)
{
// Update the stock quantity of the item.
// This only updates the copy of the Inventory Item that's in local memory here;
// It's not yet saved in the dictionary.
quantityAdded = item.Value.AddStock(quantity);
// We have to store the item back in the dictionary in order to actually save it.
// This will then replicate the updated item for
await inventoryItems.SetAsync(tx, item.Value.Id, item.Value);
}
// nothing will happen unless we commit the transaction!
await tx.CommitAsync();
ServiceEventSource.Current.ServiceMessage(
this,
"Add stock complete. Item: {0}. Added: {1}. Total: {2}",
item.Value.Id,
quantityAdded,
item.Value.AvailableStock);
}
return quantityAdded;
}
///
/// Removes the given quantity of stock from an in item in the inventory.
///
///
/// int: Returns the quantity removed from stock.
public async Task RemoveStockAsync(InventoryItemId itemId, int quantity, CustomerOrderActorMessageId amId)
{
ServiceEventSource.Current.ServiceMessage(this, "inside remove stock {0}|{1}", amId.GetHashCode(), amId.GetHashCode());
IReliableDictionary inventoryItems =
await this.StateManager.GetOrAddAsync>(InventoryItemDictionaryName);
IReliableDictionary recentRequests =
await this.StateManager.GetOrAddAsync>(ActorMessageDictionaryName);
IReliableDictionary> requestHistory =
await
this.StateManager.GetOrAddAsync>>(RequestHistoryDictionaryName);
int removed = 0;
ServiceEventSource.Current.ServiceMessage(this, "Received remove stock request. Item: {0}. Quantity: {1}.", itemId, quantity);
using (ITransaction tx = this.StateManager.CreateTransaction())
{
//first let's see if this is a duplicate request
ConditionalValue previousRequest = await recentRequests.TryGetValueAsync(tx, amId);
if (!previousRequest.HasValue)
{
//first time we've seen the request or it was a dupe from so long ago we have forgotten
// Try to get the InventoryItem for the ID in the request.
ConditionalValue item = await inventoryItems.TryGetValueAsync(tx, itemId);
// We can only remove stock for InventoryItems in the system.
if (item.HasValue)
{
// Update the stock quantity of the item.
// This only updates the copy of the Inventory Item that's in local memory here;
// It's not yet saved in the dictionary.
removed = item.Value.RemoveStock(quantity);
// We have to store the item back in the dictionary in order to actually save it.
// This will then replicate the updated item
await inventoryItems.SetAsync(tx, itemId, item.Value);
//we also have to make a note that we have returned this result, so that we can protect
//ourselves from stale or duplicate requests that come back later
await requestHistory.SetAsync(tx, amId, new Tuple(itemId, removed));
ServiceEventSource.Current.ServiceMessage(
this,
"Removed stock complete. Item: {0}. Removed: {1}. Remaining: {2}",
item.Value.Id,
removed,
item.Value.AvailableStock);
}
}
else
{
//this is a duplicate request. We need to send back the result we already came up with and hope they get it this time
//find the previous result and send it back
ConditionalValue> previousResponse = await requestHistory.TryGetValueAsync(tx, amId);
if (previousResponse.HasValue)
{
removed = previousResponse.Value.Item2;
ServiceEventSource.Current.ServiceMessage(
this,
"Retrieved previous response for request {0}, from {1}, for Item {2} and quantity {3}",
amId,
previousRequest.Value,
previousResponse.Value.Item1,
previousResponse.Value.Item2);
}
else
{
//we've seen the request before but we don't have a record for what we responded, inconsistent state
ServiceEventSource.Current.ServiceMessage(
this,
"Inconsistent State: recieved duplicate request {0} but don't have matching response in history",
amId);
this.Partition.ReportFault(System.Fabric.FaultType.Transient);
}
//note about duplicate Requests: technically if a duplicate request comes in and we have
//sufficient invintory to return more now that we did previously, we could return more of the order and decrement
//the difference to reduce the total number of round trips. This optimization is not currently implemented
}
//always update the datetime for the given request
await recentRequests.SetAsync(tx, amId, DateTime.UtcNow);
// nothing will happen unless we commit the transaction!
ServiceEventSource.Current.Message("Committing Changes in Inventory Service");
await tx.CommitAsync();
ServiceEventSource.Current.Message("Inventory Service Changes Committed");
}
ServiceEventSource.Current.Message("Removed {0} of item {1}", removed, itemId);
return removed;
}
public async Task IsItemInInventoryAsync(InventoryItemId itemId, CancellationToken ct)
{
ServiceEventSource.Current.Message("checking item {0} to see if it is in inventory", itemId);
IReliableDictionary inventoryItems =
await this.StateManager.GetOrAddAsync>(InventoryItemDictionaryName);
await this.PrintInventoryItemsAsync(inventoryItems, ct);
using (ITransaction tx = this.StateManager.CreateTransaction())
{
ConditionalValue item = await inventoryItems.TryGetValueAsync(tx, itemId);
return item.HasValue;
}
}
///
/// Retrieves a customer-specific view (defined in the InventoryItemView class in the Fabrikam Common namespace)
/// of all items in the IReliableDictionary in InventoryService. Only items with a CustomerAvailableStock greater than
/// zero are returned as a business logic constraint to reduce overordering.
///
/// IEnumerable of InventoryItemView
public async Task> GetCustomerInventoryAsync(CancellationToken ct)
{
IReliableDictionary inventoryItems =
await this.StateManager.GetOrAddAsync>(InventoryItemDictionaryName);
ServiceEventSource.Current.Message("Called GetCustomerInventory to return InventoryItemView");
await this.PrintInventoryItemsAsync(inventoryItems, ct);
IList results = new List();
using (ITransaction tx = this.StateManager.CreateTransaction())
{
ServiceEventSource.Current.Message("Generating item views for {0} items", await inventoryItems.GetCountAsync(tx));
IAsyncEnumerator> enumerator =
(await inventoryItems.CreateEnumerableAsync(tx)).GetAsyncEnumerator();
while (await enumerator.MoveNextAsync(ct))
{
if (enumerator.Current.Value.AvailableStock > 0)
{
results.Add(enumerator.Current.Value);
}
}
}
return results;
}
///
/// NOTE: This should not be used in published MVP code.
/// This function allows us to remove inventory items from inventory.
///
///
///
public async Task DeleteInventoryItemAsync(InventoryItemId itemId)
{
IReliableDictionary inventoryItems =
await this.StateManager.GetOrAddAsync>(InventoryItemDictionaryName);
using (ITransaction tx = this.StateManager.CreateTransaction())
{
await inventoryItems.TryRemoveAsync(tx, itemId);
await tx.CommitAsync();
}
}
///
/// Creates a new communication listener
///
///
protected override IEnumerable CreateServiceReplicaListeners()
{
return new[]
{
new ServiceReplicaListener(context => this.CreateServiceRemotingListener(context))
};
}
//Dataloss testing can be triggered via powershell. To do so, run the following commands as a script
//Connect-ServiceFabricCluster
//$s = "fabric:/WebReferenceApplication/InventoryService"
//$p = Get-ServiceFabricApplication | Get-ServiceFabricService -ServiceName $s | Get-ServiceFabricPartition | Select -First 1
//$p | Invoke-ServiceFabricPartitionDataLoss -DataLossMode FullDataLoss -ServiceName $s
protected override async Task OnDataLossAsync(RestoreContext restoreCtx, CancellationToken cancellationToken)
{
ServiceEventSource.Current.ServiceMessage(this, "OnDataLoss Invoked!");
this.SetupBackupManager();
try
{
string backupFolder;
if (this.backupStorageType == BackupManagerType.None)
{
//since we have no backup configured, we return false to indicate
//that state has not changed. This replica will become the basis
//for future replica builds
return false;
}
else
{
backupFolder = await this.backupManager.RestoreLatestBackupToTempLocation(cancellationToken);
}
ServiceEventSource.Current.ServiceMessage(this, "Restoration Folder Path " + backupFolder);
RestoreDescription restoreRescription = new RestoreDescription(backupFolder, RestorePolicy.Force);
await restoreCtx.RestoreAsync(restoreRescription, cancellationToken);
ServiceEventSource.Current.ServiceMessage(this, "Restore completed");
DirectoryInfo tempRestoreDirectory = new DirectoryInfo(backupFolder);
tempRestoreDirectory.Delete(true);
return true;
}
catch (Exception e)
{
ServiceEventSource.Current.ServiceMessage(this, "Restoration failed: " + "{0} {1}" + e.GetType() + e.Message);
throw;
}
}
protected override Task RunAsync(CancellationToken cancellationToken)
{
try
{
ServiceEventSource.Current.ServiceMessage(this, "inside RunAsync for Inventory Service");
return Task.WhenAll(
this.PeriodicInventoryCheck(cancellationToken),
this.PeriodicOldMessageTrimming(cancellationToken),
this.PeriodicTakeBackupAsync(cancellationToken));
}
catch (Exception e)
{
ServiceEventSource.Current.ServiceMessage(this, "RunAsync Failed, {0}", e);
throw;
}
}
private async Task BackupCallbackAsync(BackupInfo backupInfo, CancellationToken cancellationToken)
{
ServiceEventSource.Current.ServiceMessage(this, "Inside backup callback for replica {0}|{1}", this.Context.PartitionId, this.Context.ReplicaId);
long totalBackupCount;
IReliableDictionary backupCountDictionary =
await this.StateManager.GetOrAddAsync>(BackupCountDictionaryName);
using (ITransaction tx = this.StateManager.CreateTransaction())
{
ConditionalValue value = await backupCountDictionary.TryGetValueAsync(tx, "backupCount");
if (!value.HasValue)
{
totalBackupCount = 0;
}
else
{
totalBackupCount = value.Value;
}
await backupCountDictionary.SetAsync(tx, "backupCount", ++totalBackupCount);
await tx.CommitAsync();
}
ServiceEventSource.Current.Message("Backup count dictionary updated, total backup count is {0}", totalBackupCount);
try
{
ServiceEventSource.Current.ServiceMessage(this, "Archiving backup");
await this.backupManager.ArchiveBackupAsync(backupInfo, cancellationToken);
ServiceEventSource.Current.ServiceMessage(this, "Backup archived");
}
catch (Exception e)
{
ServiceEventSource.Current.ServiceMessage(this, "Archive of backup failed: Source: {0} Exception: {1}", backupInfo.Directory, e.Message);
}
await this.backupManager.DeleteBackupsAsync(cancellationToken);
ServiceEventSource.Current.Message("Backups deleted");
return true;
}
private async Task PrintInventoryItemsAsync(IReliableDictionary inventoryItems, CancellationToken cancellationToken)
{
using (ITransaction tx = this.StateManager.CreateTransaction())
{
ServiceEventSource.Current.Message("Printing Inventory for {0} items:", await inventoryItems.GetCountAsync(tx));
IAsyncEnumerator> enumerator =
(await inventoryItems.CreateEnumerableAsync(tx)).GetAsyncEnumerator();
while (await enumerator.MoveNextAsync(cancellationToken))
{
ServiceEventSource.Current.Message("ID:{0}|Item:{1}", enumerator.Current.Key, enumerator.Current.Value);
}
}
}
private async Task PeriodicOldMessageTrimming(CancellationToken cancellationToken)
{
IReliableDictionary recentRequests =
await this.StateManager.GetOrAddAsync>(ActorMessageDictionaryName);
IReliableDictionary> requestHistory =
await
this.StateManager.GetOrAddAsync>>(RequestHistoryDictionaryName);
while (!cancellationToken.IsCancellationRequested)
{
using (ITransaction tx = this.StateManager.CreateTransaction())
{
IAsyncEnumerator> enumerator =
(await recentRequests.CreateEnumerableAsync(tx)).GetAsyncEnumerator();
while (await enumerator.MoveNextAsync(cancellationToken))
{
//if we have a record of a message that is older than 2 hours from current time, then remove that record
//from both of the stale message tracking dictionaries.
if (enumerator.Current.Value < (DateTime.UtcNow.AddHours(-2)))
{
await recentRequests.TryRemoveAsync(tx, enumerator.Current.Key);
await requestHistory.TryRemoveAsync(tx, enumerator.Current.Key);
}
}
await tx.CommitAsync();
}
//sleep for 5 minutes then scan again
await Task.Delay(TimeSpan.FromMinutes(5), cancellationToken);
}
}
private async Task PeriodicInventoryCheck(CancellationToken cancellationToken)
{
IReliableDictionary inventoryItems =
await this.StateManager.GetOrAddAsync>(InventoryItemDictionaryName);
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
IList items = new List();
using (ITransaction tx = this.StateManager.CreateTransaction())
{
ServiceEventSource.Current.ServiceMessage(this, "Checking inventory stock for {0} items.", await inventoryItems.GetCountAsync(tx));
IAsyncEnumerator> enumerator =
(await inventoryItems.CreateEnumerableAsync(tx)).GetAsyncEnumerator();
while (await enumerator.MoveNextAsync(cancellationToken))
{
InventoryItem item = enumerator.Current.Value;
//Check if stock is below restockThreshold and if the item is not already on reorder
if ((item.AvailableStock <= item.RestockThreshold) && !item.OnReorder)
{
items.Add(enumerator.Current.Value);
}
}
}
foreach (InventoryItem item in items)
{
cancellationToken.ThrowIfCancellationRequested();
try
{
ServiceUriBuilder builder = new ServiceUriBuilder(RestockRequestManagerServiceName);
IRestockRequestManager restockRequestManagerClient = ServiceProxy.Create(
builder.ToUri(),
new ServicePartitionKey());
// we reduce the quantity passed in to RestockRequest to ensure we don't overorder
RestockRequest newRequest = new RestockRequest(item.Id, (item.MaxStockThreshold - item.AvailableStock));
InventoryItem updatedItem = new InventoryItem(
item.Description,
item.Price,
item.AvailableStock,
item.RestockThreshold,
item.MaxStockThreshold,
item.Id,
true);
// TODO: this call needs to be idempotent in case we fail to update the InventoryItem after this completes.
await restockRequestManagerClient.AddRestockRequestAsync(newRequest);
// Write operations take an exclusive lock on an item, which means we can't do anything else with that item while the transaction is open.
// If something blocks before the transaction is committed, the open transaction on the item will prevent all operations on it, including reads.
// Once the transaction commits, the lock is released and other operations on the item can proceed.
// Operations on the transaction all have timeouts to prevent deadlocking an item,
// but we should do as little work inside the transaction as possible that is not related to the transaction itself.
using (ITransaction tx = this.StateManager.CreateTransaction())
{
await inventoryItems.TryUpdateAsync(tx, item.Id, updatedItem, item);
await tx.CommitAsync();
}
ServiceEventSource.Current.ServiceMessage(
this,
"Restock order placed. Item ID: {0}. Quantity: {1}",
newRequest.ItemId,
newRequest.Quantity);
}
catch (Exception e)
{
ServiceEventSource.Current.ServiceMessage(this, "Failed to place restock order for item {0}. {1}", item.Id, e.ToString());
}
}
await Task.Delay(TimeSpan.FromSeconds(30), cancellationToken);
}
}
private async Task PeriodicTakeBackupAsync(CancellationToken cancellationToken)
{
long backupsTaken = 0;
this.SetupBackupManager();
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
if (this.backupStorageType == BackupManagerType.None)
{
break;
}
else
{
await Task.Delay(TimeSpan.FromSeconds(this.backupManager.backupFrequencyInSeconds));
BackupDescription backupDescription = new BackupDescription(BackupOption.Full, this.BackupCallbackAsync);
await this.BackupAsync(backupDescription, TimeSpan.FromHours(1), cancellationToken);
backupsTaken++;
ServiceEventSource.Current.ServiceMessage(this, "Backup {0} taken", backupsTaken);
}
}
}
private void SetupBackupManager()
{
string partitionId = this.Context.PartitionId.ToString("N");
long minKey = ((Int64RangePartitionInformation) this.Partition.PartitionInfo).LowKey;
long maxKey = ((Int64RangePartitionInformation) this.Partition.PartitionInfo).HighKey;
if (this.Context.CodePackageActivationContext != null)
{
ICodePackageActivationContext codePackageContext = this.Context.CodePackageActivationContext;
ConfigurationPackage configPackage = codePackageContext.GetConfigurationPackageObject("Config");
ConfigurationSection configSection = configPackage.Settings.Sections["Inventory.Service.Settings"];
string backupSettingValue = configSection.Parameters["BackupMode"].Value;
if (string.Equals(backupSettingValue, "none", StringComparison.InvariantCultureIgnoreCase))
{
this.backupStorageType = BackupManagerType.None;
}
else if (string.Equals(backupSettingValue, "azure", StringComparison.InvariantCultureIgnoreCase))
{
this.backupStorageType = BackupManagerType.Azure;
ConfigurationSection azureBackupConfigSection = configPackage.Settings.Sections["Inventory.Service.BackupSettings.Azure"];
this.backupManager = new AzureBlobBackupManager(azureBackupConfigSection, partitionId, minKey, maxKey, codePackageContext.TempDirectory);
}
else if (string.Equals(backupSettingValue, "local", StringComparison.InvariantCultureIgnoreCase))
{
this.backupStorageType = BackupManagerType.Local;
ConfigurationSection localBackupConfigSection = configPackage.Settings.Sections["Inventory.Service.BackupSettings.Local"];
this.backupManager = new DiskBackupManager(localBackupConfigSection, partitionId, minKey, maxKey, codePackageContext.TempDirectory);
}
else
{
throw new ArgumentException("Unknown backup type");
}
ServiceEventSource.Current.ServiceMessage(this, "Backup Manager Set Up");
}
}
private enum BackupManagerType
{
Azure,
Local,
None
};
}
}
================================================
FILE: ReferenceApp/Inventory.Service/LocalBackupStore.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Inventory.Service
{
using System;
using System.Collections.Generic;
using System.Fabric.Description;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.ServiceFabric.Data;
public class DiskBackupManager : IBackupStore
{
private string PartitionArchiveFolder;
private string PartitionTempDirectory;
private long backupFrequencyInSeconds;
private int MaxBackupsToKeep;
private long keyMin;
private long keyMax;
public DiskBackupManager(ConfigurationSection configSection, string partitionId, long keymin, long keymax, string codePackageTempDirectory)
{
this.keyMin = keymin;
this.keyMax = keymax;
string BackupArchivalPath = configSection.Parameters["BackupArchivalPath"].Value;
this.backupFrequencyInSeconds = long.Parse(configSection.Parameters["BackupFrequencyInSeconds"].Value);
this.MaxBackupsToKeep = int.Parse(configSection.Parameters["MaxBackupsToKeep"].Value);
this.PartitionArchiveFolder = Path.Combine(BackupArchivalPath, "Backups", partitionId);
this.PartitionTempDirectory = Path.Combine(codePackageTempDirectory, partitionId);
ServiceEventSource.Current.Message(
"DiskBackupManager constructed IntervalinSec:{0}, archivePath:{1}, tempPath:{2}, backupsToKeep:{3}",
this.backupFrequencyInSeconds,
this.PartitionArchiveFolder,
this.PartitionTempDirectory,
this.MaxBackupsToKeep);
}
long IBackupStore.backupFrequencyInSeconds
{
get { return this.backupFrequencyInSeconds; }
}
public Task ArchiveBackupAsync(BackupInfo backupInfo, CancellationToken cancellationToken)
{
string fullArchiveDirectory = Path.Combine(
this.PartitionArchiveFolder,
string.Format("{0}_{1}_{2}", Guid.NewGuid().ToString("N"), this.keyMin, this.keyMax));
DirectoryInfo dirInfo = new DirectoryInfo(fullArchiveDirectory);
dirInfo.Create();
string fullArchivePath = Path.Combine(fullArchiveDirectory, "Backup.zip");
ZipFile.CreateFromDirectory(backupInfo.Directory, fullArchivePath, CompressionLevel.Fastest, false);
DirectoryInfo backupDirectory = new DirectoryInfo(backupInfo.Directory);
backupDirectory.Delete(true);
return Task.FromResult(true);
}
public Task RestoreLatestBackupToTempLocation(CancellationToken cancellationToken)
{
ServiceEventSource.Current.Message("Restoring backup to temp source:{0} destination:{1}", this.PartitionArchiveFolder, this.PartitionTempDirectory);
DirectoryInfo dirInfo = new DirectoryInfo(this.PartitionArchiveFolder);
string backupZip = dirInfo.GetDirectories().OrderByDescending(x => x.LastWriteTime).First().FullName;
string zipPath = Path.Combine(backupZip, "Backup.zip");
ServiceEventSource.Current.Message("latest zip backup is {0}", zipPath);
DirectoryInfo directoryInfo = new DirectoryInfo(this.PartitionTempDirectory);
if (directoryInfo.Exists)
{
directoryInfo.Delete(true);
}
directoryInfo.Create();
ZipFile.ExtractToDirectory(zipPath, this.PartitionTempDirectory);
ServiceEventSource.Current.Message("Zip backup {0} extracted to {1}", zipPath, this.PartitionTempDirectory);
return Task.FromResult(this.PartitionTempDirectory);
}
public async Task DeleteBackupsAsync(CancellationToken cancellationToken)
{
await Task.Run(
() =>
{
ServiceEventSource.Current.Message("deleting old backups");
if (!Directory.Exists(this.PartitionArchiveFolder))
{
//Nothing to delete; Backups may not even have been created for the partition
return;
}
DirectoryInfo dirInfo = new DirectoryInfo(this.PartitionArchiveFolder);
IEnumerable oldBackups = dirInfo.GetDirectories().OrderByDescending(x => x.LastWriteTime).Skip(this.MaxBackupsToKeep);
foreach (DirectoryInfo oldBackup in oldBackups)
{
ServiceEventSource.Current.Message("Deleting old backup {0}", oldBackup.FullName);
oldBackup.Delete(true);
}
ServiceEventSource.Current.Message("Old backups deleted");
return;
},
cancellationToken);
}
}
}
================================================
FILE: ReferenceApp/Inventory.Service/PackageRoot/Config/Settings.xml
================================================
================================================
FILE: ReferenceApp/Inventory.Service/PackageRoot/ServiceManifest.xml
================================================
Inventory.Service.exe
================================================
FILE: ReferenceApp/Inventory.Service/Program.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Inventory.Service
{
using System;
using System.Diagnostics;
using System.Threading;
using Microsoft.ServiceFabric.Services.Runtime;
public class Program
{
public static void Main(string[] args)
{
try
{
ServiceRuntime.RegisterServiceAsync(InventoryService.InventoryServiceType, (context) => new InventoryService(context)).GetAwaiter().GetResult();
ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(InventoryService).Name);
Thread.Sleep(Timeout.Infinite);
}
catch (Exception e)
{
ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString());
throw;
}
}
}
}
================================================
FILE: ReferenceApp/Inventory.Service/Properties/AssemblyInfo.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("InventoryService")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("InventoryService")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("714b1c61-ffa8-4a5e-8dd2-d0a290677293")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: InternalsVisibleTo("Inventory.UnitTests")]
================================================
FILE: ReferenceApp/Inventory.Service/ServiceEventSource.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Inventory.Service
{
using System;
using System.Diagnostics.Tracing;
using System.Threading.Tasks;
using Microsoft.ServiceFabric.Services.Runtime;
[EventSource(Name = "MyCompany-Web_UIApplication-InventoryService")]
internal sealed class ServiceEventSource : EventSource
{
private const int MessageEventId = 1;
// For very high-frequency events it might be advantageous to raise events using WriteEventCore API.
// This results in more efficient parameter handling, but requires explicit allocation of EventData structure and unsafe code.
// To enable this code path, define UNSAFE conditional compilation symbol and turn on unsafe code support in project properties.
private const int ServiceMessageEventId = 2;
private const int ServiceTypeRegisteredEventId = 3;
private const int ServiceHostInitializationFailedEventId = 4;
// A pair of events sharing the same name prefix with a "Start"/"Stop" suffix implicitly marks boundaries of an event tracing activity.
// These activities can be automatically picked up by debugging and profiling tools, which can compute their execution time, child activities,
// and other statistics.
private const int ServiceRequestStartEventId = 5;
private const int ServiceRequestStopEventId = 6;
private const int ServiceRequestFailedEventId = 7;
public static readonly ServiceEventSource Current = new ServiceEventSource();
static ServiceEventSource()
{
// A workaround for the problem where ETW activities do not get tracked until Tasks infrastructure is initialized.
// This problem will be fixed in .NET Framework 4.6.2.
Task.Run(() => { }).Wait();
}
// Instance constructor is private to enforce singleton semantics
private ServiceEventSource() : base()
{
}
// Define an instance method for each event you want to record and apply an [Event] attribute to it.
// The method name is the name of the event.
// Pass any parameters you want to record with the event (only primitive integer types, DateTime, Guid & string are allowed).
// Each event method implementation should check whether the event source is enabled, and if it is, call WriteEvent() method to raise the event.
// The number and types of arguments passed to every event method must exactly match what is passed to WriteEvent().
// Put [NonEvent] attribute on all methods that do not define an event.
// For more information see https://msdn.microsoft.com/en-us/library/system.diagnostics.tracing.eventsource.aspx
[NonEvent]
public void Message(string message, params object[] args)
{
if (this.IsEnabled())
{
string finalMessage = string.Format(message, args);
this.Message(finalMessage);
}
}
[Event(MessageEventId, Level = EventLevel.Informational, Message = "{0}")]
public void Message(string message)
{
if (this.IsEnabled())
{
this.WriteEvent(MessageEventId, message);
}
}
[NonEvent]
public void ServiceMessage(StatefulService service, string message, params object[] args)
{
if (this.IsEnabled())
{
string finalMessage = string.Format(message, args);
this.ServiceMessage(
service.Context.ServiceName.ToString(),
service.Context.ServiceTypeName,
service.Context.ReplicaId,
service.Context.PartitionId,
service.Context.CodePackageActivationContext.ApplicationName,
service.Context.CodePackageActivationContext.ApplicationTypeName,
service.Context.NodeContext.NodeName,
finalMessage);
}
}
[Event(ServiceTypeRegisteredEventId, Level = EventLevel.Informational, Message = "Service host process {0} registered service type {1}",
Keywords = Keywords.ServiceInitialization)]
public void ServiceTypeRegistered(int hostProcessId, string serviceType)
{
this.WriteEvent(ServiceTypeRegisteredEventId, hostProcessId, serviceType);
}
[Event(ServiceHostInitializationFailedEventId, Level = EventLevel.Error, Message = "Service host initialization failed",
Keywords = Keywords.ServiceInitialization)]
public void ServiceHostInitializationFailed(string exception)
{
this.WriteEvent(ServiceHostInitializationFailedEventId, exception);
}
[Event(ServiceRequestStartEventId, Level = EventLevel.Informational, Message = "Service request '{0}' started", Keywords = Keywords.Requests)]
public void ServiceRequestStart(string requestTypeName)
{
this.WriteEvent(ServiceRequestStartEventId, requestTypeName);
}
[Event(ServiceRequestStopEventId, Level = EventLevel.Informational, Message = "Service request '{0}' finished", Keywords = Keywords.Requests)]
public void ServiceRequestStop(string requestTypeName)
{
this.WriteEvent(ServiceRequestStopEventId, requestTypeName);
}
[Event(ServiceRequestFailedEventId, Level = EventLevel.Error, Message = "Service request '{0}' failed", Keywords = Keywords.Requests)]
public void ServiceRequestFailed(string requestTypeName, string exception)
{
this.WriteEvent(ServiceRequestFailedEventId, exception);
}
[Event(ServiceMessageEventId, Level = EventLevel.Informational, Message = "{7}")]
private void ServiceMessage(
string serviceName,
string serviceTypeName,
long replicaOrInstanceId,
Guid partitionId,
string applicationName,
string applicationTypeName,
string nodeName,
string message)
{
this.WriteEvent(
ServiceMessageEventId,
serviceName,
serviceTypeName,
replicaOrInstanceId,
partitionId,
applicationName,
applicationTypeName,
nodeName,
message);
}
// Event keywords can be used to categorize events.
// Each keyword is a bit flag. A single event can be associated with multiple keywords (via EventAttribute.Keywords property).
// Keywords must be defined as a public class named 'Keywords' inside EventSource that uses them.
public static class Keywords
{
public const EventKeywords Requests = (EventKeywords) 0x1L;
public const EventKeywords ServiceInitialization = (EventKeywords) 0x2L;
}
}
}
================================================
FILE: ReferenceApp/Inventory.Service/StatefulServiceParameters.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Inventory.Service
{
using System;
using System.Fabric;
internal class StatefulServiceParameters
{
public StatefulServiceParameters(
CodePackageActivationContext codePackageActivationContext, byte[] initializationData, Guid partitionId, Uri serviceName, string serviceTypeName,
long replicaId)
{
this.CodePackageActivationContext = codePackageActivationContext;
this.InitializationData = initializationData;
this.PartitionId = partitionId;
this.ServiceName = serviceName;
this.ServiceTypeName = serviceTypeName;
this.ReplicaId = replicaId;
}
public CodePackageActivationContext CodePackageActivationContext { get; }
public byte[] InitializationData { get; }
public Guid PartitionId { get; }
public long ReplicaId { get; }
public Uri ServiceName { get; }
public string ServiceTypeName { get; }
}
}
================================================
FILE: ReferenceApp/Inventory.Service/packages.config
================================================
================================================
FILE: ReferenceApp/Inventory.UnitTests/Inventory.UnitTests.csproj
================================================
Debug
AnyCPU
{71E58E09-BEE4-4B19-BCF7-4C9BD71514FB}
Library
Properties
Inventory.UnitTests
Inventory.UnitTests
v4.5.2
512
{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
10.0
$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
False
UnitTest
true
bin\x64\Debug\
DEBUG;TRACE
full
x64
prompt
MinimumRecommendedRules.ruleset
bin\x64\Release\
TRACE
true
pdbonly
x64
prompt
MinimumRecommendedRules.ruleset
..\packages\Microsoft.ServiceFabric.Data.2.6.204\lib\net45\Microsoft.ServiceFabric.Data.dll
..\packages\Microsoft.ServiceFabric.Data.2.6.204\lib\net45\Microsoft.ServiceFabric.Data.Interfaces.dll
True
..\packages\Microsoft.ServiceFabric.FabricTransport.Internal.2.6.204\lib\net45\Microsoft.ServiceFabric.FabricTransport.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\Microsoft.ServiceFabric.Internal.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\Microsoft.ServiceFabric.Internal.Strings.dll
..\packages\Microsoft.ServiceFabric.Services.2.6.204\lib\net45\Microsoft.ServiceFabric.Services.dll
..\packages\Microsoft.ServiceFabric.Services.Remoting.2.6.204\lib\net45\Microsoft.ServiceFabric.Services.Remoting.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.dll
True
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Management.ServiceModel.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Management.ServiceModel.XmlSerializers.dll
..\packages\Microsoft.ServiceFabric.5.6.204\lib\net45\System.Fabric.Strings.dll
True
{9EC0063F-489E-43FE-94B5-BF5F89977CD3}
Common
{7e9c2dfd-71a5-496d-aa4d-5ec53eaeb9ae}
Inventory.Domain
{714b1c61-ffa8-4a5e-8dd2-d0a290677293}
Inventory.Service
{00e00484-bd00-40cd-b2cc-1cfa8e893972}
Mocks
{a44fc2c5-6781-447e-80f5-7265b960b36a}
RestockRequest.Domain
False
False
False
False
================================================
FILE: ReferenceApp/Inventory.UnitTests/InventoryServiceTests.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Inventory.UnitTests
{
using Common;
using Inventory.Domain;
using Inventory.Service;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Mocks;
using System;
using System.Fabric;
using System.Threading;
using System.Threading.Tasks;
[TestClass]
public class InventoryServiceTests
{
private static ICodePackageActivationContext codePackageContext = new MockCodePackageActivationContext(
"fabric:/someapp",
"SomeAppType",
"Code",
"1.0.0.0",
Guid.NewGuid().ToString(),
@"C:\Log",
@"C:\Temp",
@"C:\Work",
"ServiceManifest",
"1.0.0.0"
);
StatefulServiceContext statefulServiceContext = new StatefulServiceContext(
new NodeContext("Node0", new NodeId(0, 1), 0, "NodeType1", "TEST.MACHINE"),
codePackageContext,
InventoryService.InventoryServiceType,
new Uri("fabric:/someapp/someservice"),
null,
Guid.NewGuid(),
long.MaxValue
);
[TestMethod]
public async Task TestCreateAndIsItemInInventoryAsync()
{
MockReliableStateManager stateManager = new MockReliableStateManager();
InventoryService target = new InventoryService(statefulServiceContext, stateManager);
InventoryItem expected = new InventoryItem("test", 1, 10, 1, 10);
await target.CreateInventoryItemAsync(expected);
bool resultTrue = await target.IsItemInInventoryAsync(expected.Id, CancellationToken.None);
bool resultFalse = await target.IsItemInInventoryAsync(new InventoryItemId(), CancellationToken.None);
Assert.IsTrue(resultTrue);
Assert.IsFalse(resultFalse);
}
[TestMethod]
public async Task TestAddStock()
{
int expectedQuantity = 10;
int quantityToAdd = 3;
MockReliableStateManager stateManager = new MockReliableStateManager();
InventoryService target = new InventoryService(statefulServiceContext, stateManager);
InventoryItem item = new InventoryItem("test", 1, expectedQuantity - quantityToAdd, 1, expectedQuantity);
RestockRequest.Domain.RestockRequest request = new RestockRequest.Domain.RestockRequest(item.Id, quantityToAdd);
await target.CreateInventoryItemAsync(item);
int actualAdded = await target.AddStockAsync(request.ItemId, quantityToAdd);
Assert.AreEqual(quantityToAdd, actualAdded);
Assert.AreEqual(item.AvailableStock, expectedQuantity);
}
[TestMethod]
public async Task TestRemoveStock()
{
int expectedQuantity = 5;
int quantityToRemove = 3;
MockReliableStateManager stateManager = new MockReliableStateManager();
InventoryService target = new InventoryService(statefulServiceContext, stateManager);
InventoryItem item = new InventoryItem("test", 1, expectedQuantity + quantityToRemove, 1, expectedQuantity);
await target.CreateInventoryItemAsync(item);
int actualRemoved = await target.RemoveStockAsync(item.Id, quantityToRemove, CustomerOrderActorMessageId.GetRandom());
Assert.AreEqual(quantityToRemove, actualRemoved);
Assert.AreEqual(expectedQuantity, item.AvailableStock);
}
[TestMethod]
public async Task TestRemoveStockWithDuplicateRequest()
{
int totalStartingStock = 8;
int expectedQuantity = 5;
int quantityToRemove = 3;
MockReliableStateManager stateManager = new MockReliableStateManager();
InventoryService target = new InventoryService(statefulServiceContext, stateManager);
InventoryItem item = new InventoryItem("test", 1, totalStartingStock, 1, expectedQuantity);
await target.CreateInventoryItemAsync(item);
CustomerOrderActorMessageId cmid = CustomerOrderActorMessageId.GetRandom();
int actualRemoved = await target.RemoveStockAsync(item.Id, quantityToRemove, cmid);
Assert.AreEqual(quantityToRemove, actualRemoved);
Assert.AreEqual(expectedQuantity, item.AvailableStock);
//save the current availablestock so we can check to be sure it doesn't change
int priorAvailableStock = item.AvailableStock;
//but now lets say that the reciever didn't get the response and so sends the exact same request again
int actualRemoved2 = await target.RemoveStockAsync(item.Id, quantityToRemove, cmid);
//in this case the response for the amount removed should be the same
Assert.AreEqual(actualRemoved, actualRemoved2);
//also, since the request was a duplicate the remaining invintory should be the same as it was before.
Assert.AreEqual(item.AvailableStock, priorAvailableStock);
}
}
}
================================================
FILE: ReferenceApp/Inventory.UnitTests/Properties/AssemblyInfo.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Inventory.UnitTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Inventory.UnitTests")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("71e58e09-bee4-4b19-bcf7-4c9bd71514fb")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
================================================
FILE: ReferenceApp/Inventory.UnitTests/app.config
================================================
================================================
FILE: ReferenceApp/Inventory.UnitTests/packages.config
================================================
================================================
FILE: ReferenceApp/Mocks/MockActorStateManager.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Mocks
{
using Microsoft.ServiceFabric.Actors.Runtime;
using Microsoft.ServiceFabric.Data;
using Microsoft.ServiceFabric.Data.Collections;
using Microsoft.ServiceFabric.Data.Notifications;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Fabric;
using System.Threading;
using System.Threading.Tasks;
public class MockActorStateManager : IActorStateManager
{
private ConcurrentDictionary store = new ConcurrentDictionary();
public Task AddOrUpdateStateAsync(string stateName, T addValue, Func updateValueFactory, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task AddStateAsync(string stateName, T value, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task ContainsStateAsync(string stateName, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task GetOrAddStateAsync(string stateName, T value, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task GetStateAsync(string stateName, CancellationToken cancellationToken = default(CancellationToken))
{
object result;
this.store.TryGetValue(stateName, out result);
return Task.FromResult((T)result);
}
public Task> GetStateNamesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task RemoveStateAsync(string stateName, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task SetStateAsync(string stateName, T value, CancellationToken cancellationToken = default(CancellationToken))
{
this.store.AddOrUpdate(stateName, value, (key, oldvalue) => value);
return Task.FromResult(true);
}
public Task TryAddStateAsync(string stateName, T value, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task> TryGetStateAsync(string stateName, CancellationToken cancellationToken = default(CancellationToken))
{
object item;
bool result = this.store.TryGetValue(stateName, out item);
return Task.FromResult(new ConditionalValue(result, (T)item));
}
public Task TryRemoveStateAsync(string stateName, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task ClearCacheAsync(CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task SaveStateAsync(CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
}
}
================================================
FILE: ReferenceApp/Mocks/MockAsyncEnumerable.cs
================================================
namespace Mocks
{
using Microsoft.ServiceFabric.Data;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
///
/// Simple wrapper for a synchronous IEnumerable of T.
///
///
internal class MockAsyncEnumerable : IAsyncEnumerable
{
private IEnumerable enumerable;
public MockAsyncEnumerable(IEnumerable enumerable)
{
this.enumerable = enumerable;
}
public IAsyncEnumerator GetAsyncEnumerator()
{
return new MockAsyncEnumerator(this.enumerable.GetEnumerator());
}
}
///
/// Simply wrapper for a synchronous IEnumerator of T.
///
///
internal class MockAsyncEnumerator : IAsyncEnumerator
{
private readonly IEnumerator enumerator;
public MockAsyncEnumerator(IEnumerator enumerator)
{
this.enumerator = enumerator;
}
public T Current
{
get
{
return this.enumerator.Current;
}
}
public void Dispose()
{
this.enumerator.Dispose();
}
public Task MoveNextAsync(CancellationToken cancellationToken)
{
return Task.FromResult(this.enumerator.MoveNext());
}
public void Reset()
{
this.enumerator.Reset();
}
}
}
================================================
FILE: ReferenceApp/Mocks/MockCodePackageActivationContext.cs
================================================
namespace Mocks
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Fabric;
using System.Fabric.Description;
using System.Fabric.Health;
public class MockCodePackageActivationContext : ICodePackageActivationContext
{
public string ApplicationName { get; private set; }
public string ApplicationTypeName { get; private set; }
public string CodePackageName { get; private set; }
public string CodePackageVersion { get; private set; }
public string ContextId { get; private set; }
public string LogDirectory { get; private set; }
public string TempDirectory { get; private set; }
public string WorkDirectory { get; private set; }
private string ServiceManifetName { get; set; }
private string ServiceManifestVersion { get; set; }
public event EventHandler> CodePackageAddedEvent;
public event EventHandler> CodePackageModifiedEvent;
public event EventHandler> CodePackageRemovedEvent;
public event EventHandler> ConfigurationPackageAddedEvent;
public event EventHandler> ConfigurationPackageModifiedEvent;
public event EventHandler> ConfigurationPackageRemovedEvent;
public event EventHandler> DataPackageAddedEvent;
public event EventHandler> DataPackageModifiedEvent;
public event EventHandler> DataPackageRemovedEvent;
public ApplicationPrincipalsDescription GetApplicationPrincipals()
{
throw new NotImplementedException();
}
public IList GetCodePackageNames()
{
return new List() { this.CodePackageName };
}
public CodePackage GetCodePackageObject(string packageName)
{
throw new NotImplementedException();
}
public IList GetConfigurationPackageNames()
{
return new List() { "" };
}
public ConfigurationPackage GetConfigurationPackageObject(string packageName)
{
throw new NotImplementedException();
}
public IList GetDataPackageNames()
{
return new List() { "" };
}
public DataPackage GetDataPackageObject(string packageName)
{
throw new NotImplementedException();
}
public EndpointResourceDescription GetEndpoint(string endpointName)
{
throw new NotImplementedException();
}
public KeyedCollection GetEndpoints()
{
throw new NotImplementedException();
}
public KeyedCollection GetServiceGroupTypes()
{
throw new NotImplementedException();
}
public string GetServiceManifestName()
{
return this.ServiceManifetName;
}
public string GetServiceManifestVersion()
{
return this.ServiceManifestVersion;
}
public KeyedCollection GetServiceTypes()
{
throw new NotImplementedException();
}
public void ReportApplicationHealth(HealthInformation healthInformation)
{
throw new NotImplementedException();
}
public void ReportDeployedServicePackageHealth(HealthInformation healthInformation)
{
throw new NotImplementedException();
}
public void ReportDeployedApplicationHealth(HealthInformation healthInformation)
{
throw new NotImplementedException();
}
public MockCodePackageActivationContext(
string ApplicationName,
string ApplicationTypeName,
string CodePackageName,
string CodePackageVersion,
string Context,
string LogDirectory,
string TempDirectory,
string WorkDirectory,
string ServiceManifestName,
string ServiceManifestVersion)
{
this.ApplicationName = ApplicationName;
this.ApplicationTypeName = ApplicationTypeName;
this.CodePackageName = CodePackageName;
this.CodePackageVersion = CodePackageVersion;
this.ContextId = Context;
this.LogDirectory = LogDirectory;
this.TempDirectory = TempDirectory;
this.WorkDirectory = WorkDirectory;
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects).
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
disposedValue = true;
}
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion
}
}
================================================
FILE: ReferenceApp/Mocks/MockInventoryService.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Mocks
{
using Common;
using Inventory.Domain;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class MockInventoryService : IInventoryService
{
public MockInventoryService()
{
this.AddStockAsyncFunc = (itemId, quantity) => Task.FromResult(quantity);
this.RemoveStockAsyncFunc = (itemId, quantity, amId) => Task.FromResult(quantity);
this.IsItemInInventoryAsyncFunc = (itemId) => Task.FromResult(true);
this.GetCustomerInventoryAsyncFunc = () => Task.FromResult>(new List() { new InventoryItemView() });
this.CreateInventoryItemAsyncFunc = item => Task.FromResult(true);
}
public Func> AddStockAsyncFunc { get; set; }
public Func> CreateInventoryItemAsyncFunc { get; set; }
public Func>> GetCustomerInventoryAsyncFunc { get; set; }
public Func> IsItemInInventoryAsyncFunc { get; set; }
public Func> RemoveStockAsyncFunc { get; set; }
public Task AddStockAsync(InventoryItemId itemId, int quantity)
{
return this.AddStockAsyncFunc(itemId, quantity);
}
public Task CreateInventoryItemAsync(InventoryItem item)
{
return this.CreateInventoryItemAsyncFunc(item);
}
public Task> GetCustomerInventoryAsync()
{
return this.GetCustomerInventoryAsyncFunc();
}
public Task> GetCustomerInventoryAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task IsItemInInventoryAsync(InventoryItemId itemId)
{
return this.IsItemInInventoryAsyncFunc(itemId);
}
public Task IsItemInInventoryAsync(InventoryItemId itemId, CancellationToken cancellationToken)
{
return this.IsItemInInventoryAsyncFunc(itemId);
}
public Task RemoveStockAsync(InventoryItemId itemId, int quantity, CustomerOrderActorMessageId amId)
{
return this.RemoveStockAsyncFunc(itemId, quantity, amId);
}
}
}
================================================
FILE: ReferenceApp/Mocks/MockReliableDictionary.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Mocks
{
using Microsoft.ServiceFabric.Data;
using Microsoft.ServiceFabric.Data.Collections;
using Microsoft.ServiceFabric.Data.Notifications;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
public class MockReliableDictionary : IReliableDictionary
where TKey : IComparable, IEquatable
{
private ConcurrentDictionary dictionary = new ConcurrentDictionary();
public event EventHandler> DictionaryChanged;
public Uri Name { get; set; }
public Func, NotifyDictionaryRebuildEventArgs, Task> RebuildNotificationAsyncCallback
{
set
{
throw new NotImplementedException();
}
}
public Task AddAsync(ITransaction tx, TKey key, TValue value)
{
if (!this.dictionary.TryAdd(key, value))
{
throw new InvalidOperationException("key already exists: " + key.ToString());
}
return Task.FromResult(true);
}
public Task AddAsync(ITransaction tx, TKey key, TValue value, TimeSpan timeout, CancellationToken cancellationToken)
{
if (!this.dictionary.TryAdd(key, value))
{
throw new InvalidOperationException("key already exists: " + key.ToString());
}
return Task.FromResult(true);
}
public Task AddOrUpdateAsync(ITransaction tx, TKey key, Func addValueFactory, Func updateValueFactory)
{
return Task.FromResult(this.dictionary.AddOrUpdate(key, addValueFactory, updateValueFactory));
}
public Task AddOrUpdateAsync(ITransaction tx, TKey key, TValue addValue, Func updateValueFactory)
{
return Task.FromResult(this.dictionary.AddOrUpdate(key, addValue, updateValueFactory));
}
public Task AddOrUpdateAsync(
ITransaction tx, TKey key, Func addValueFactory, Func updateValueFactory, TimeSpan timeout,
CancellationToken cancellationToken)
{
return Task.FromResult(this.dictionary.AddOrUpdate(key, addValueFactory, updateValueFactory));
}
public Task AddOrUpdateAsync(
ITransaction tx, TKey key, TValue addValue, Func updateValueFactory, TimeSpan timeout, CancellationToken cancellationToken)
{
return Task.FromResult(this.dictionary.AddOrUpdate(key, addValue, updateValueFactory));
}
public Task ClearAsync()
{
this.dictionary.Clear();
return Task.FromResult(true);
}
public Task ClearAsync(TimeSpan timeout, CancellationToken cancellationToken)
{
this.dictionary.Clear();
return Task.FromResult(true);
}
public Task ContainsKeyAsync(ITransaction tx, TKey key)
{
return Task.FromResult(this.dictionary.ContainsKey(key));
}
public Task ContainsKeyAsync(ITransaction tx, TKey key, LockMode lockMode)
{
return Task.FromResult(this.dictionary.ContainsKey(key));
}
public Task ContainsKeyAsync(ITransaction tx, TKey key, TimeSpan timeout, CancellationToken cancellationToken)
{
return Task.FromResult(this.dictionary.ContainsKey(key));
}
public Task ContainsKeyAsync(ITransaction tx, TKey key, LockMode lockMode, TimeSpan timeout, CancellationToken cancellationToken)
{
return Task.FromResult(this.dictionary.ContainsKey(key));
}
public Task> TryGetValueAsync(ITransaction tx, TKey key)
{
TValue value;
bool result = this.dictionary.TryGetValue(key, out value);
return Task.FromResult(new ConditionalValue(result, value));
}
public Task> TryGetValueAsync(ITransaction tx, TKey key, LockMode lockMode)
{
TValue value;
bool result = this.dictionary.TryGetValue(key, out value);
return Task.FromResult(new ConditionalValue(result, value));
}
public Task> TryGetValueAsync(ITransaction tx, TKey key, TimeSpan timeout, CancellationToken cancellationToken)
{
TValue value;
bool result = this.dictionary.TryGetValue(key, out value);
return Task.FromResult(new ConditionalValue(result, value));
}
public Task> TryGetValueAsync(
ITransaction tx, TKey key, LockMode lockMode, TimeSpan timeout, CancellationToken cancellationToken)
{
TValue value;
bool result = this.dictionary.TryGetValue(key, out value);
return Task.FromResult(new ConditionalValue(result, value));
}
public Task SetAsync(ITransaction tx, TKey key, TValue value)
{
this.dictionary[key] = value;
return Task.FromResult(true);
}
public Task SetAsync(ITransaction tx, TKey key, TValue value, TimeSpan timeout, CancellationToken cancellationToken)
{
this.dictionary[key] = value;
return Task.FromResult(true);
}
public Task GetOrAddAsync(ITransaction tx, TKey key, Func valueFactory)
{
return Task.FromResult(this.dictionary.GetOrAdd(key, valueFactory));
}
public Task GetOrAddAsync(ITransaction tx, TKey key, TValue value)
{
return Task.FromResult(this.dictionary.GetOrAdd(key, value));
}
public Task GetOrAddAsync(ITransaction tx, TKey key, Func valueFactory, TimeSpan timeout, CancellationToken cancellationToken)
{
return Task.FromResult(this.dictionary.GetOrAdd(key, valueFactory));
}
public Task GetOrAddAsync(ITransaction tx, TKey key, TValue value, TimeSpan timeout, CancellationToken cancellationToken)
{
return Task.FromResult(this.dictionary.GetOrAdd(key, value));
}
public Task TryAddAsync(ITransaction tx, TKey key, TValue value)
{
return Task.FromResult(this.dictionary.TryAdd(key, value));
}
public Task TryAddAsync(ITransaction tx, TKey key, TValue value, TimeSpan timeout, CancellationToken cancellationToken)
{
return Task.FromResult(this.dictionary.TryAdd(key, value));
}
public Task> TryRemoveAsync(ITransaction tx, TKey key)
{
TValue outValue;
return Task.FromResult(new ConditionalValue(this.dictionary.TryRemove(key, out outValue), outValue));
}
public Task> TryRemoveAsync(ITransaction tx, TKey key, TimeSpan timeout, CancellationToken cancellationToken)
{
return this.TryRemoveAsync(tx, key);
}
public Task TryUpdateAsync(ITransaction tx, TKey key, TValue newValue, TValue comparisonValue)
{
return Task.FromResult(this.dictionary.TryUpdate(key, newValue, comparisonValue));
}
public Task TryUpdateAsync(
ITransaction tx, TKey key, TValue newValue, TValue comparisonValue, TimeSpan timeout, CancellationToken cancellationToken)
{
return Task.FromResult(this.dictionary.TryUpdate(key, newValue, comparisonValue));
}
public Task GetCountAsync()
{
return Task.FromResult((long)this.dictionary.Count);
}
public Task>> CreateEnumerableAsync(ITransaction txn)
{
return Task.FromResult>>(new MockAsyncEnumerable>(this.dictionary));
}
public Task>> CreateEnumerableAsync(ITransaction txn, EnumerationMode enumerationMode)
{
return Task.FromResult>>(new MockAsyncEnumerable>(
enumerationMode == EnumerationMode.Unordered
? (IEnumerable>)this.dictionary
: this.dictionary.OrderBy(x => x.Key)));
}
public Task>> CreateEnumerableAsync(ITransaction txn, Func filter, EnumerationMode enumerationMode)
{
return Task.FromResult>>(new MockAsyncEnumerable>(
enumerationMode == EnumerationMode.Unordered
? this.dictionary.Where(x => filter(x.Key))
: this.dictionary.Where(x => filter(x.Key)).OrderBy(x => x.Key)));
}
public Task GetCountAsync(ITransaction tx)
{
return Task.FromResult((long)this.dictionary.Count);
}
}
}
================================================
FILE: ReferenceApp/Mocks/MockReliableQueue.cs
================================================
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
namespace Mocks
{
using Microsoft.ServiceFabric.Data;
using Microsoft.ServiceFabric.Data.Collections;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
public class MockReliableQueue : IReliableQueue
{
private ConcurrentQueue queue = new ConcurrentQueue();
public Task EnqueueAsync(ITransaction tx, T item, TimeSpan timeout, CancellationToken cancellationToken)
{
this.queue.Enqueue(item);
return Task.FromResult(true);
}
public Task EnqueueAsync(ITransaction tx, T item)
{
this.queue.Enqueue(item);
return Task.FromResult(true);
}
public Task> TryDequeueAsync(ITransaction tx, TimeSpan timeout, CancellationToken cancellationToken)
{
T item;
bool result = this.queue.TryDequeue(out item);
return Task.FromResult((ConditionalValue)Activator.CreateInstance(typeof(ConditionalValue), result, item));
}
public Task> TryDequeueAsync(ITransaction tx)
{
T item;
bool result = this.queue.TryDequeue(out item);
return Task.FromResult((ConditionalValue)Activator.CreateInstance(typeof(ConditionalValue), result, item));
}
public Task> TryPeekAsync(ITransaction tx, LockMode lockMode, TimeSpan timeout, CancellationToken cancellationToken)
{
T item;
bool result = this.queue.TryPeek(out item);
return Task.FromResult((ConditionalValue)Activator.CreateInstance(typeof(ConditionalValue), result, item));
}
public Task> TryPeekAsync(ITransaction tx, LockMode lockMode)
{
T item;
bool result = this.queue.TryPeek(out item);
return Task.FromResult((ConditionalValue