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.
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.
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.
We can see the results of our command execution in the Run window, and right next to it is our Services window.
Once our infrastructure is up and running, we can use Rider to identify our .NET Core processes quickly and attach the debugger seamlessly.
As you can see, we hit the breakpoint in our Frontend project.
We can also use the Services windows in Rider to view our Docker logs and navigate the folder structure inside of our container instances.
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:
- We need to put the configuration in one project, but Tye works at the solution level.
- 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.