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); }
public Task <int> RemoveStockAsync(InventoryItemId itemId, int quantity, CustomerOrderActorMessageId amId) { return(this.RemoveStockAsyncFunc(itemId, quantity, amId)); }
public Task<int> RemoveStockAsync(InventoryItemId itemId, int quantity, CustomerOrderActorMessageId amId) { return this.RemoveStockAsyncFunc(itemId, quantity, amId); }
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); }
/// <summary> /// Removes the given quantity of stock from an in item in the inventory. /// </summary> /// <param name="request"></param> /// <returns>int: Returns the quantity removed from stock.</returns> public async Task <int> RemoveStockAsync(InventoryItemId itemId, int quantity, CustomerOrderActorMessageId amId) { ServiceEventSource.Current.ServiceMessage(this, "inside remove stock {0}|{1}", amId.GetHashCode(), amId.GetHashCode()); IReliableDictionary <InventoryItemId, InventoryItem> inventoryItems = await this.stateManager.GetOrAddAsync <IReliableDictionary <InventoryItemId, InventoryItem> >(InventoryItemDictionaryName); IReliableDictionary <CustomerOrderActorMessageId, DateTime> recentRequests = await this.stateManager.GetOrAddAsync <IReliableDictionary <CustomerOrderActorMessageId, DateTime> >(ActorMessageDictionaryName); IReliableDictionary <CustomerOrderActorMessageId, Tuple <InventoryItemId, int> > requestHistory = await this.stateManager.GetOrAddAsync <IReliableDictionary <CustomerOrderActorMessageId, Tuple <InventoryItemId, int> > >(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 ConditionalResult <DateTime> 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. ConditionalResult <InventoryItem> 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 <InventoryItemId, int>(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 ConditionalResult <Tuple <InventoryItemId, int> > 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.ServicePartition.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); }
/// <summary> /// Removes the given quantity of stock from an in item in the inventory. /// </summary> /// <param name="request"></param> /// <returns>int: Returns the quantity removed from stock.</returns> public async Task<int> RemoveStockAsync(InventoryItemId itemId, int quantity, CustomerOrderActorMessageId amId) { ServiceEventSource.Current.ServiceMessage(this, "inside remove stock {0}|{1}", amId.GetHashCode(), amId.GetHashCode()); IReliableDictionary<InventoryItemId, InventoryItem> inventoryItems = await this.stateManager.GetOrAddAsync<IReliableDictionary<InventoryItemId, InventoryItem>>(InventoryItemDictionaryName); IReliableDictionary<CustomerOrderActorMessageId, DateTime> recentRequests = await this.stateManager.GetOrAddAsync<IReliableDictionary<CustomerOrderActorMessageId, DateTime>>(ActorMessageDictionaryName); IReliableDictionary<CustomerOrderActorMessageId, Tuple<InventoryItemId, int>> requestHistory = await this.stateManager.GetOrAddAsync<IReliableDictionary<CustomerOrderActorMessageId, Tuple<InventoryItemId, int>>>(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 ConditionalResult<DateTime> 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. ConditionalResult<InventoryItem> 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<InventoryItemId, int>(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 ConditionalResult<Tuple<InventoryItemId, int>> 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.ServicePartition.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; }
/// <summary> /// Removes the given quantity of stock from an in item in the inventory. /// </summary> /// <param name="request"></param> /// <returns>int: Returns the quantity removed from stock.</returns> public async Task <int> RemoveStockAsync(InventoryItemId itemId, int quantity, CustomerOrderActorMessageId amId) { int removed = 0; ServiceEventSource.Current.ServiceMessage(this, "inside remove stock {0}|{1}", amId.GetHashCode(), amId.GetHashCode()); IReliableDictionary <InventoryItemId, InventoryItem> inventoryItems = await this.StateManager.GetOrAddAsync <IReliableDictionary <InventoryItemId, InventoryItem> >(InventoryItemDictionaryName); using (ITransaction tx = this.StateManager.CreateTransaction()) { ConditionalValue <InventoryItem> item = await inventoryItems.TryGetValueAsync(tx, itemId); if (item.HasValue) { removed = item.Value.RemoveStock(quantity); await inventoryItems.SetAsync(tx, itemId, item.Value); ServiceEventSource.Current.ServiceMessage( this, "Removed stock complete. Item: {0}. Removed: {1}. Remaining: {2}", item.Value.Id, removed, item.Value.AvailableStock); await tx.CommitAsync(); ServiceEventSource.Current.Message("Inventory Service Changes Committed"); ServiceEventSource.Current.Message("Removed {0} of item {1}", removed, itemId); } } return(removed); }