AWS Lambda .NET Core 2.1 Support Released

Amazon Web Services (AWS) has just announced that its serverless function offering, AWS Lambda, now supports the .NET Core 2.1 runtime, which was released towards the end of May 2018.

Quoting the official announcement:

“Today we released support for the new .NET Core 2.1.0 runtime in AWS Lambda. You can now take advantage of this version’s more performant HTTP client. This is particularly important when integrating with other AWS services from your AWS Lambda function. You can also start using highly anticipated new language features such as Span<T> and Memory<T>.

“We encourage you to update your .NET Core 2.0 AWS Lambda functions to use .NET Core 2.1 as soon as possible. Microsoft is expected to provide long-term support (LTS) for .NET Core 2.1 starting later this summer, and will continue that support for three years. Microsoft will end its support for .NET Core 2.0 at the beginning of October, 2018[2]. At that time, .NET Core 2.0 AWS Lambda functions will be subject to deprecation per the AWS Lambda Runtime Support Policy. After three months, you will no longer be able to create AWS Lambda functions using .NET Core 2.0, although you will be able to update existing functions. After six months, update functionality will also be disabled.

“[1] See Microsoft Support for .NET Core for the latest details on Microsoft’s .NET Core support.
“[2] See this blog post from Microsoft about .NET Core 2.0’s end of life.”

The choice here seems obvious: upgrade and get faster HttpClient, new language features, and long-term support; or lose support for your functions targeting .NET Core 2.0 (whatever that actually means).

In order to migrate to .NET Core 2.1, you’ll need the latest tooling – either version 1.14.4.0 of the AWS Toolkit for Visual Studio, or version 2.2.0 of the Amazon.Lambda.Tools NuGet package.

Check out the official announcement at the AWS blog for more information, including additional tips on upgrading.

Orleans 2.0 Stateless Worker Grains

In this article, we’ll see how to create grains that automatically scale up and down depending on load, in Microsoft Orleans 2.0.

The source code for this article is very similar to that in “Getting Started with Microsoft Orleans 2.0 in .NET Core“, with a few key differences:

  • It has been modified to gracefully stop the silo and gracefully close the client.
  • It uses the latest packages at the time of writing this article – Orleans 2.0.3 and OrleansDashboard 2.0.7.
  • It uses a slightly different example, and the load generation has been adapted accordingly.

Since there’s nothing really new in the client and silo setup, we’ll be focusing mainly on the grain and load generation parts. However, you may find the full source code for this article in the Orleans2StatelessWorkers folder in the Gigi Labs BitBucket repository.

Example Grain

For the sake of example, we’ll imagine that the job of our Orleans cluster is to provide hashing as a service. A client provides an input string, and we’ll have a grain that computes a hash of the string (it doesn’t really matter what hash function it is – we’ll use MD5 in the example) and returns it.

Based on this requirement, we can easily write a grain and its corresponding interface to perform the hash calculation:

    public interface IHashGeneratorGrain : IGrainWithIntegerKey
    {
        Task<string> GenerateHashAsync(string input);
    }

    public class HashGeneratorGrain : Grain, IHashGeneratorGrain
    {
        private HashAlgorithm hashAlgorithm;

        public HashGeneratorGrain()
        {
            this.hashAlgorithm = MD5.Create();
        }

        public Task<string> GenerateHashAsync(string input)
        {
            var inputBytes = Encoding.UTF8.GetBytes(input);
            var hashBytes = hashAlgorithm.ComputeHash(inputBytes);
            var hashBase64Str = Convert.ToBase64String(hashBytes);

            return Task.FromResult(hashBase64Str);
        }
    }

Load Generation

Typically, when we talk about actor models, the whole point is to have an instance of an actor (grain in Orleans) per entity ID. For instance, you’d have a grain instance for each Device, Vehicle, BlogPost, Game, User, or whatever other domain object you’re dealing with. In this case, however, our grain is completely stateless, and there is no difference in behaviour between one activation and another. In fact, since the grain ID doesn’t matter, we can just pass in 0 as a sort of convention when requesting a grain of this kind:

