Introducción a Dapr .NET SDK (6/N): Debugging (VSCode y VS) + Sidekick


Segun hemos podido ir avanzando a lo largo de todos estos posts, ya casi que podemos decir que conocemos y sabemos trabajar con Dapr. A pesar de ello nos queda bastante recorrido, y, por eso, antes de continuar, es el momento de tener más control y conseguir mayor facilidad durante la construcción y depuración, en nuestro día a día. En este post vamos a ver los mecanismos para depurar tanto desde Visual Studio Code como desde Visual Studio y, lo más interesante, tal y como estamos acostrumbrados, pulsando F5.

¡Si no puedes esperar a leer el articulo completo o ya conoces el camino habitual para depurar con Dapr, ve al último apartado para conocer la incorporación de Sidekick, seguro que te sorprende!

Vamos a centrarnos en el primer ejemplo que vimos (Service-to-service invocation), para depurarlo e incluir break points.

A continuación, el listado completo de posts relacionados sobre los que iré trabajando y publicando:

  1. Introducción a Dapr .NET SDK (1/N) y «Service-to-service invocation»
  2. Introducción a Dapr .NET SDK (2/N): State management
  3. Introducción a Dapr .NET SDK (3/N): Secrets
  4. Introducción a Dapr .NET SDK (4/N): Pub/Sub
  5. Introducción a Dapr .NET SDK (5/N): Bindings y Triggers
  6. [ -> ] Introducción a Dapr .NET SDK (6/N): Debugging (VSCode y VS) + Sidekick
  7. Introducción a Dapr .NET SDK (7/N): Docker Compose and HTTPS
  8. Introducción a Dapr .NET SDK (8/N): Kubernetes mode
  9. Introducción a Dapr .NET SDK (9/N): Azure Container Apps mode

Visual Studio Code

En primer lugar instalamos la extensión de Dapr para VS Code.

Una vez instalada continuamos con los siguientes pasos:

1. Crear un «launch.json» si no existe. Si seguimos los pasos que nos indica VSCode, tenemos algo como lo siguiente:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Launch (proxy)",
      "type": "coreclr",
      "request": "launch",
      "preLaunchTask": "build",
      "program": "${workspaceFolder}/WeatherForecastProxyService/bin/Debug/net5.0/WeatherForecastProxyService.dll",
      "args": [],
      "cwd": "${workspaceFolder}/WeatherForecastProxyService",
      "stopAtEntry": false,
      "serverReadyAction": {
        "action": "openExternally",
        "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
      },
      "env": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "sourceFileMap": {
        "/Views": "${workspaceFolder}/Views"
      }
    }
  ]
}

2. Duplicamos esta sección de configuración y le damos el nombre «Launch (backend)» eliminando en la nueva los prefijos/sufijos «Proxy». De esta manera tendremos dos configuraciones, cada una de ellas para la depuración de cada Web Api.

3. Añadimos esta nueva sección, que nos va a permitir lanzar la depuración para ambos proyectos a la vez:

