Monday, January 7, 2019

More DI: Async Interfaces

Async code is everywhere now (as it probably should be). As mentioned in the previous article, I've updated my dependency injection sample code to use an asynchronous interface. This particular code is designed to explore the decorator pattern a bit more, and you can find it on GitHub in the jeremybytes/di-decorators project: https://github.com/jeremybytes/di-decorators.

Note: for an introduction to dependency injection, take a look at my on-line materials or my course on Pluralsight: Dependency Injection On-Ramp.

The Application
The application is a one-screen WPF application that shows "Person" objects. Here is the running application:


The data can come from a variety of sources, and the application parts can be snapped together in a variety of ways (this is where loose coupling comes in handy).

The Repository Interface
The repository represents the data access code. A repository should be able to talk to a specific data source (such as a web service, database, or text file) and provide regular C# objects that the application can use. This means that our core application does not need to know the details of making an HTTP call or file I/O operations; the code is encapsulated in the repository.

The previous repository code looked like this, IPersonRepository:


This shows the read and write operations that would be used for "Person" objects.

The first change I made was to split out a separate "Reader" interface. This application only uses read operations, so this interface is a violation of the Interface Segregation Principle. Here's a new interface with just the read operations.


For more information on this, you can see this article: Applying the Interface Segregation Principle to a Repository.

The short version is that since our client only needs the read operations, we should not provide the other operations that it does not need.

Making the Interface Asynchronous
We have just the read operations that we need, but this is still synchronous code. The next step is to make the interface methods asynchronous. We do this by having the methods return "Task". (This code is in the "Common" project, IPersonReader.cs file.)


We don't need to mark anything as "async" here; that will happen in the code that uses this interface (as well as, potentially, the code that implements the interface).

A note on naming: I've opted to keep the names of the methods that same. The other way would be to append "Async" to the names ("GetPeopleAsync" and "GetPersonAsync"). I've seen arguments both ways regarding naming. I've decided to not include "Async" and instead let the return type (Task) denote that these are async methods.

An Async Interface Implementation
The individual readers that implement this interface must provide the asynchronous implementations that return Task. This can be a benefit because many of the data access calls that we make using the .NET Framework are now asynchronous.

This is true of HttpClient which is used in the ServiceReader implementation of the interface. This is in the "PersonReader.Service" project, ServiceReader.cs file.


The HttpClient object has a "GetAsync" method that we can "await". When we "await" something, we need to mark the method as "async", so we can see that the "GetPeople" method is marked with the "async" modifier.

To get the result, we also await the "ReadAsStringAsync" method. Since we are "await"ing in our code, we do not need to do anything special to get a Task back as a return type. We just return an "IEnumerable<Person>", and the compiler takes care of wrapping things in the appropriate "Task". This is a bit confusing, which is why I stay away from this code when introducing dependency injection.

It gets easier once we're comfortable using Task and await. If you'd like a refresher, check out my materials on Task & Await, or watch one of the videos on YouTube: I'll Get Back to You: Task, Await, and Asynchronous Methods in C#.

Other Readers
Other readers have similar code. Here is the code for the CSV reader's "GetPeople" method. (Available in the "PersonReader.CSV" project, CSVReader.cs file.)


And here is the code for the SQL reader. (Available in the "PersonReader.SQL" project, SQLReader.cs file.)


Altogether, this gives us the readers that access our data for us -- from a web service, from a SQL server, or from a text file.

The View Model
We've seen the code that implements the interface, now we need to take a quick look at the upstream code that calls the interface.

For this, we look at the View Model of the application. The view model contains the presentation logic of the application, providing properties that can be used for data binding as well as functions that the UI elements can call. Here is the code from the "PeopleViewer.Presentation" project, PeopleReaderViewModel.cs file:


This view model provides a "People" property that can be data-bound to the list box in the UI. In addition, there are "RefreshPeople" and "ClearPeople" methods that can be hooked up to the buttons in the UI.

The only code change here is with the "RefreshPeople" method. Instead of returning "void", it returns "Task". It's important that this method returns "Task" so that we have the ability to check the status (whether it completed successfully) as well as inspect any exceptions that may happen.

Inside the method, we "await" the "GetPeople" method. And because we use "await", we also mark the method itself as "async".