var hashGenerator = client.GetGrain<IHashGeneratorGrain>(0);

Once we have an instance of the grain, we can generate some load by creating random strings and invoking the relevant method on the grain repeatedly:

            while (true)
            {
                var randomString = GenerateRandomString();
                var hash = await hashGenerator.GenerateHashAsync(randomString);
                Console.WriteLine(hash);
            }

You can monitor the grain’s activity from the Orleans Dashboard (localhost:8080 by default), and as you’d expect, there is only one activation of the grain:

Stateless Worker Grains

This situation is a very good fit for Stateless Worker Grains.

Normally, when you request a grain with a particular ID, you get a single activation – and it is a singleton throughout the cluster, so you would never (bar edge cases involving failover scenarios) get more than one instance of that grain in the cluster. However, if you just add a [StatelessWorker] attribute on the grain…

    [StatelessWorker]
    public class HashGeneratorGrain : Grain, IHashGeneratorGrain

…you’ll see very different behaviour:

Notice how there are now two activations of the HashGeneratorGrain, even though we’re still requesting an instance with ID 0.

When Orleans sees the [StatelessWorker] attribute, it will create a pool of grains behind the ID you specify. This is similar to a load balancer. Those grains are hidden behind that same ID, so you can’t access individual grains in the pool directly (it wouldn’t make any sense to do that). The number of grains will grow up to as many CPU cores are available on the machine, unless you pass an argument to the attribute specifying otherwise.

Aside from autoscaling, another important benefit of stateless worker grains is that they are always local. Orleans will always execute a request to a stateless worker on the same silo where the request was generated, spawning a new activation if necessary. This saves the overhead of potentially passing the request to an instance in a different silo (i.e. remote call), which makes a lot of sense for stateless workers that are pure logic and there’s no difference between activations running in different places.

Although stateless worker grains are best used for stateless logic (as one would expect), there is nothing preventing their use with state. However, coordination of state between multiple grain activations with the same ID can be complicated. The Stateless Worker Grains documentation describes some patterns where stateless worker grains with state make sense (although calling them that way is bizarre).

Summary

  • Use the [StatelessWorker] attribute to treat a grain as a stateless worker grain.
  • This creates a load-balanced autoscaling pool of grains with the same ID.
  • Requests to stateless worker grains are always local and never incur a remote call.
  • Stateless worker grains may have state, although this is unusual.

Accessing an ASP .NET Core Web Application Remotely

After setting up an empty ASP .NET Core Web Application, it’s easy to quickly run it and see something working, in the form of the usual “Hello World”:

When trying to deploy this somewhere though, you might be disappointed to notice that you can’t access the web application from another machine:

In fact, you’ll notice that you can’t even access it from the same machine if you use the actual hostname rather than localhost.

This is because by default, Kestrel will listen only on localhost. In order for another machine to access the web application using the server’s hostname, the web application must specify the endpoints on which Kestrel will listen to, using code or command-line arguments.

Note: you may also need to open a port in your firewall.

In code, this can be done by invoking UseUrls() in the webhost builder as follows:

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .UseUrls("http://myhostname:54691")
                .Build();

Replace “myhostname” with the hostname of the server, and note that the localhost endpoint will still work even though it’s not specified explicitly here.

If you want to pass the the endpoint(s) via command line parameters instead, you can do so via the --urls argument. First, you need to change the BuildWebHost() method generated by the project template as per this GitHub comment, to allow command line parameters to be passed to the WebHostBuilder via configuration:

public static IWebHost BuildWebHost(string[] args)
{
    var configuration = new ConfigurationBuilder().AddCommandLine(args).Build();

    return WebHost.CreateDefaultBuilder(args)
        .UseConfiguration(configuration)
        .UseStartup<Startup>()
        .Build();
}

Then, use the --urls argument when invoking dotnet run:

dotnet run --urls http://banshee:54691/

Either of these methods is fine to allow remote machines to access your ASP .NET Core web application.

.NET Core 3 to Support Desktop Applications… Kind of

