• No results found

Creating a Microservice

71Making the service asynchronous

The StreamContent object inherits from HttpContent. You can provide any stream to StreamContent, which means you don’t have to keep the full content of the POST in memory. The PostAsync method’s also nice if you don’t want to block the thread as you wait for the POST to complete. In this example, we didn’t take advantage of the async features of .NET. But to build high performance microservices applications, we need to understand how to use these features.

7.3

Making the service asynchronous

In the code from Using HttpClient to call a web service, POST a file, and read the response, we explicitly call .Result on the returned values of two async methods: PostAsync and ReadAsStringAsync. These methods return Task objects. Our cli- ent doesn’t need to be asynchronous because it’s only doing one thing. It doesn’t mat- ter if we block the main thread, because there’s nothing else that needs to happen.

Services can’t afford to tie up threads waiting for something. Let’s take a closer look at the service code the converts the posted Markdown to HTML. The method is shown in Synchronous Convert method blocks a thread waiting for the request content.

[HttpPost]

public IActionResult Convert() {

var reader = new StreamReader(Request.Body); var markdown = reader.ReadToEnd();

This is the call that blocks the thread

var result = engine.Markup(markdown); return Content(result);

}

The problem with blocking the thread to read the incoming HTTP request is when the client doesn’t execute as fast as you think. If the client has a slow upload speed or is malicious, it could take minutes to upload all the data. Meanwhile, the service has a whole thread stuck on this client. Add enough of these clients and soon you’ll run out of available threads and/or memory.

The answer’s to rely on two powerful C# constructs called async and await. Asyn- chronous Convert method that does not block the thread waiting for the request con- tent shows how we could rewrite the Convert method to be asynchronous.

[HttpPost]

public async Task<IActionResult> Convert()

Mark the method async and return a Task or Task<T>

{

using (var reader = new StreamReader(Request.Body))

using block’s to clean up reader, not necessary for async {

var markdown = await reader.ReadToEndAsync();

await on the result of ReadToEndAsync()

var result = engine.Markup(markdown);

return Content(result); }

Listing 7.11 Synchronous Convert method blocks a thread waiting for the request content

Listing 7.12 Asynchronous Convert method that doesn’t block the thread waiting for the request content

If the client’s slowly uploading its request content, the only impact’s the socket held open. The layers beneath your service code are responsible for gathering the network IO and buffering it until the request content length’s reached. This means your ser- vice can handle more requests with fewer threads. Writing asynchronous code becomes more important as the service depends on other services, which limits opera- tions to the speed of the network. We’ll see an example of this in the next section.

7.4

Getting data from Azure blob storage

Now that we’ve figured out how to convert Markdown to HTML, let’s incorporate stor- age of the posts in Azure blob storage. Instead of posting data to the Markdown ser- vice, I prefer to send it a blob name and have it return the converted HTML. We can do this by adding a GET method to our service. Before going into that, we’ve some val- ues we need to pull from configuration.

7.4.1 Getting values from configuration

Our code uses the Microsoft.Extensions.Configuration library, which we learned about in chapter 6. Consult chapter 6 for instructions on adding a config.json file to your project, copying it to the build output, and adding the dependency on the Con- figuration library. The code for getting the config values is shown in Code to read the Azure storage account information from configuration.

public class MdlController : Controller {

private readonly IMarkdownEngine engine; private readonly string AccountName; private readonly string AccountKey; private readonly string BlobEndpoint; private readonly string ServiceVersion; public MdlController(IMarkdownEngine engine) {

this.engine = engine;

var configBuilder = new ConfigurationBuilder(); configBuilder.AddJsonFile("config.json", true);

Only using JSON config, no default config

var configRoot = configBuilder.Build(); AccountName = configRoot["AccountName"]; AccountKey = configRoot["AccountKey"]; BlobEndpoint = configRoot["BlobEndpoint"];

Blob endpoint is determined differently in emulator than in Azure

The async/await constructs are a bit of compiler magic that make asynchronous code much easier to write. The await signals a point in the method where the code needs to wait for something. The C# compiler splits the Convert method into two methods, with the second being invoked when the awaited item’s finished. This all happens behind the scenes, but if you’re curious how it works, try viewing the IL generated for async methods in the ILDASM tool that comes with Visual Studio.

73