Categories
Software

Microsoft Tye

Tye’s promise:

  1. Making development of microservices easier by:
    • Running many services with one command
    • Using dependencies in containers
    • Discovering addresses of other services using simple conventions
  2. Automating deployment of .NET applications to Kubernetes by:
    • Automatically containerizing .NET applications
    • Generating Kubernetes manifests with minimal knowledge or configuration
    • Using a single configuration file

Installation

dotnet tool install --global Microsoft.Tye --version 0.10.0-alpha.21420.1

Running a single service

Let’s create a new front-end project, and start it via Tye.

mkdir microservices
cd microservices
dotnet new razor -n frontend
tye run frontend

Here we can see Tye has built the project, configured HTTP bindings, and started it running. We can navigate to 127.0.0.1:8000 to view the Tye dashboard.

We can stop Tye with Ctrl+C.

Running multiple services

Now suppose out front-end project needs to talk to a back-end API. We will create a solution file containing both projects

dotnet new webapi -n backend
dotnet new sln
dotnet sln add frontend backend
tye run

Now we have both services running.

We will wire up the two services using Tye’s service discovery.

  1. Stop Type with Ctrl+C.
  2. Add WeatherForecast.cs to the frontend project, to match the similar class in backend.
namespace frontend;

public class WeatherForecast
{
    public DateTime Date { get; set; }

    public int TemperatureC { get; set; }

    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    public string? Summary { get; set; }
}
  1. Add WeatherClient.cs to the frontend project:
namespace frontend;

using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

public class WeatherClient
{
    private readonly JsonSerializerOptions options = new JsonSerializerOptions()
    {
        PropertyNameCaseInsensitive = true,
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    };

    private readonly HttpClient client;

    public WeatherClient(HttpClient client)
    {
        this.client = client;
    }

    public async Task<WeatherForecast[]> GetWeatherAsync()
    {
        var responseMessage = await this.client.GetAsync("/weatherforecast");
        var stream = await responseMessage.Content.ReadAsStreamAsync();
        return await JsonSerializer.DeserializeAsync<WeatherForecast[]>(stream, options);
    }
}

4. Add a reference to the Microsoft.Tye.Extensions.Configuration package to the frontend project.

dotnet add frontend/frontend.csproj package Microsoft.Tye.Extensions.Configuration  --version "0.2.0-*"

  1. Now we register WeatherClient for dependency injection in out start-up code:
using frontend;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddHttpClient<WeatherClient>(client =>
{
    client.BaseAddress = builder.Configuration.GetServiceUri("backend");
});

builder.Services.AddRazorPages();

...
  1. Add a Forecasts property to the Index page model under Pages\Index.cshtml.cs in the frontend project.
public WeatherForecast[] Forecasts { get; set; }
  1. Change the OnGet method to take the WeatherClient to call the backend service and store the result in the Forecasts property:
public async Task OnGet([FromServices]WeatherClient client)
{
    Forecasts = await client.GetWeatherAsync();
}
  1. Render the Forecasts property in the Index.cshtml razor view:
@page
@model IndexModel
@{
   ViewData["Title"] = "Home page";
}

<div class="text-center">
   <h1 class="display-4">Welcome</h1>
   <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

Weather Forecast:

<table class="table">
   <thead>
      <tr>
         <th>Date</th>
         <th>Temp. (C)</th>
         <th>Temp. (F)</th>
         <th>Summary</th>
      </tr>
   </thead>
   <tbody>
      @foreach (var forecast in @Model.Forecasts)
      {
         <tr>
            <td>@forecast.Date.ToShortDateString()</td>
            <td>@forecast.TemperatureC</td>
            <td>@forecast.TemperatureF</td>
            <td>@forecast.Summary</td>
         </tr>
      }
   </tbody>
</table>

Now if we re-start the services with tye run we should see the front-end service is displaying random weather data generated by the back-end.

Tye configuration

Let’s generate Tye’s optional configuration file, tye.yaml, for our solution:

tye init

Here we can re-configure the services, e.g., we can set specific bindings.

(Changing the name of the backend service means we must also update the reference to it in the frontend Program.cs).

Adding external dependencies (Redis)

Let’s change the WeatherForecastController.Get() method in the backend to cache weather information using an IDistributedCache.

  1. Add these using statements
using Microsoft.Extensions.Caching.Distributed;
using System.Text.Json;
  1. Update Get()
    [HttpGet(Name = "GetWeatherForecast")]
    public async Task<string> Get([FromServices]IDistributedCache cache)
    {
        var weather = await cache.GetStringAsync("weather");

        if (weather == null)
        {
            var rng = new Random();
            var forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();

            weather = JsonSerializer.Serialize(forecasts);

            await cache.SetStringAsync("weather", weather, new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(5)
            });
        }
        return weather;
    }
  1. Add a package reference to Microsoft.Extensions.Caching.StackExchangeRedis
cd backend/
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
cd ..
  1. Register the IDistributedCache implementation in the backend startup code.
...
builder.Services.AddStackExchangeRedisCache(o =>
{
    o.Configuration = builder.Configuration.GetConnectionString("redis");
});

builder.Services.AddControllers();
...
  1. Modify tye.yaml to include redis as a dependency.
...
- name: redis
  image: redis
  bindings:
  - port: 6379
    connectionString: "${host}:${port}"
- name: redis-cli
  image: redis
  args: "redis-cli -h redis MONITOR"