A few days ago, Microsoft published a blog post titled “.NET Core 3 and Support for Windows Desktop Applications“. Just by reading the title, I’m pretty sure many of us jumped in their seats as thoughts like “WPF on Linux” became a source of excitement.

Image source: .NET Core 3 and Support for Windows Desktop Applications

Unfortunately however, the excitement turns into a disappointed “Oh. [Awkward silence] OK.” when reading that although .NET Core 3 is planned to support desktop applications built on technologies like Windows Forms and WPF, this support is for Windows only:

“Support for Windows desktop will be added as a set of “Windows Desktop Packs”, which will only work on Windows. .NET Core isn’t changing architecturally with this new version. We’ll continue to offer a great cross-platform product, focused on the cloud. We have lots of improvements planned for those scenarios that we’ll share later.”

The article does mention that this will bring several benefits ranging from performance improvements to deployment options, but this pales in comparison to the prospect of going cross-platform.

But given that they “are planning on releasing a first preview of .NET Core 3 later this year and the final version in 2019”, I can only wonder why they would spend a year doing a huge amount of work that most people won’t even care about, and pass it as a major release of .NET Core.

Time will tell, but one can get an idea of how people feel about this from the comments in the blog post.

My guess is that this could be part of a long-term strategy to retire the full .NET Framework, rather than bringing any real value to .NET Core or desktop applications.

Orleans 2.0 Dependency Injection

Dependency Injection (DI) has become a cornerstone of any well-designed and testable application nowadays, and Microsoft Orleans applications are no exception. In the 2.0 release, Microsoft Orleans has replaced some of its old internal frameworks (such as logging and dependency injection) with the corresponding Microsoft packages; thus these will be familiar for those who already worked with ASP .NET Core.

In this article we’ll focus on setting up dependency injection in the silo so that we can pass dependencies into our grains. However, if you read the dependency injection documentation page for Orleans 2.0, you’ll see that you can also have DI on the client side.

The source code for this article is the Orleans2DependencyInjection folder at the Gigi Labs BitBucket repository. Be careful not to confuse it with the OrleansDependencyInjection folder which targets Orleans 1.4.x.

TL;DR if you just want to quickly see how to do DI without going through the whole example, jump to the Registering Dependencies section.

Update 30th June 2018: The source code for this article needs a little adjusting, in order to gracefully stop the silo and gracefully close the client. Counterintuitively, directly disposing a silo or client is non-graceful and is generally discouraged.

Grain

We’ll start off with a project structure based on the Getting Organised article. Once that is in place, we can build an example representing a blog’s comment system. In the Grains project, we’ll add a grain representing a blog post, and that will be responsible for saving and retrieving all comments for that blog post.

    public class BlogPostGrain : Grain, IBlogPostGrain
    {
        private ICommentRepository repo;
        private ITimeService time;

        public BlogPostGrain(ICommentRepository repo, ITimeService time)
        {
            this.repo = repo;
            this.time = time;
        }

        public Task SaveCommentAsync(int blogPostId, InputComment comment)
        {
            var storedComment = new StoredComment()
            {
                Name = comment.Name,
                EmailAddress = comment.EmailAddress,
                Body = comment.Body,
                Timestamp = this.time.UtcNow
            };

            return this.repo.SaveCommentAsync(blogPostId, storedComment);
        }

        public Task<List<StoredComment>> GetCommentsAsync(int blogPostId)
            => this.repo.GetCommentsAsync(blogPostId);
    }

There are a few classes and interfaces in here that we haven’t created yet, but let’s understand what we’re doing here. We have a dependency on a repository where the comments will be held (whatever that is – we don’t care about the implementation at this stage). The grain acts mostly as a pass-through to this repository for storage and retrieval of comments, but when saving, we transform it by adding a timestamp. We use different DTOs for input comments and stored comments so that it is not possible to supply a timestamp with the input data.

We also have a second dependency on something called a time service. While you could just use DateTime.UtcNow in your code, time is typically one of the dependencies you want to factor out of your unit tests because it can affect the results. So we wrap DateTime.UtcNow in something we can mock, just for the sake of unit tests later.

Contracts

