public async Task<Guid> PostCheckout(List<CustomerOrderItem> cart) { ServiceEventSource.Current.Message("Now printing cart for POSTCHECKOUT..."); foreach (CustomerOrderItem item in cart) { ServiceEventSource.Current.Message("Guid {0}, quantity {1}", item.ItemId.ToString(), item.Quantity.ToString()); } Guid orderId = Guid.NewGuid(); ServiceUriBuilder builder = new ServiceUriBuilder(CustomerOrderServiceName); //We create a unique Guid that is associated with a customer order, as well as with the actor that represents that order's state. ICustomerOrderActor customerOrder = ActorProxy.Create<ICustomerOrderActor>(new ActorId(orderId), builder.ToUri()); try { await customerOrder.SubmitOrderAsync(cart); ServiceEventSource.Current.Message("Customer order submitted successfully. ActorOrderID: {0} created", orderId); } catch (InvalidOperationException ex) { ServiceEventSource.Current.Message("Web Service: Actor rejected {0}: {1}", customerOrder, ex); throw; } catch (Exception ex) { ServiceEventSource.Current.Message("Web Service: Exception {0}: {1}", customerOrder, ex); throw; } return orderId; }
public Task<string> GetOrderStatus(Guid customerOrderId) { ServiceUriBuilder builder = new ServiceUriBuilder(CustomerOrderServiceName); ICustomerOrderActor customerOrder = ActorProxy.Create<ICustomerOrderActor>(new ActorId(customerOrderId), builder.ToUri()); try { return customerOrder.GetStatusAsync(); } catch (Exception ex) { ServiceEventSource.Current.Message("Web Service: Exception {0}: {1}", customerOrder, ex); throw; } }
public Task<bool> CreateInventoryItem(string description, decimal price, int number, int reorderThreshold, int max) { InventoryItem i = new InventoryItem(description, price, number, reorderThreshold, max); ServiceUriBuilder builder = new ServiceUriBuilder(InventoryServiceName); IInventoryService inventoryServiceClient = ServiceProxy.Create<IInventoryService>(builder.ToUri(), i.Id.GetPartitionKey()); try { return inventoryServiceClient.CreateInventoryItemAsync(i); } catch (Exception ex) { ServiceEventSource.Current.Message("Web Service: Exception creating {0}: {1}", i, ex); throw; } }
public async Task<IEnumerable<InventoryItemView>> GetStore() { ServiceUriBuilder builder = new ServiceUriBuilder(InventoryServiceName); Uri serviceName = builder.ToUri(); List<InventoryItemView> itemList = new List<InventoryItemView>(); ServicePartitionList partitions = await fc.QueryManager.GetPartitionListAsync(serviceName); foreach(Partition p in partitions) { long minKey = (p.PartitionInformation as Int64RangePartitionInformation).LowKey; IInventoryService inventoryServiceClient = ServiceProxy.Create<IInventoryService>(minKey, serviceName); itemList.AddRange(await inventoryServiceClient.GetCustomerInventoryAsync()); } return itemList; }
/// <summary> /// 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 /// </summary> /// <returns></returns> public async Task InternalActivateAsync(ICodePackageActivationContext context, IServiceProxyFactory proxyFactory) { this.tokenSource = new CancellationTokenSource(); this.builder = new ServiceUriBuilder(context, InventoryServiceName); this.ServiceProxyFactory = proxyFactory; }
/// <summary> /// Drains the queue of completed restock requests sends them to InventoryService. /// </summary> /// <param name="cancellationToken"></param> /// <returns></returns> protected override async Task RunAsync(CancellationToken cancellationToken) { IReliableQueue<RestockRequest> completedRequests = await this.StateManager.GetOrAddAsync<IReliableQueue<RestockRequest>>(CompletedRequestsQueueName); while (!cancellationToken.IsCancellationRequested) { using (ITransaction tx = this.StateManager.CreateTransaction()) { ConditionalResult<RestockRequest> result = await completedRequests.TryDequeueAsync(tx, TxTimeout, cancellationToken); if (result.HasValue) { ServiceUriBuilder builder = new ServiceUriBuilder(InventoryServiceName); IInventoryService inventoryService = ServiceProxy.Create<IInventoryService>(result.Value.ItemId.GetPartitionKey(), builder.ToUri()); await inventoryService.AddStockAsync(result.Value.ItemId, result.Value.Quantity); ServiceEventSource.Current.ServiceMessage( this, "Adding stock to inventory service. ID: {0}. Quantity: {1}", result.Value.ItemId, result.Value.Quantity); } // This commits the dequeue operations. // If the request to add the stock to the inventory service throws, this commit will not execute // and the items will remain on the queue, so we can be sure that we didn't dequeue items // that didn't get saved successfully in the inventory service. // However there is a very small chance that the stock was added to the inventory service successfully, // but service execution stopped before reaching this commit (machine crash, for example). await tx.CommitAsync(); } await Task.Delay(CompletedRequestsBatchInterval, cancellationToken); } }
private async Task PeriodicInventoryCheck(CancellationToken cancellationToken) { IReliableDictionary<InventoryItemId, InventoryItem> inventoryItems = await this.stateManager.GetOrAddAsync<IReliableDictionary<InventoryItemId, InventoryItem>>(InventoryItemDictionaryName); while (!cancellationToken.IsCancellationRequested) { ServiceEventSource.Current.ServiceMessage(this, "Checking inventory stock for {0} items.", await inventoryItems.GetCountAsync()); foreach (InventoryItem item in inventoryItems.Select(x => x.Value)) { cancellationToken.ThrowIfCancellationRequested(); try { //Check if stock is below restockThreshold and if the item is not already on reorder if ((item.AvailableStock <= item.RestockThreshold) && !item.OnReorder) { ServiceUriBuilder builder = new ServiceUriBuilder(RestockRequestManagerServiceName); IRestockRequestManager restockRequestManagerClient = ServiceProxy.Create<IRestockRequestManager>(0, builder.ToUri()); // 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); } }
/// <summary> /// 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. /// /// </summary> /// <returns>The number of items put on backorder after fulfilling the order.</returns> internal async Task FulfillOrderAsync() { ServiceUriBuilder builder = new ServiceUriBuilder(InventoryServiceName); await this.SetOrderStatusAsync(CustomerOrderStatus.InProcess); IList<CustomerOrderItem> orderedItems = await this.StateManager.GetStateAsync<IList<CustomerOrderItem>>(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 = ServiceProxy.Create<IInventoryService>(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<long>(RequestIdPropertyName))); item.FulfillmentRemaining -= numberItemsRemoved; } IList<CustomerOrderItem> items = await this.StateManager.GetStateAsync<IList<CustomerOrderItem>>(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<long>(RequestIdPropertyName); await this.StateManager.SetStateAsync<long>(RequestIdPropertyName, ++messageRequestId); }