"compounds": [
        {
            "name": "Both Console & Web API",
            "configurations": [
                ".NET Core Launch (console)",
                ".NET Core Launch (web)"
            ]
        }

2. Pulsar Ctrl + Shift + P y ejecutar la acción «Dapr: Scaffold Dapr Tasks«

3. Seleccionar la configuración previa «Launch (proxy)» e indicar los valores «proxy» y «6001»para los parámetros solicitados (Dapr ID y Puerto respectivamente).

4. Tras este proceso tendremos una nueva configuración (en el «launch.json» y dos nuevas tasks en el «tasks.json»).

5. Duplicamos estas tareas hasta obtener el siguiente resultado (en el «tasks.json«):

// ...
{
    "appId": "proxy",            
    "label": "daprd-debug-proxy",
    "type": "daprd",
    "dependsOn": ["build-proxy"],
    "appPort": 6001,
    "httpPort": 3501,
    "grpcPort": 50001,
    "metricsPort": 9091,
    "appSsl": true
},
{
    "appId": "backend",            
    "label": "daprd-debug-backend",
    "type": "daprd",
    "dependsOn": "build-backend",
    "appPort": 5001,
    "httpPort": 3500,
    "grpcPort": 50002,
    "metricsPort": 9092, 
    "appSsl": true,            
},
// ...

Nota: Recordemos que el appId de la aplicación llamada debe ser «backend«, dado que es invocado con este nombre desde el código de la aplicacion llamadora (proxy).

6. Nuevamente, duplicamos la entrada creada en el «launch.json» hasta obtener este resultado, realizando también algunos ajustes y obteniendo estas dos configuraciones.

{
      "name": "Launch (proxy) with Dapr",
      "type": "coreclr",
      "request": "launch",
      "preLaunchTask": "daprd-debug-proxy",
      "program": "${workspaceFolder}/WeatherForecastProxyService/bin/Debug/net5.0/WeatherForecastProxyService.dll",
      "args": [],
      "cwd": "${workspaceFolder}/WeatherForecastProxyService",
      "stopAtEntry": false,
      "serverReadyAction": {
        "action": "openExternally",
        "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
      },
      "env": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "sourceFileMap": {
        "/Views": "${workspaceFolder}/Views"
      },
      "postDebugTask": "daprd-down-proxy"
    },
    {
      "name": "Launch (backend) with Dapr",
      "type": "coreclr",
      "request": "launch",
      "preLaunchTask": "daprd-debug-backend",
      "program": "${workspaceFolder}/WeatherForecastService/bin/Debug/net5.0/WeatherForecastService.dll",
      "args": [],
      "cwd": "${workspaceFolder}/WeatherForecastService",
      "stopAtEntry": false,
      "serverReadyAction": {
        "action": "openExternally",
        "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
      },
      "env": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "sourceFileMap": {
        "/Views": "${workspaceFolder}/Views"
      },
      "postDebugTask": "daprd-down-backend"
    }

7. Añadimos una nueva entrada en el array «compounds». De esta manera, y, como vimos anteriormente, podemos lanzar ambos proyectos (web api) al mismo tiempo en modo depuración y bajo la ejecución de Dapr.

{
  "name": "Proxy & Backend (with Dapr)",
  "configurations": [
    "Launch (backend) with Dapr",
    "Launch (proxy) with Dapr"
  ]
}

8. Y seleccionamos esta opción en el desplegable de depuración de Visual Studio Code y pulsar F5.

9. Finalmente, establecemos los breakpoints y navegamos a http://localhost:6001/swagger para lanzar un POST.

Importante: Si tras una o varias ejecuciones observamos errores derivados del bloqueo por uso de algunos de los puertos indicados en el «tasks.json«. Buscamos el proceso «dapr» en el Task Manager y hacemos un «kill» del mismo. Puede ser debido a que alguno de los procesos «dapr» no haya finalizado correctamente durante alguna de sus ejecuciones/depuraciones. Adicionalmetne podemos hacer uso de esa otra instrucción para comprobar cuales son los puertos que estan en uso y así identificar sus PID.

netstat -ano -p tcp

Visual Studio

En el momento de escribir este post no existe una depuración específica ni extensión alguna para Visual Studio (realmente veremos una alternaiva, en el siguiene apartado), por lo que podemos hacerlo siguiendo el camino habitual de adjuntar (o «atachar») los procesos. Para ello:

1. Ejecutar las siguientes instrucciones de línea de comandos en dos terminales diferentes o en dos tabs diferentes para el caso de Windows Terminal. ¡Espero que ya estés actualizado y trabajando con ella!

dapr run --app-id backend --app-port 5001 --app-ssl -- dotnet run --urls=https://localhost:5001/ -p ./WeatherForecastService/WeatherForecastService.csproj 
dapr run --app-id proxy --app-port 6001 --app-ssl -- dotnet run --urls=https://localhost:6001/  -p ./WeatherForecastProxyService/WeatherForecastProxyService.csproj

Nota: Al trabajar con «https» es necesario indicárselo a dapr a través del parámetro «–app-ssl«. Si además, existe algun conflicto de puertos, recordemos usar los parámetros «–dapr-http-port» y «–dapr-grpc-port».

2. Desde el menú «Debug» de Visual Studio, seleccionar «Attach to Process…» y buscar los procesos de las aplicaciones a depurar («weather»). Marcarlos y pulsar «Attach«.

3. Establecer los Breackpoints y navegar a http://localhost:6001/swagger para lanzar un POST.

Visual Studio usando Dapr Sidekick for .NET

Dapr sin y con Sidekick

En esta imagen, en el gráfico de la izquierda vemos la comunicación entre «dapr.exe», nuestra aplicación y otro proceso «daprd.exe» que es realmente el proceso Sidecar. Este es el camino habitual («out of the box») y de ahí, que la depuración con Visual Studio no sea trivial como hemos visto en el apartado anterior. Si bien, Sidekick simplifica este proceso/comunicación tal y como podemos ver en el gráfico de la derecha y, como podemos intuir, tenemos más control sobre el proceso de depuración entre otras carácterísticas.

En concreto, Dapr Sidekick for .NET es un componente que permite agregar Dapr a nuestros proyectos evitando fricciones. Simplifica el desarrollo y las operaciones .NET. Además mejora la experiencia de depuración y proporciona herramientas de desarrollo. No requiere el SDK oficial de Dapr para .NET, es complementario y está diseñado para funcionar junto con él.

Como podemos ver en la imagen anterior nuestra aplicación/proceso se encarga de todo lo necesario para poner en funcionamiento Dapr.

Para incluirlo en nuestro proyecto de ejemplo y comenzar a trabajar con él, y, de esta manera beneficiarnos por tanto, de la mejorada experiencia de depuración, seguiremos los siguientes pasos. Eso si, no sin antes tener en cuenta, que el código de ejemplo se encuentra aquí, en github. Se trata de nuestro mismo proyecto (según pasos anteriores), donde se han realizado algunas modificaciones para usar Sidekick:

1. Añadir el paquete nuget: «Man.Dapr.Sidekick.AspNetCore«.

2. Modificar el método «ConfigureServices» de la clase «Startup.cs» como sigue:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    // Add Dapr Sidekick
    services.AddDaprSidekick(Configuration);
}

