ASP.NET Microservices Best Practices

As you can see, there are a lot of new improvements and enhancements to ASP.NET for developers and architects to consider. In addition to framework- and feature-level improvements, we also wanted to add some generally accepted recommendations or considerations when designing microservices using ASP.NET Web API.

Use Async Everywhere

Ensure that your web APIs use the Async/Await pattern where possible. When executing a request synchronously, for example an I/O request to read a file, the thread will idle and block execution until the I/O request is complete. Using the Async/Await pattern enables your thread to return to the thread pool so it can handle future requests. For API developers, ensure that both your controllers and anything that accesses resources, files, database repositories, other APIs, and so on, are designed to use Async and Await. Here’s a basic before and after example that assumes a data repository named DbContext. In the second example, the Async controller method is marked with the async keyword, and the call to the ToListAsync() method is prefixed with the await keyword.

        [HttpGet]
        public IEnumerable<Product> Get()
        {
            return DbContext.Products;
       }
        [HttpGet]
        public async Task<IEnumerable<Product>> Get()
        {
            return await DbContext.Products.ToListAsync<Product>();
        }

Design Stateless APIs

While this design principle has been obvious for the last several years for web developers, it’s especially important with the introduction of containers. Remember that containers are ephemeral, meaning that if you write an API to upload photos that in turn saves photos to the local disk inside the container, those images by default will be gone when the container is stopped or removed (unless you set up a Docker volume mount). Stateless APIs also make it easy to scale up or down the number of containers handling requests without having to worry about the state of each API. There are, however, some scenarios where you want to build stateful microservices. In those cases, Service Fabric, which supports ASP.NET, is the right choice to manage distributed state for services.

Consider the Right Web API Return Type