I had the option of making the "ClearPeople" method async as well. This would give some consistency between "RefreshPeople" and "ClearPeople". But since the async is not strictly required for the clear method, I opted to leave the original.

The View
The last piece of code that needs to be updated is the View -- this is the actual form and UI elements. This is in the "PeopleViewer" project, MainWindow.xaml.cs file.


Since the view model's "RefreshPeople" method is asynchronous, we need to update the call in the view. In the "RefreshButton_Click" event handler, we "await" the RefreshPeople call. And we also mark the method itself as "async".

Above, we changed the return type to "Task", but we can't do that here. The event handler specifically needs a method that returns "void". So this is the one spot where we can use "async void" without feeling bad about it. If the "RefreshPeople" method throws an exception, we'll see it here, and our global exception handler can grab it.

We'll look more at the exception handling when we look the Decorators in a future article (in particular, the exception logging decorator).

The Rest of the Application
No other parts of the application production code need to be updated. The composition root where we create and assemble our objects works just the same way.

If you look at the composition root of the application (in the "PeopleViewer" project, App.xaml.cs file), the composition looks a bit complicated. That's because this code is using all of the decorators in the project. We'll take a closer look at that when we look at the Decorators themselves.

We do need some updates to the unit tests, and we'll take a closer look at asynchronous unit testing in a future article.

Starting the Service
If you download and run the application yourself, the service needs to be started in order to use the "ServiceReader" object. I've found that starting the service within Visual Studio is problematic, so I run the service from the command line.

Here's the easiest way that I've found. First, install the Power Commands for Visual Studio. From Visual Studio, choose "Tools", then "Extensions and Updates". From here, you can search for "Power Commands for Visual Studio".

Once this is installed, there is a new right-click menu item available. In the Solution Explorer, right-click on the "People.Service" project (the web service) and choose "Power Commands", then "Open Command Prompt". This gives you a command prompt at the project location.

From there, just type "dotnet run" to start the service. Then use "Ctrl+C" to stop the service.


Starting and stopping the service will be important to test the decorators.

Wrap Up
Data access is generally a long-running operation (relatively speaking) because it often involves network calls. Because of this, we should be making these calls asynchronously, whether we're accessing a web service, a database, or a file on the file system. The updates to the code to make things asynchronous are not terribly extensive, but the updates do assume that we have a good handle on using async methods.

Coming up, we'll look at a the Decorators in our project. We'll see how dependency injection (and the loose coupling it provides) let us easily add functionality to our application by snapping our pieces together in a different order. The new functionality comes without having to modify any of our existing code. And that's pretty cool.

Happy Coding!


3 comments:

  1. Hi Jeremy,

    I have checked your project and found it simple and easy to follow.

    In the previous article you mentioned a comment from Stephen Cleary (which is a sort of "source of truth about concurrency" to me) that I believe makes total sense when preparing a material like that.
    When I was first learning how to programming I would be very disappointed with materials with things like "not suitable for production" because I was trying to learn how to develop things, guess what, for production. But the truth is, there is so many details to take care that exploring everything at the same time would make the specific point disappear, specially for starters in programming.

    Long story short, IMHO, I believe you could put some comment in your code to warn people about the specific point that could be improved or, at least, used with some caution. For instance, "be careful about new HttpClient" or "loading the entire file into memory could cause problems".

    Anyways, thanks for sharing!

    ReplyDelete
    Replies
    1. Thank you, Anderson. I've struggled with whether to show "real code" or "demo code" quite a bit. I've landed on "demo code" so that we can focus on the topic at hand. The code in this article is a perfect example of that; it's really easy to get mired in async topics when we're focusing on DI.

      I've also struggled with adding comments because the correct approach is always "it depends". For example, newing up HttpClient is fine for this implementation because it gets created exactly once at application startup. With a different implementation, the Reader may be created multiple times, which would be a concern. And that still may be okay based on memory pressure, frequency of HTTP calls, and other factors. I don't have a solution that I'm happy with for this (yet). Thank you for adding your experience to the mix. Other viewpoints are always helpful. Thanks!

      Delete
  2. Very bad example showing returning IEnumerable in Task even though it is eager evaluated.

    ReplyDelete