3. A continuación, y como nuestra aplicación invocadora (o proxy) llama a la otra aplicación, con el nombre/id «backend«,necesitamos especificar su AppId. De la misma manera, y como estamos usando «https» tenemos que indicarlo. Todas estas especificaciones se pasan a Sidekick a traves del fichero «appsetings.json» tal y como indican a continuación.

Proyecto «backend» appsettings.json:

  "DaprSidekick": {
    "Sidecar": {
      "AppId": "backend",
      "AppSsl": true,
      "AppPort": 5001,
      "DaprHttpPort": 3501,
      "DaprGrpcPort": 50001
    },
    "Placement": {},
    "Sentry": {}
  },
  //...

Proyecto «proxy» appsettings.json

  "DaprSidekick": {
    "Sidecar": {
      "AppId": "proxy",
      "AppSsl": true,
      "AppPort": 6001,
      "DaprHttpPort": 3601,
      "DaprGrpcPort": 60001     
    },
    "Placement": {},
    "Sentry": {}
  },
  // ...

Nota: Para los ficheros de configuración anteriores y, como estamos ejecutando más de un proyecto, tenemos que especificar también las propiedades «AppPort«, «DaprHttpPort» y «DaprGrpcPort«. El resto de secciones «Placement» y «Sentry», así como otras propiedades, las podemos obviar por el momento.

4. Configuramos la solución de Visual Studio para que trabaje con multi-proyecto, guardamos los cambios (OK) y pulsamos a continuación F5.

5. Finalmente establecemos los breakpoints y navegamos a http://localhost:6001/swagger para lanzar un POST.

Como podemos comprobar, este camino para depurar es mucho mas sencillo y además, complementario al SDK de Dapr para .NET, lo que en mi opinión, aporta mucho más valor, es más, Dapr Sidekick for .NET nos ofrece aun más, pero esto ya es objeto de otro post y, por tanto, lo veremos en otra ocasión. Mientras tanto, espero que haya sido de utilidad.

Happy #Dapr coding !!!

Referencias: