DEV Community

Tommaso Stocchi
Tommaso Stocchi

Posted on

.NET Aspire Multilanguage

In this article I want to explore how we can leverage a tool such as .NET Aspire to improve the way we build and deploy distributed applications, even when we work with languages and frameworks other than .NET.

What is .NET Aspire

.NET Aspire is an opinionated, cloud ready stack designed to improve the experience of building observable, production ready, distributed applications. It is delivered through a collection of NuGet packages that handle specific cloud-native concerns.

.NET Aspire is designed to help you with:

  • Orchestration: .NET Aspire provides features for running and connecting multi-project applications and their dependencies for local development environments.
  • Integrations: .NET Aspire integrations are NuGet packages for commonly used services, such as Redis or Postgres, with standardized interfaces ensuring they connect consistently and seamlessly with your app.
  • Tooling: .NET Aspire comes with project templates and tooling experiences for Visual Studio, Visual Studio Code, and the dotnet CLI to help you create and interact with .NET Aspire projects.

The core of .NET Aspire can be found in the AppHost project that's created when you start working with Aspire from the .NET template. The AppHost project defines the components of our distributed application, from services - such as backend or fronted - to resources - such as databases or caches.
This project can generate a manifest describing from an infrastructure perspective our application. It can therefor be interpreted by the Azure Developer CLI to deploy the application to Azure, without the need to write any Infrastructure as Code.

Work with different languages

Of course when we work with distributed applications and microservices we might incur in a scenario in which different teams like to write code in different languages. And of course .NET Aspire belongs to the .NET ecosystem. Nonetheless, it is design to be able to integrate with different languages and frameworks.

Distributed Calculator

Let's consider a simple example. We have a distributed calculator that is composed of different services: a frontend service and a backend for each operation.

  • fronted: react
  • subtraction: dotnet
  • addition: go
  • multiplication: python
  • division: nodejs

We also have a redis cache to store the state of the calculator.

Architecture

In this scenario we can use .NET Aspire to build the distributed calculator, leveraging all the tool and integrations that come with it. Aspire offers native support for certain languages, but we can also extend it to support other languages using containers.

At the time of writing, .NET Aspire supports the following languages:

Here's how we can configure all the backend services in the AppHost project:

Golang

Golang isn't natively supported, so we will add it as a container. Note that we can decide to use different images for the scenarios in which we are running the AppHost for publishing the manifest or for local development.

// Configure Adder in Go
var add = (builder.ExecutionContext.IsPublishMode
    ? builder.AddContainer("addapp", "acrt6xtihl2b3uxe.azurecr.io/addapp")
    : builder.AddContainer("addapp", "addapp"))
        .WithHttpEndpoint(targetPort: 6000, env: "APP_PORT", name: "http")
        .WithOtlpExporter()
        .WithEnvironment("OTEL_SERVICE_NAME", "addapp")
        .PublishAsContainer();
var addEnpoint = add.GetEndpoint("http");
Enter fullscreen mode Exit fullscreen mode

Python

Python is natively supported, so we can use the AddPythonProject method to configure the multiplier service. Please follow this tutorial to correctly configure the Python project.

// Configure Multiplier in Python
var multiply = builder.AddPythonProject("multiplyapp", "../../python-multiplier", "app.py")
    .WithHttpEndpoint(targetPort: 5001, env: "APP_PORT", name: "http")
    .WithEnvironment("OTEL_SERVICE_NAME", "multiplyapp")
    .PublishAsDockerFile();
Enter fullscreen mode Exit fullscreen mode

NodeJS

NodeJS is natively supported, so we can use the AddNodeApp method to configure the divider service.

// Configure Divider in NodeJS
var divide = builder.AddNodeApp(name: "divideapp", scriptPath: "app.js", workingDirectory: "../../node-divider")
    .WithHttpEndpoint(targetPort: 4000, env: "APP_PORT", name: "http")
    .WithEnvironment("OTEL_SERVICE_NAME", "divideapp")
    .PublishAsDockerFile();
Enter fullscreen mode Exit fullscreen mode

.NET

No surprise here, .NET Aspire natively supports .NET, so we can use the AddProject method to configure the subtractor service.

// Configure Subtractor in .NET
var subtract = builder.AddProject<Projects.dotnet_subtractor>("subtractapp")
    .WithReference(insights)
    .WithEnvironment("OTEL_SERVICE_NAME", "subtractapp");
Enter fullscreen mode Exit fullscreen mode

Redis

The redis cache can be easily configured using the AddRedis method, or in this scenario using Dapr via the AddDaprStateStore method.

// Configure Dapr State Store
var stateStore = builder.AddDaprStateStore("statestore");
Enter fullscreen mode Exit fullscreen mode

Dapr is not the focus of this article, but it is worth mentioning that .NET Aspire can be used in conjunction with Dapr to build distributed applications. For further information regarding Dapr, please reference the official documentation.

React

Lastly, we can configure the frontend service using the AddNpmApp method.

// Configure Frontend in React
builder.AddNpmApp(name: "calculator-front-end", workingDirectory: "../../react-calculator")
    .WithDaprSidecar(new DaprSidecarOptions
    {
        AppPort = 3000,
        AppProtocol = "http",
        DaprHttpPort = 3500
    })
    .WithEnvironment("DAPR_HTTP_PORT", "3500")
    .WithReference(addEnpoint)
    .WithReference(multiply)
    .WithReference(divide)
    .WithReference(subtract)
    .WithReference(stateStore)
    .WithReference(insights)
    .WithHttpEndpoint(targetPort: 3000, env: "PORT")
    .WithExternalHttpEndpoints()
    .WithEnvironment("OTEL_SERVICE_NAME", "calculator-front-end")
    .PublishAsDockerFile();
Enter fullscreen mode Exit fullscreen mode

Since we are referencing all the previously configured services, we can easily connect them to the frontend service. When we need to invoke the adder from the frontend, we can easily do so by using the environment variable that has been injected by Aspire:

app.post('/calculate/add', async (req, res) => {
  try {
      const serviceUrl = process.env.services__addapp__http__0;

      const appResponse = await axios.post(`${serviceUrl}/add`, req.body);

      // Return expected string result to client
      return res.send(`${appResponse.data}`); 
  } catch (err) {
      console.log(err);
  }
});
Enter fullscreen mode Exit fullscreen mode

Further considerations

The entire application can be deployed to Azure using the Azure Developer CLI. The CLI will read the manifest generated by the AppHost project and deploy the application to Azure, creating all the necessary resources. To learn how to integrate Aspire with the Azure Developer CLI, please reference the official tutorial.

All the code for the distributed calculator can be found in the Aspire Multilanguage repository.

Top comments (0)