The Orders application is a Stateful Service that implements a pair of lightweight WebAPI controllers. These
controllers use IReliableStateManager
to persist and query data from IReliableQueue
and IReliableDictionary
implementations.
Once an order has been placed on the IReliableQueue
then it will be dequeued and processed by the OrdersService
and then have the data persisted to MongoDB. Once the data in MongoDB has been written then the Statistics
data
will be written to the IReliableDictionary
.
Execute the following command in the root directory of this repository:
git checkout orders
There are three parts to this service, the two controllers OrdersController
and StatisticsController
, and
the OrdersService
Stateful Service that processes orders and persists them to MongoDB.
ServiceRuntime.RegisterServiceAsync("OrdersApiType", context =>
{
if (repository == null)
{
lock (sync)
{
if (repository == null)
{
BsonSerializer.RegisterSerializer(typeof(decimal), new DecimalSerializer(BsonType.Decimal128));
BsonSerializer.RegisterSerializer(typeof(decimal?), new NullableSerializer<decimal>(new DecimalSerializer(BsonType.Decimal128)));
repository = new OrdersRepository(context);
}
}
}
return new OrdersApi(context, repository);
}).GetAwaiter().GetResult();
return new WebHostBuilder()
.UseKestrel()
.ConfigureServices(
services => services
.AddSingleton(serviceContext)
.AddSingleton(StateManager)
.AddSingleton(repository))
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.UseUniqueServiceUrl | ServiceFabricIntegrationOptions.UseReverseProxyIntegration)
.UseUrls(url)
.Build();
protected override async Task RunAsync(CancellationToken cancellationToken)
{
var queue = await StateManager.GetOrAddAsync<IReliableConcurrentQueue<Order>>(StateName);
var statistics = await StateManager.GetOrAddAsync<IReliableDictionary2<string, string>>(StatisticsName);
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
using (var tx = StateManager.CreateTransaction())
{
var result = await queue.TryDequeueAsync(tx, cancellationToken);
if (result.HasValue)
{
var currentStatistics = await repository.AddOrderAsync(result.Value, cancellationToken);
if (currentStatistics != null)
{
var json = JsonConvert.SerializeObject(currentStatistics);
await statistics.AddOrUpdateAsync(tx, $"{result.Value.OrderDateTime:yyyyMMdd}", json, (id, stats) => json);
}
await tx.CommitAsync();
}
else
{
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
}
}
}
[HttpPost]
public async Task<ActionResult> Post([FromBody] Order order)
{
try
{
var state = await stateManager.GetOrAddAsync<IReliableConcurrentQueue<Order>>(StateName);
using (var tx = stateManager.CreateTransaction())
{
await state.EnqueueAsync(tx, order);
await tx.CommitAsync();
return Ok();
}
}
catch (Exception e)
{
var response = Json(new
{
e.Message
});
response.StatusCode = 500;
return response;
}
}
[HttpGet("{id}")]
public async Task<ActionResult<IEnumerable<Statistics>>> Get(string id)
{
try
{
var statistics = await stateManager.TryGetAsync<IReliableDictionary2<string, string>>(StatisticsName);
if (!statistics.HasValue)
{
return NotFound();
}
using (var tx = stateManager.CreateTransaction())
{
var value = await statistics.Value.TryGetValueAsync(tx, id);
if (!value.HasValue)
{
return NotFound();
}
var stats = JsonConvert.DeserializeObject<IEnumerable<Statistics>>(value.Value);
return Ok(stats);
}
}
catch (Exception e)
{
var response = Json(new
{
e.Message
});
response.StatusCode = 500;
return response;
}
}
If you are running Visual Studio then the service can be deployed to a local Service Fabric cluster by right clicking the Service Fabric Application in Solution Explorer and choosing the Publish option. This will open a dialog that allows the cluster to be selected. By default this will be an Azure cluster but there are also options for Single and Five Node clusters.
Choose the option that matches your local Service Fabric cluster setup and click Publish. The output will be displayed in the Output Window in Visual Studio.
The ASP.NET MVC application from Lab 3 will also be updated to support ordering the items currently in the basket and displaying statistics about previous orders.
The Application service is an MVC application that provides controllers which access the microservices. In order
to support Orders, the existing BasketController
will be updated to provide a means of submitting the order
and then clearing the local basket ID cookie.
In addition to allowing orders to be placed this service will also be extended to provide statistical data from the orders which have been placed.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Order(string redirect)
{
using (var response = await http.GetAsync(await GetServiceUriAsync("Basket", "BasketApi", $"/api/basket/{GetBasketId()}")))
{
var order = new Order
{
Id = new Guid(GetBasketId()),
OrderDateTime = DateTimeOffset.Now,
Products = await DeserializeResponseAsync<Product[]>(response)
};
using (var content = GetJsonContent(order))
using (await http.PostAsync(await GetServiceUriAsync("Orders", "OrdersApi", $"/api/orders", () => new ServicePartitionKey(1)), content))
using (await http.DeleteAsync(await GetServiceUriAsync("Basket", "BasketApi", $"/api/basket/{GetBasketId()}")))
{
ClearBasketId();
}
return !string.IsNullOrEmpty(redirect) ?
(IActionResult)Redirect(redirect) :
RedirectToAction("Index");
}
}
It is necessary to update the CodePackage
version for the service in order to carry out an update. This follows
the same process as a typical deployment. The Manifest Versions
button can be clicked to view the updated code
version, which should appear as follows:
Set the New Version
for the Code
package to be 3.0.0
and ensure that the checkbox to update the application
and service versions is ticked. Once the versions have been updated then click Save
and then Publish
to deploy
the updated application.
When the Orders application has been deployed and Application has been updated then the new basket functionality can be tested. This can either be via the Swagger UI, going directly to the Orders API or orders can be made through the UI.
The Swagger endpoint for the Basket API is http://localhost:19081/Orders/OrdersApi/swagger
The UI is accessible through http://localhost and a link on the updated home page.