Microservices in .NET with Project Tye

Microservices in .NET with Project Tye

Setting up microservices is often a pain. It can be a tiny misconfiguration or simply a lack of required knowledge. Fortunately there is one tool that can take all of this pain away - Project Tye.

Getting started 📌

Start with a simple application consisting of two services, one of which triggers the other one. It may sound trivial but it's enough to showcase the basics of the Project Tye usage and its features.

Installing Project Tye 🧰

Project Tye is a .NET CLI tool that require .NET Core 3.1 SDK for version 0.10.x or .NET 6 SDK for all newer releases. I am using the 0.11.0-alpha.22057 version when writing this article. Install the latest version of Tye globally by running the following command.

dotnet tool install -g Microsoft.Tye

If you install it successfully you'll be able to run the tye command.

Prepare Solution 🧩

Create FakeWeatherApp solution with two projects WeatherApi and NotificationApi. Use the Visual Studio or the following commands.

mkdir FakeWeatherApp
cd FakeWeatherApp
dotnet new sln -n FakeWeatherApp
dotnet new webapi -n WeatherApi
dotnet new webapi -n NotificationApi
dotnet sln add WeatherApi NotificationApi

Adding Tye support 🔧

Tye uses a tye.yaml file for configuration purposes. Create one by running the tye init command.

# tye application configuration file
name: fakeweatherapp
services:
- name: weatherapi
  project: WeatherApi/WeatherApi.csproj
- name: notificationapi
  project: NotificationApi/NotificationApi.csproj

Each service is an individual part of an entire application. It could be a project, container or executable.

Tye automatically detected our two projects and added them to its configuration file. Unfortunately, if you add more projects, you will have to update this file manually. For now, let's leave the configuration as it is and see the output of the tye run command.

tye_run.png

Tye searches for free ports and creates bindings automatically. It also hosts the Tye Dashboard with some basic but useful information about services.

Tye Dashboard 📺

Dashboard is available at http://localhost:8000/ by default. In case of the port being taken, Tye will use a different, random free port. See the output of tye run to find out what yours is.

dashboard.png

Let's go over all the Services table columns.

  • Name: the service name from tye.yaml file, which is also a link to the metrics page shown below stats.png
  • Type: the service type, which can be project, container, executable or external
  • Source: the path of the source from which the service was built
  • Bindings: a links to the URLs of the service
  • Replicas: how many replicas are running / how many replicas are expected to be running
  • Restarts: how many restarts have happened
  • Logs: a link to the service streaming logs logs.png

Connecting the Services 🤙🏻

At this point we have two exactly the same applications running. Let's make some changes and then wire them up!

NotificationApi

Firstly, remove all the weather logic that was automatically added by dotnet new webapi command and create a new controller called NotificationController. Its route should be /notifications. Then add a new action that supports HTTP POST method and accepts an object with Type and Message fields. Finally, to see any results, just log incoming data to the console. A sample code is shown below.

using Microsoft.AspNetCore.Mvc;

namespace NotificationApi.Controllers;

[ApiController]
[Route("notifications")]
public class NotificationController : ControllerBase
{
    private readonly ILogger<NotificationController> _logger;

    public NotificationController(ILogger<NotificationController> logger)
    {
        _logger = logger;
    }

    [HttpPost]
    public ActionResult Post([FromBody] NotificationDto notificationDto)
    {
        _logger.LogInformation(notificationDto.ToString());

        return NoContent();
    }
}

public record NotificationDto(string Type, string Message);

WeatherApi

Let's start by modifying the WeatherForecastController class. Change its route to /weather-forecasts to follow the naming convention. Remove the field of type ILogger as it's not necessary. Use HttpClient to connect to the second service. To do so, add HttpClientFactory field and expect it in the constructor function. Then create a client and make a HTTP POST request to /notifications url.

using Microsoft.AspNetCore.Mvc;

namespace WeatherApi.Controllers;

[ApiController]
[Route("weather-forecasts")]
public class WeatherForecastController : ControllerBase
{
    private static readonly string[] Summaries =
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    private readonly IHttpClientFactory _httpClientFactory;

    public WeatherForecastController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    [HttpGet]
    public async Task<IEnumerable<WeatherForecast>> Get()
    {
        var httpClient = _httpClientFactory.CreateClient("notificationapi");
        await httpClient.PostAsJsonAsync("/notifications", new
        {
            Type = "TestType",
            Message = "Test"
        });

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

Of course, it won't work without the configuration. Set up a new HttpClient in Program.cs file. Remember to do it before builder.Build() call.

builder.Services.AddHttpClient("notificationapi", client =>
{
    client.BaseAddress = // ???
});

Usually we put all environment variables into the .appsettings file, but this time we don't know what the actual NotificationApi address is. We decided not to use a fixed port so we have to get it somehow.

The Tye team provides a NuGet package with the configuration extensions called Microsoft.Tye.Extensions.Configuration. It makes very easy to get the service URI not only during the development but also when deploying to Kubernetes.

builder.Services.AddHttpClient("notificationapi", client =>
{
    client.BaseAddress = builder.Configuration
        .GetServiceUri("notificationapi"); // service name from tye.yaml
});

Testing 🕹️

Use the tye run command to start the application and open the dashboard. Click the WeatherApi url and append /swagger at the end. You'll see the Swagger UI. Make a HTTP GET request to /weather-forecasts.

weather_api_swagger.png

Open the NotificationApi logs to check if it was successfully called. See the results below.

notification_api_logs.png

Debugging 🪲

Tye can run each service in debug mode. It exposes a hook to attach to from your editor or IDE. The debugging experience is the same as with any other local project. You can add breakpoints, step through code, see locals etc.

Run in debug mode using the command below. Alternatively, pass * instead of a specific service name for all services.

tye run --debug notificationapi

Open Debug / Attach to Process... window in the Visual Studio and search for the NotificationApi.exe to attach the built-in debugger.

attach.png

External dependencies 🔗

Applications are usually built using many databases, services and other processes. Fortunately, the Tye makes it quite easy to add and setup any Docker image.

A PostgreSQL database setup as an example.

# tye application configuration file
name: app
services:
  - name: postgres
    image: postgres
    env:
      - name: POSTGRES_PASSWORD
        value: "@password1"
      - name: POSTGRES_DB
        value: "test_db"
    bindings:
      - port: 5432
        connectionString: Server=${host};Port=${port};User Id=postgres;Password=${env:POSTGRES_PASSWORD};

Learn more 📖

Check the project's GitHub repository for more https://github.com/dotnet/tye. Give it a star so they can continue developing this awesome tool.

Conclusion 🏆

Project Tye is an experimental tool, so I wouldn't recommend using it in production. However, it might be a great idea to use it in your side project. Who knows, maybe thanks to the time saved on setup, you will finish it and become a millionaire 💸.

Did you find this article valuable?

Support Aleksander Woźnica by becoming a sponsor. Any amount is appreciated!