In the previous lab, our Knative service simply logged out the received Pub/Sub event. While this might be useful for debugging, it's not terribly exciting.
Cloud Translation API is one of Machine Learning APIs of Google Cloud. It can dynamically translate text between thousands of language pairs. In this lab, we will use translation requests sent via Pub/Sub messages and use Translation API to translate text between languages.
Since we're making calls to Google Cloud services, you need to make sure that the outbound network access is enabled, as described in the previous lab.
Let's start with creating an empty ASP.NET Core app:
dotnet new web -o translation-csharp
Inside the translation-csharp
folder, update Startup.cs to log incoming messages for now:
using System;
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace translation_csharp
{
public class Startup
{
private readonly ILogger _logger;
public Startup(ILogger<Startup> logger)
{
_logger = logger;
}
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.Run(async (context) =>
{
using (var reader = new StreamReader(context.Request.Body))
{
try
{
var content = reader.ReadToEnd();
_logger.LogInformation($"Received content: {content}");
await context.Response.WriteAsync(content);
}
catch (Exception e)
{
_logger.LogError("Something went wrong: " + e.Message);
await context.Response.WriteAsync(e.Message);
}
}
});
}
}
}
Change the log level in appsettings.json
to Information
:
{
"Logging": {
"LogLevel": {
"Default": "Information"
}
},
"AllowedHosts": "*"
}
At this point, the sample simply logs out the received messages.
Let's first define the translation protocol we'll use in our sample. The body of Pub/Sub messages will include text and the languages to translate from and to as follows:
{text = 'Hello World', from='en', to='es'}: English to Spanish
{text = 'Hello World', from='', to='es'}: Detected language to Spanish
{text = 'Hello World', from='', to=''}: Error
To encapsulate this, create a TranslationRequest.cs class:
namespace translate_csharp
{
public class TranslationRequest
{
public string From;
public string To;
public string Text;
}
}
Our Knative service will receive Pub/Sub messages in the form of CloudEvents which roughly has the following form:
{
"ID": "",
"Data": "",
"Attributes": {
},
"PublishTime": ""
}
In this case, the actual translation request will be Base64 encoded in Data
field and it's the only thing we're interested in.
Create a CloudEvent.cs class to help us parse CloudEvents and decode Data
field:
using System;
using System.Collections.Generic;
using System.Text;
namespace translation_csharp
{
public class CloudEvent
{
public string ID;
public string Data;
public Dictionary<string, string> Attributes;
public string PublishTime;
public string GetDecodedData() => (
string.IsNullOrEmpty(Data) ?
string.Empty :
Encoding.UTF8.GetString(Convert.FromBase64String(Data)));
}
}
Before adding Translation API code to our service, let's make sure Translation API is enabled in our project:
gcloud services enable translate.googleapis.com
And add Translation API NuGet package to our project:
dotnet add package Google.Cloud.Translation.V2
Finally, we can change Startup.cs to first extract the CloudEvent
and then extract TranslationRequest
out of it.
var cloudEvent = JsonConvert.DeserializeObject<CloudEvent>(content);
if (cloudEvent == null) return;
var decodedData = cloudEvent.GetDecodedData();
_logger.LogInformation($"Decoded data: {decodedData}");
var translationRequest = JsonConvert.DeserializeObject<TranslationRequest>(decodedData);
Once we have the translation request, we can pass that to Translation API:
_logger.LogInformation("Calling Translation API");
var response = await TranslateText(translationRequest);
_logger.LogInformation($"Translated text: {response.TranslatedText}");
if (response.DetectedSourceLanguage != null)
{
_logger.LogInformation($"Detected language: {response.DetectedSourceLanguage}");
}
await context.Response.WriteAsync(response.TranslatedText);
You can see the full code in Startup.cs.
Before building the Docker image, make sure the app has no compilation errors:
dotnet build
Create a Dockerfile for the image:
FROM microsoft/dotnet:2.2-sdk
WORKDIR /app
COPY *.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o out
ENV PORT 8080
ENV ASPNETCORE_URLS http://*:${PORT}
CMD ["dotnet", "out/translation-csharp.dll"]
Build and push the Docker image (replace meteatamel
with your actual DockerHub):
docker build -t meteatamel/translation-csharp:v1 .
docker push meteatamel/translation-csharp:v1
Create a trigger.yaml file.
apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
name: translation-csharp
spec:
runLatest:
configuration:
revisionTemplate:
spec:
container:
# Replace meteatamel with your actual DockerHub
image: docker.io/meteatamel/translation-csharp:v1
metadata:
annotations:
# Disable scale to zero with a minScale of 1.
autoscaling.knative.dev/minScale: "1"
---
apiVersion: eventing.knative.dev/v1alpha1
kind: Trigger
metadata:
name: translation-csharp
spec:
subscriber:
ref:
apiVersion: serving.knative.dev/v1alpha1
kind: Service
name: translation-csharp
This defines the Knative Service that will run our code and Trigger to connect to Pub/Sub messages.
kubectl apply -f trigger.yaml
Check that the service and trigger are created:
kubectl get ksvc,trigger
We can now test our service by sending a translation request message to Pub/Sub topic:
gcloud pubsub topics publish testing --message="{text:'Hello World', from: 'en', to:'es'}"
Wait a little and check that a pod is created:
kubectl get pods --selector serving.knative.dev/service=translation-csharp
You can inspect the logs of the subscriber (replace <podid>
with actual pod id):
kubectl logs --follow -c user-container <podid>
You should see something similar to this:
info: translation_csharp.Startup[0]
Decoded data: {text:'Hello World', from: 'en', to:'es'}
info: translation_csharp.Startup[0]
Calling Translation API
info: translation_csharp.Startup[0]
Translated text: Hola Mundo
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 767.2586ms 200