In the Contracts project, we’ll add all our interfaces and DTOs. Let’s start with our dependencies:

    public interface ITimeService
    {
        DateTime UtcNow { get; }
    }

    public interface ICommentRepository
    {
        Task SaveCommentAsync(int blogPostId, StoredComment comment);
        Task<List<StoredComment>> GetCommentsAsync(int blogPostId);
    }

Then we have our grain interface:

    public interface IBlogPostGrain : IGrainWithIntegerKey
    {
        Task SaveCommentAsync(int blogPostId, InputComment comment);
        Task<List<StoredComment>> GetCommentsAsync(int blogPostId);
    }

And finally our DTOs:

    public class InputComment
    {
        public string Name { get; set; }
        public string EmailAddress { get; set; }
        public string Body { get; set; }
    }

    public class StoredComment : InputComment
    {
        public DateTime Timestamp { get; set; }
    }

Dependency Implementations

In the Silo, we can create implementations for our dependencies.

To keep it simple, we’ll implement our repository using a ConcurrentDictionary. This is a volatile, in-memory implementation that is for demonstration only, but it allows us to focus on what we’re doing with Orleans, rather than distracting us with store-specific details.

Note: We could also use Orleans storage providers, but that’s out of scope here.

    public class MemoryCommentRepository : ICommentRepository
    {
        private ConcurrentDictionary<int, List<StoredComment>> dict;

        public MemoryCommentRepository()
        {
            this.dict = new ConcurrentDictionary<int, List<StoredComment>>();
        }

        public Task<List<StoredComment>> GetCommentsAsync(int blogPostId)
        {
            this.dict.TryGetValue(blogPostId, out var comments);
            return Task.FromResult(comments);
        }

        public Task SaveCommentAsync(int blogPostId, StoredComment comment)
        {
            this.dict.AddOrUpdate(blogPostId,
                addValue: new List<StoredComment>() { comment },
                updateValueFactory: (postId, commentsList) => {
                    commentsList.Add(comment);
                    return commentsList;
                });

            return Task.CompletedTask;
        }
    }

The time service is really simple: it just wraps DateTime.UtcNow.

    public class TimeService : ITimeService
    {
        public DateTime UtcNow => DateTime.UtcNow;
    }

Registering Dependencies

All the above was setting up the example, and now we get to the part we’ve all been waiting for.

We’ll set up our silo’s code similarly to what we’ve done in the past two articles, but this time, we’ll add a call to ConfigureServices() in order to register our dependencies:

            var siloBuilder = new SiloHostBuilder()
                .UseLocalhostClustering()
                .UseDashboard(options => { })
                .Configure<ClusterOptions>(options =>
                {
                    options.ClusterId = "dev";
                    options.ServiceId = "Orleans2DependencyInjection";
                })
                .Configure<EndpointOptions>(options =>
                    options.AdvertisedIPAddress = IPAddress.Loopback)
                .ConfigureServices(services =>
                {
                    services.AddSingleton<ITimeService, TimeService>();
                    services.AddSingleton<ICommentRepository, MemoryCommentRepository>();
                })
                .ConfigureLogging(logging => logging.AddConsole());

Note: as per the previous articles, C# 7.1 or above is needed in order to allow async/await in Main().

Since AddSingleton() is an extension method coming from Mirosoft.Extensions.DependencyInjection (already included as a dependency of Microsoft.Orleans.Core), you’ll need to add the following for this to work:

using Microsoft.Extensions.DependencyInjection;

The API

We can complete this example by exposing the grain’s functionality via our Web API. For this, we’ll add the following controller:

    [Produces("application/json")]
    [Route("api/BlogPosts")]
    public class BlogPostsController : Controller
    {
        private IClusterClient orleansClient;

        public BlogPostsController(IClusterClient orleansClient)
        {
            this.orleansClient = orleansClient;
        }

        [HttpGet]
        public Task<List<StoredComment>> Get(int blogPostId)
        {
            var grain = this.orleansClient.GetGrain<IBlogPostGrain>(blogPostId);
            return grain.GetCommentsAsync(blogPostId);
        }

        [HttpPut]
        public async Task Put(int blogPostId, InputComment comment)
        {
            var grain = this.orleansClient.GetGrain<IBlogPostGrain>(blogPostId);
            await grain.SaveCommentAsync(blogPostId, comment);
        }
    }

 

