Microservices with Steeltoe and Cloud Foundry: A .NET App Using MS Face API
Why Steeltoe
Steeltoe is an open-source project that can help developers to create .NET microservices on Cloud Foundry, providing access to Spring Cloud and NetflixOSS tools powering cloud-native Java apps.
In our previous post, we explained how to consume Spring Cloud / NetflixOSS services from a .NET Core app using Steeltoe. If you want to explore Steeltoe in detail, you may also check out these two articles: Introducing Steeltoe and Steeltoe for ASP.NET 4.x.
Microservices-based and cross-platform
With this blog post, we wanted to go further and created a sample .NET Core app using RabbitMQ and Microsoft Cognitive Services API. The app allows users to detect human faces on a photo and extract avatars from it. With .NET Core, the code can be used for both Windows and Linux systems.
This example demonstrates the results of how the app works. 😉 We’ve taken the source photo (featuring the legendary James Watters) from Nima Badiey’s Twitter:
The extracted avatars:
The solution itself consists of a website and two services. All communication between the website and the services was done via RabbitMQ queues. Implementing the Competing Consumers pattern made the system more reliable and scalable, while also helping us to decouple this system into small components.
The source code of the app is available in this GitHub repository.
Website implementation
We used Steeltoe to simplify the code needed to initialize the RabbitMQ client. It hooks up automatically the app’s environment variables to the RabbitMQ client and provides a factory for creating instances of the client.
Configuration initialization:
public class Startup { public Startup(IHostingEnvironment env, ILoggerFactory logFactory) { ... // Set up configuration sources. var builder = new ConfigurationBuilder() ... .AddEnvironmentVariables() // Add to configuration the Cloudfoundry VCAP settings .AddCloudFoundry(); Configuration = builder.Build(); } public void ConfigureServices(IServiceCollection services) { ... services.AddRabbitConnection(Configuration); } }
The FromServices
attribute tells the dependency injection resolver to create an instance of the client using ConnectionFactory
initialized with a connection string and credentials from environment variables.
public class MainController : ApiController { private readonly ConnectionFactory _rabbitConnection; public MainController([FromServices] ConnectionFactory rabbitConnection) { _rabbitConnection = rabbitConnection; } }
Building the project
To create a framework-dependent deployment, we set "type": "platform"
in the project.json file. To build the project, you can run the following commands:
dotnet restore
The command restores NuGet packages for third-party dependencies.
dotnet publish -o publish -c Release
The command builds and publishes the project into the publish folder.
Cloud Foundry manifest file
The manifest.yml file contains settings for application deployment to Cloud Foundry:
--- applications: - name: avatarmaker.web memory: 512M command: dotnet ./AvatarMaker.Web.dll --server.urls "http://*:$PORT" buildpack: https://github.com/cloudfoundry-community/asp.net5-buildpack.git services: - avatarmaker.rmq
Since the .NET framework is installed into containers by Cloud Foundry’s .NET Core buildpack (that’s why we opted for the framework-dependent deployment setting in the project), the command for starting the application looks as follows:
dotnet ./AvatarMaker.Web.dll --server.urls "http://*:$PORT"
We also needed to set the RabbitMQ service as a dependency to bind the service instance automatically to the app.
services: - avatarmaker.rmq
Deployment
To create an instance of the RabbitMQ service and deploy the application, the following commands should be used.
Get the list of services available in the Cloud Foundry marketplace:
cf marketplace service plans description mysql 100mb, 1gb MySQL databases on demand p-rabbitmq standard RabbitMQ is a robust and scalable high-performance multi-protocol messaging broker. postgresql95 free postgresql 9.5 (beta1) service for application development and testing redis shared-vm, dedicated-vm Redis service to provide a key-value store
Create an instance of the service:
cf create-service p-rabbitmq standard avatarmaker.rmq
Deploy the application:
cf push -p publish
Services implementation
Similarly to the website implementation process, we relied on Steeltoe to create instances of the RabbitMQ client:
IServiceCollection services = new ServiceCollection(); var config = new ConfigurationBuilder() .AddEnvironmentVariables() .AddCloudFoundry() .Build(); services.AddRabbitConnection(config); var connectionFactory = services.BuildServiceProvider().GetService<ConnectionFactory>();
The Face API from Microsoft Cognitive Services was used to detect human faces in images. The Recognizer app sends an image URL to the web service, and the latter returns an array of rectangles with faces. The data is then sent to the message broker and processed by the last microservice in the chain—the EmailSender app. It downloads the image, crops it using the list of rectangles, and sends the cropped out face avatars back by email.
Building the services
To build the services, see the website building steps described above.
Cloud Foundry manifest file
The manifest.yml file for the services is different from the website’s manifest file:
--- applications: - name: avatarmaker.emailsender memory: 512M buildpack: https://github.com/cloudfoundry/dotnet-core-buildpack.git command: dotnet ./AvatarMaker.EmailSender.dll services: - avatarmaker.rmq no-route: true health-check-type: none
This application is a worker without an HTTP endpoint, so no routes are needed:
no-route: true
You have to disable health-check
to make Cloud Foundry believe that the app is running properly:
health-check-type: none
Deployment
The deployment process is the same as that of the website, but with the first two steps skipped: there is no need to create another service instance.
Note that this demo app has some deficiencies that real-life applications don’t have the luxury to disregard, such as:
- App settings are hardcoded, while the best practice is to use centralized storage for the settings, such as Spring Cloud Config Server—Steeltoe offers helper classes for it.
- Nothing was done for error logging, and only the Cloud Foundry log aggregator component was used.
The source code of the app is available in this GitHub repository.
Conclusion
With Cloud Foundry, a .NET developer can create and deploy a scalable microservices-based .NET Core app fast and cost-effectively. Usage of Steeltoe’s adapter for ASP.NET Core RabbitMQ Client saved the effort required to initialize RabbitMQ connectors—otherwise, connection strings and credentials would need to be extracted from environment variables manually.
The developed system can be deployed on any Cloud Foundry distribution. Since the project uses .NET Core, it supports both Windows and Linux stacks. However, since .NET Core is a cross-platform technology, one should not expect that it supports the full .NET Framework functionality.