When designing your microservices using the ASP.NET Web API you have two general categories of return types, POCO (Plain Old C# Object) return types, and results that implement the IActionResult interface.

POCO object return types refer to types that return a C# object as shown in the next example, which returns an IEnumerable<Product> containing the first 25 products. The object is serialized into JSON and sent over wire.

        [HttpGet]
        public async Task<IEnumerable<Product>> Get()
        {
            var result = await _context.Products
                .Take(25).ToArrayAsync();

            return result;
        }

An IActionResult return type like ObjectResult behaves exactly like the C# return type, but with the difference that with an IActionResult, you can return RESTful values using built-in primitives like HttpNotFound(), which translates to a HTTP 404 error, or return any HTTP status code using the HttpStatusCodeResult class. The snippet below shows the same API returning 25 objects, but this time using an IActionResult return type.

        [HttpGet]
        public async Task<IActionResult> Get()
        {
            var result = await _context.Products
                .Take(25).ToArrayAsync();

            return new ObjectResult(result);
        }

One common question is when to use which return type. There are no hard-and-fast rules, and although both are commonly used, the IActionResult is often used due to the additional flexibility and support for HTTP status codes it provides.

Another consideration is whether to allow for XML as a valid return type or not. Many online services like Facebook and Twitter have switched to only provide JSON for results because of the bad performance and overhead with using XML. Because XML is more verbose, it also results in higher egress costs from Azure and is potentially more expensive for clients consuming services over metered connections, such as a mobile phone. If you don’t have legacy apps that depend on XML already, it’s likely best to switch to only supporting JsonResult types.

Design RESTful APIs

When designing your APIs, use ASP.NET’s attribute-based routing. As you can see in the example below, you can specify attributes for HttpGet, HttpPost, HttpPut, HttpPatch, HttpDelete and more. Similarly for return types, notice how we are using IActionResult and using Http return codes like the HTTP 404 request, which is returned in the HttpNotFound() example below.

    public class ProductsController : Controller
    {
        [HttpGet("{id}")]
        public async Task<IActionResult> Get(int id)
        {
     var product =
      await _context.Products.FirstOrDefaultAsync(p =>
         p.ProductId == id);

            if (product == null)
            {
                return HttpNotFound();
            }

            return new JsonResult(product);
        }

Here’s a list of some of most common HTTP error codes to consider for your APIs.

1. 200 – OK

2. 201 – Created: A new resource was created.

3. 301 – Moved Permanently: This resource has been moved to a new URI.

4. 400 – Bad Request: There was an error in the request, typically caused by a malformed request (missing or invalid parameters, etc).

5. 401 – Unauthorized: Authentication is required for accessing the resource.

6. 403 – Forbidden: The server is refusing to respond to the request, even after authenticating it.

7. 404 – Not Found: The given resource was not found on the server.

8. 500 – Internal Service Error: A generic error message when a server returns an error.

9. 503 – Service Unavailable: The service is currently unavailable say because of high load. Some Azure services, like storage, will return a 503 Service Unavailable error intermittently if they cannot handle the load.

For reference, Wikipedia includes the full list of HTTP status codes at: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes.

Consider Using New High-Concurrency Data Types

For microservices and cloud developers, one of the previous challenges with the .NET Framework has been that the default collection classes didn’t include support for concurrency. This is important if, for instance, you are building a real-time “voting” microservice that enables customers to vote on something, and you’ll be updating and displaying vote counts quickly. Rather than building your own custom locking code for the .NET default collections, you can use the new System.Collections.Concurrent namespace, which includes common data types you could use including a ConcurrentDictionary (key/value pairs), ConcurrentQueue (first-in, first-out collection), and ConcurrentStack (last-in, first-out collection) for improved performance.

Design your Microservice Defensively

Even if your microservice is only used by other teams inside your organization, assume that any publicly exposed API will be “fair game” to be used and abused. Defensive design of microservices means a number of things. First, you shouldn’t expose API calls that are, by design, a drain on your resources. As one simple example, we can refactor the get request of our product’s API so that instead of returning hundreds of products on an API call, to return a maximum of 25 results using the LINQ Take() extension method as shown below.

        [HttpGet]
        public async Task<IEnumerable<Product>> Get()
        {
            return await
_context.Products.Take(25).ToListAsync<Product>();
        }

You can then ensure that your APIs are factored to provide support for sorting, paging, and item count for further control by your callers.

Beyond API design, you also want to keep a close eye on how your API is used, who are the callers, and what API caller pattern are they using? Ensure that you have monitoring and diagnostic tools to track and understand who is using your API and how are they are using it. Depending on how your API is used (or abused), one potential option is to rate-limit your API to restrict certain callers to a fixed amount of API calls per day.

Consider a Batch API Service

Unlike your standard Web APIs, a Batch API service is not designed to return results in real time. The most common use case for a Batch API is reporting. Let’s say that an internal audit team needs to make a request for a large data set, perhaps the order history for the last six months. Rather than having the audit team make hundreds of calls to your real-time APIs, you can create a separate set of Batch APIs where jobs are placed into a queue and run during system downtime, commonly late at night. The advantages of this are that you can separate out API calls that don’t have to happen in real time from those that do, you can provide custom APIs for batch data requests, and you can scale each independently. One other common feature for batch APIs is support for additional return types. Adding support for comma-separated values (CSV) as a return type makes importing and exporting data into reporting tools or other automated batch processing tools much easier.

Design for Mobile Clients

In 2014, as shown in Figure A.2 from the Kliener Perkins Caufield and Biers Internet Trends report (http://www.kpcb.com/internet-trends) for the first time, more adults used mobile devices than desktop or laptops or other connected devices for digital media. Many development shops are already facing the need to build multimode applications that could run in a desktop locally via a browser, or in a custom mobile application for iOS, Android, or Windows. For web developers, this means ensuring your layout is responsive for multiple form factors, from mobile devices to tablets to desktops.

Image

FIGURE A.1: Mobile is now used more than desktop/laptop or other devices

Consider OpenID for API authentication

Certain API requests, such as a customer’s order history, should only be available after authenticating and authorizing the request. There are many ways to handle authentication, and details depend on what underlying user authentication service you are using. For example, if you are building a microservices app for your enterprise, you might want to consider using Azure’s Active Directory service to handle user authentication. For Active Directory authentication, you can use OpenID Connect, a standard and extensible identity layer on top of the OAuth 2.0 protocol standard. With OpenID, when an unauthorized request to an API is received, the request is forwarded to Active Directory for a user to sign in, for instance from an iOS device. After successfully authenticating, the user receives a token that it can then use for calling APIs that require authentication. Choosing and designing the right authentication system is a book unto itself, but to get started, the Active Directory team publishes a number of examples on their team GitHub for integrating OpenID Connect with ASP.NET Web and Web API projects, Node, iOS, and many more at http://bit.ly/azureadsamples.

Make Your Microservices Easier to Consume Using Swagger

If your goal is to increase the number of developers using your microservice inside your enterprise, the onus is on you to make it as simple as possible to consume. Swagger, as we’ve discussed earlier, is a way to describe your APIs so that a consumer of your service can open a browser, see what services you have available, and even make simple test requests for your service. Companies such as Facebook, which provides a rich set of APIs for your social graph, provide a similar tool to test-drive their API with the Graph API Explorer as shown in Figure A.2, https://developers.facebook.com/tools/explorer.

Image

FIGURE A.2: Facebook’s Graph API explorer enables you to test-drive their API from a browser

For ASP.NET Core 1.0 Web API developers, Ahoy!

At https://github.com/domaindrivendev/Ahoy is an ASP.NET Core 1.0 NuGet package that makes it easy to add Swagger support to your API. You can configure Ahoy! to use the XML comments from your classes to document the input parameters and return types used in your API. Once you do this, you’ll get a beautiful documentation page as shown in Figure A.3, that includes a list of all your APIs. Each API includes a test harness page to test-drive the API.

Image

FIGURE A.3: Swagger shows the resources and URLs for the Products API

Consider Providing an “Uptime” Service

One of the most frustrating parts of working with distributed systems is tracking down where a particular service in a multiservice request is failing, or having delayed or limited results. For your most important microservices, you can help mitigate this by providing a service uptime microservice. As meta as that sounds, the idea is that you can provide a service dashboard page, much as you see major cloud vendors like Amazon, Microsoft, or Google provide for their services. This enables you to provide both automated service uptime listing each API and its status, as well as additional information your incident response team would add as they provide regular status updates on any issues your services are experiencing. The monitoring and diagnostics chapter dicusses the tools and services you can use to monitor your services.

Consider if OData is Right for You

OData, or the Open Data Protocol, is a standard, cross-platform way to provide queryable services via REST APIs. The advantage of OData is the developer productivity boost it provides to easily add deep querying semantics capability to your APIs with almost no work. Instead of building a series of REST services and inputs that include every possible way to filter or sort data, OData makes it trivial to add querying capabilities to your domain model. For example, in a single query, you can select the fields you want, like product id, name, and price; filter those products that are between $50 and $100 whose name starts with the letter “B”; and that are in the “Misc” product category. This powerful querying capability comes with some downsides. The first is that your data source and data model might not be optimized for this deep level of querying which can translate to unexpected performance issues. It’s easier to understand the use cases and performance characteristics for a hand-crafted REST API. Another criticism of OData is that its overhead can add complexity to consumers of your APIs, especially if OData client libraries aren’t available for their target language or platform. There is no right choice here, but for API developers whose primary use case is rich querying capability, OData could be a great fit for you.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.131.10