As developers, we have an arsenal of technologies at our disposal. While using new technologies can make us more productive, setting them up can drain us of enthusiasm and stunt our creative momentum. Container technologies, especially Docker, have blunted a lot of the pain inflicted by continuous yak-shaving. As .NET developers, we’ve benefited greatly from the cross-platform efforts of the teams at Microsoft.

The effort of the .NET team has allowed us to play nicely with other ecosystems, have integrations with non-Microsoft stacks, and ultimately be better for it.

In this post, we’ll be exploring a Kubernetes-like approach to developing a .NET microservices and distributed solutions locally and how we can take advantage of a new library called Tye.

What Is Tye

Project Tye is a tool that attempts to mimic the surface behavior of Kubernetes but with a focus on .NET projects. The tool provides local orchestration of projects, container instances, and other dependencies.

Tye is a tool that makes developing, testing, and deploying microservices and distributed applications easier. Tye

The main goals of the project include:

  • Simplify microservices development locally.
  • Make deploying .NET microservice solutions effortless.

In summary, Project Tye is attempting to reduce the overhead of developing distributed solutions in the .NET ecosystem while improving the time to production.

It’s essential for us to take note that Project Tye is still experimental and is its alpha stages.

A fun fact about the name Tye. The word Tye is an obsolete British term for a small box. Quite an apt name, given we’ll be dealing with containers.

Getting Started

To start, we can install Project Tye as a global .NET tool. The installation of Tye occurs via NuGet and requires that we have .NET Core 3.1 installed as part of our development environment.

dotnet tool install -g Microsoft.Tye --version "0.1.0-alpha.20209.5"

We can verify that the installation worked by running the version command in our console.

tye --version
> 0.1.0-alpha.20209.5+e3fc0045bd1e5913da935241874761929f1e8465

We can also get an idea of the other functions of Tye by running the help command.

> tye --help
tye:
  Developer tools and publishing for microservices.

Usage:
  tye [options] [command]

Options:
  -?, -h, --help    Show help and usage information
  --version         Show version information

Commands:
  init <path>      create a yaml manifest
  run <path>       run the application
  build <path>     build containers for the application
  deploy <path>    deploy the application

For the rest of this post, we’ll be using a completed project developed following the Tye getting started guide. Folks are welcome to walk through the guide themselves or clone the finished sample.

Clone The Repository Here.

Local Development

Looking at the sample solution, the first thing that sticks out is the existence of a tye.yaml file at the root of the solution. The YAML file lists our projects and external dependencies. In this solution, we have two projects, frontend and backend. We also have a reference to Redis which caches the results of our backend API.

name: microservice
services:
- name: frontend
  project: frontend/frontend.csproj
- name: backend
  project: backend/backend.csproj
- name: redis
  image: redis
  bindings:
    - port: 6379
      connectionString: "${host}:${port}"
- name: redis-cli
  image: redis
  args: "redis-cli -h redis MONITOR"

We can read more about the Tye YAML specification on the GitHub repository. The design of the schema correlates with that of a Kubernetes configuration.

To run the project, we only need to execute the Tye global tool at the root of our working directory.

> tye run

We get the following console output. We’ve reduced it for brevity.

Loading Application Details...
Launching Tye Host...

[14:33:29 INF] Executing application from /Users/khalidabuhakmeh/Projects/dotnet/microservice/tye.yaml
[14:33:29 INF] Dashboard running on http://127.0.0.1:8000
...
[14:33:37 INF] backend_0fee6b73-d running on process id 21159 bound to http://localhost:56994, https://localhost:56995
[14:33:37 INF] frontend_9c4a283e-7 running on process id 21160 bound to http://localhost:56992, https://localhost:56993
[14:33:38 INF] Listening for event pipe events for backend_0fee6b73-d on process id 21159
[14:33:38 INF] Listening for event pipe events for frontend_9c4a283e-7 on process id 21160

We’ll notice that there is a new directory named .tye. In this directory, we have two files docker_store and process_store. The temporary directory holds values for our newly created Docker containers and processes.

The docker_store file has several container names.