Note: as I write this, I am noticing a quirk in this implementation. If you get a grain with a blogPostId, then why do you have to pass it again to call the method on the grain? The grain should know its ID already. Fair enough – that was an oversight on my part. But since grain IDs are retrieved using extension methods, and thus their retrieval would also need to be mocked, I’d rather not overcomplicate things in this example.

We can then add Swagger to the Web API and wire up the Orleans client as we did in the Getting Organised article (complete with retries):

       private IClusterClient CreateOrleansClient()
        {
            var clientBuilder = new ClientBuilder()
                .UseLocalhostClustering()
                .Configure<ClusterOptions>(options =>
                {
                    options.ClusterId = "dev";
                    options.ServiceId = "Orleans2DependencyInjection";
                })
                .ConfigureLogging(logging => logging.AddConsole());

            var client = clientBuilder.Build();

            client.Connect(async ex =>
            {
                Console.WriteLine("Retrying...");
                await Task.Delay(3000);
                return true;
            }).Wait();

            return client;
        }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            var orleansClient = CreateOrleansClient();
            services.AddSingleton<IClusterClient>(orleansClient);

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
            });

            services.AddMvc();
        }

Manual Testing with Swagger

We can quickly add a couple of comments on a blog post and retrieve them to see that all this is working:

Note: seems like Swagger recently changed their UI. I liked it a lot better before.

Unit Testing

Dependency injection makes it easy for us to write unit tests. Let’s add a Grains.Tests project (.NET Core Console App), add a reference to the Grains project, and install the following packages:

Install-Package Microsoft.NET.Test.Sdk
Install-Package NUnit
Install-Package NUnit3TestAdapter
Install-Package Moq

Remove the auto-generated Program.cs file and add the following test class instead:

    public class BlogPostGrainTests
    {
        [Test]
        public async Task SaveCommentTest()
        {
            // arrange

            const int blogPostId = 1;

            var fixedDateTime = new DateTime(2018, 4, 29, 18, 28, 33, DateTimeKind.Utc);
            var mockRepo = new Mock<ICommentRepository>(MockBehavior.Strict);
            var mockTimeService = new Mock<ITimeService>(MockBehavior.Strict);

            mockRepo.Setup(x => x.SaveCommentAsync(blogPostId, It.IsAny<StoredComment>()))
                    .Returns(Task.CompletedTask);
            mockTimeService.Setup(x => x.UtcNow)
                           .Returns(fixedDateTime);

            var grain = new BlogPostGrain(mockRepo.Object, mockTimeService.Object);

            const string name = "George";
            const string emailAddress = "george@food.com";
            const string body = "I'm hungry!";

            var comment = new InputComment()
            {
                Name = name,
                EmailAddress = emailAddress,
                Body = body
            };

            // act

            await grain.SaveCommentAsync(blogPostId, comment);

            // assert

            mockRepo.Verify(x => x.SaveCommentAsync(blogPostId, It.Is<StoredComment>(
                c => c.Name == name
                  && c.EmailAddress == emailAddress
                  && c.Body == body
                  && c.Timestamp == fixedDateTime
            )));
        }
    }

This test verifies that the submitted comment was passed on to the store with the generated timestamp. It should pass:

Exercises

We’ve seen a complete example featuring dependency injection. Registering dependencies is easy; most of the effort in this article was around building the example to demonstrate that.

As you can see, you can write unit tests for grains just as you would for any other class, without having to resort to the Orleans TestCluster.

There are a number of ways you can take this further:

  1. Have the grain perform a validation against the email address, and write unit tests for that.
  2. Have the grain retrieve its own ID (removing the need to pass it as a parameter to its methods), and find a way to mock the grain retrieval.
  3. Try dependency injection in the Orleans client.

"You don't learn to walk by following rules. You learn by doing, and by falling over." — Richard Branson