{"container":"redis_9cc52767-0"}
{"container":"redis-cli_2d0d3fe6-d"}
{"container":"frontend-proxy_ab657c4b-d"}
{"container":"backend-proxy_88437d3b-1"}

The process_store has process identifiers.

{"pid":"18000"}
{"pid":"17999"}

Tye deletes this directory when the Tye Host exits.

While we have Tye running, we can see the dashboard. The dashboard lists all of our containers and projects. It also allows us to view logs directly from the running containers. Most importantly, the dashboard shows currently bound ports to our applications.

Project Tye Dashboard

We can now interact with these services like we would any other application.

Service Discovery

One of the most significant advantages of running applications with Project Tye is the idea of service discovery. When we register our services, we name them. By explicitly naming our services, we can ask for relevant metadata within the context of our Tye host. Here is an example of asking for the instance of Backend running as part of our infrastructure.

// Get the URI of the 'backend' service and create an HttpClient.
var uri = Configuration.GetServiceUri("backend");
var httpClient = new HttpClient()
{
    BaseAddress = uri
};

We get access to service discovery by adding Microsoft.Tye.Extensions.Configuration package to our projects, which ultimately is a wrapper around the default configuration mechanisms of .NET Core. The Tye documentation highly recommends this approach of service discovery and to avoid any hardcoding of values. URLs and ports may change between different runs of Tye.

Debugging Our Applications

Debugging is a critical part of any developer’s toolbox. While Tye is running our infrastructure, it also exposes debugging hooks to be able to attach to our .NET projects.

To run our infrastructure in debug, we need to pass a specific flag to Tye.

> tye run --debug *

The combination of --debug and * pauses execution of our hosted projects until a debugger is attached. The pause allows us to debug from the start of our application’s lifetime. We could also attach a debugger at any point after the start of our Tye host.

We can run many of the Project Tye commands in JetBrains Rider using the Run Anything feature.

JetBrains Rider run anything UI

We can see the results of our command execution in the Run window, and right next to it is our Services window.

JetBrains Rider Services and Run

Once our infrastructure is up and running, we can use Rider to identify our .NET Core processes quickly and attach the debugger seamlessly.

JetBrains Rider attach

As you can see, we hit the breakpoint in our Frontend project.

JetBrains Rider attached debugger

We can also use the Services windows in Rider to view our Docker logs and navigate the folder structure inside of our container instances.

JetBrains Rider services window

Additionally, we can use the Services window to ensure that Tye cleans up our containers properly. We’ll see an approach in the next section that we should avoid.

The approach of using Tye to wire up our application topology is compelling, and we don’t lose any critical debugging features by adopting Tye into our development stack.

Noteworthy

During this post, there were a few discoveries worth noting for those looking at Tye as a solution for local development.

Don’t Run Tye With LaunchSettings

When starting, I attempted to run Project Tye from the launchSettings.json of one of the .NET projects.

    "tye": {
      "executablePath": "tye",
      "commandLineArgs": "run",
      "commandName": "Executable",
      "workingDirectory": "../../../../",
    }

The launch settings file is not the right solution for two reasons:

  1. We need to put the configuration in one project, but Tye works at the solution level.
  2. Launch settings do not exit gracefully, leaving containers and artifacts behind.

So, if you are thinking about doing this, don’t. Use the command line and Rider’s run anything feature.

Tye Init

If we already have an existing solution, running tye init in the solution directory will give us a starting YAML file. Opting into a Tye powered infrastructure is effortless.

Many Ways To View Logs

Project Tye makes no assumptions about our familiarity with Docker or Kubernetes. The dashboard allows us to view logs directly from our browser. That said, the containers are running inside of Docker, and we can access them with any compatible tool.

Ports Change

Project Tye does not check to see if there is already an instance of our configuration running. That means we could inadvertently start multiple clusters, with each one registering different ports. In these scenarios, it can get a little confusing.

Conclusion

Project Tye is a compelling tool for local development, and it is a natural entry point for thinking about Kubernetes in the cloud. While it is still in alpha, it shows a lot of promise for helping the .NET ecosystem. Tye is likely a project many .NET developers will be getting familiar with, and most likely pushed enthusiastically by Microsoft as a premier solution for developing microservice and distributed applications.