DEV Community

loading...
Cover image for Simple approach to run and debug multiple .NET Dapr projects (w/o Docker)

Simple approach to run and debug multiple .NET Dapr projects (w/o Docker)

kaiwalter profile image Kai Walter Updated on ・6 min read

TL;DR

One way of executing multiple Dapr applications locally is to use Docker compose or to use project Tye with its Dapr specific features. Both approaches base on packaging the apps into containers and start these containers. Additionally a network boundary is created to avoid or tune down "port dance" mentioned below. Debugging then is achieved by attaching to the container executing the Dapr application.

To avoid container build times and attaching the debugger - given that the setup is homogenous and/or simple enough - in this post I want to show a simple approach to start Dapr sidecars and (selectively) application instances with PowerShell (Core) background jobs.

DISCLAIMER: the approach shown here is not intended for production use; it is intended to help with local debugging and testing of .NET based Dapr applications

startDaprAsJob script

This repository contains a simple 2 app sample - app1 invoking app2 - to show the basic workings of this approach. Script ./startDaprAsJob.ps1 controls which projects / applications are to be started. Again for simplicity a static definition is used which could be replaced with some generic function to detect all relevant projects and settings.

pre-requisite: Dapr client is initialized with dapr init --slim so that the script can control lifecycle of placement service, which is required for actor scenarios

challenge - the "port dance"

When running multiple Dapr applications / services locally in parallel the challenge is, that each needs a dedicated set of ports for the application itself, for Dapr HTTP, for Dapr gRPC and also for Dapr metrics. The script solves this by generating port numbers dynamically and assigning these to the particular applications through modifying the launchSettings.json.

Now this adjustment makes the script somewhat specific to .NET and Visual Studio. But this approach can be used for adapting the script to any other environment.

configuration

$configProjects = @(
    @{
        appId       = "app1"
        folder      = "./app1"
        projectFile = "app1.csproj"
        settingName = "app1"
        debug       = $false
    }
    @{
        appId       = "app2"
        folder      = "./app2"
        projectFile = "app2.csproj"
        settingName = "app2"
        debug       = $true
    }
)
setting purpose
appId needs to be Dapr id commonly used to address service
folder relative folder of .NET project (containing components folder and tracing.yaml)
projectFile name of service project file
settingName name of launch setting (Launch:Project; not IIS Express) which is modified for startup
debug $true : no background instance is started; waiting for (debugging) instance started from VS
$false : background instance of service is started with dotnet run

Setting debug determines which projects are directly executed as background jobs (when $false) or which ones are started by yourself from a development environment (when $true). For those apps Dapr sidecar(s) wait(s) until application(s) become available.

usage

With the settings above app2 can be opened e.g. in VS, marked as startup project, desired breakpoints (for example in line 21 of app2 HealthController) can be set and be started.

Now the script is used to start the other Dapr applications / services / ...

❯ .\startDaprAsJob.ps1

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
41     placement       BackgroundJob   Running       True            localhost            …
--------------------------------------------------------------------------------
updated C:\Users\kai\src\dapr-experimental\app2app\app1\Properties\launchSettings.json
start Daprd in background app1 5000
43     app1-daprd      BackgroundJob   Running       True            localhost            …
45     app1-app        BackgroundJob   Running       True            localhost            …
--------------------------------------------------------------------------------
updated C:\Users\kai\src\dapr-experimental\app2app\app2\Properties\launchSettings.json
start Daprd in background app2 5010
47     app2-daprd      BackgroundJob   Running       True            localhost            …
expecting C:\Users\kai\src\dapr-experimental\app2app\app2\app2.csproj to be started from development environment
--------------------------------------------------------------------------------
t: test call health endpoint
s: job status
e: check all logs for errors
q: stop jobs and quit
0: show log of placement
1: show log of app1-daprd
2: show log of app1-app
3: show log of app2-daprd
Enter option:

Checking job status shows that app1 is running with its sidecar and for app2 only the sidecar is running.

Enter option: s


Name       State
----       -----
placement  Running
app1-daprd Running
app1-app   Running
app2-daprd Running

Invoking the test should bring up app2 in debugger:

Enter option: t
--------------------------------------------------------------------------------
App1 health

status : OK

App2 health (through App1)
...trip to debugger...
status : OK - App2

Breakpoint hit:

Alt Text

That's it.

other options

e does a basic check for errors on all logs produced to see whether Dapr sidecars and components started or whether errors occurred in the applications.

As a convenience numbers can be used to retrieve logs of the corresponding job.

1 would display the Dapr log of app1:

time="2020-08-21T13:54:58.6704251Z" level=info msg="starting Dapr Runtime -- version 0.9.0 -- commit 6babe85-dirty" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6704251Z" level=info msg="log level set to: debug" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.671422Z" level=info msg="metrics server started on :9091/" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.metrics type=log ver=0.9.0
time="2020-08-21T13:54:58.6774253Z" level=info msg="standalone mode configured" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6774253Z" level=info msg="app id: app1" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6774253Z" level=info msg="mTLS is disabled. Skipping certificate request and tls validation" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6794256Z" level=info msg="found component statestore (state.redis)" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6794256Z" level=info msg="found component messagebus (pubsub.redis)" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6794256Z" level=info msg="application protocol: http. waiting on port 5000.  This will block until the app is listening on that port." app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:55:00.9266468Z" level=info msg="application discovered on port 5000" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:55:00.9925923Z" level=info msg="application configuration loaded" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:55:01.0135957Z" level=info msg="local service entry announced: app1 -> 10.242.0.4:63838" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.contrib type=log ver=0.9.0
...

2 would display app2 console log:

info: Microsoft.Hosting.Lifetime[0]
      Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\Users\kai\src\dapr-experimental\app2app\app1

Option q stops and removes all related jobs. In case the script breaks, jobs can be cleaned up by starting the script again with immediately ending it with q.

supported OS

The only Windows OS specific part in this script should be the invocation of the placement service:

    C:\Dapr\placement.exe --port $port

How to run Dapr self hosted without containers indicates what needs to be adapted for Linux / MacOS.

Conclusion

Whether it is easier and more stable to have Docker containers (see above mentioned Docker compose and project Tye) or PowerShell jobs floating around on your machine is a matter of taste. I personally like this approach as it allows me to spin up a multi application scenario on my local machine quickly.

Discussion (6)

pic
Editor guide
Collapse
kep11 profile image
KEP11

Hi,
I'm interested in launching a few projects. But I did not find any scripts in the repo.
I have to run about 8 projects and every time I run one by one, it is very time-consuming.
Should you show the content of the ps1 script?
How I can achieve my goal?

Thank you in advance.

Collapse
kaiwalter profile image
Kai Walter Author • Edited

Hello @kep11
I restored the repo/script. Please see whether you can make sense out of it.
Kai

Collapse
kaiwalter profile image
Kai Walter Author

I am sorry @kep11 for breaking the link. I will update the post in the next hours.

Collapse
razorscream profile image
RazorscreaM • Edited

Could you include here as well the way to work with State Stores and Pub/Sub with this approach? as i was running into a couple of issues this way

Collapse
kaiwalter profile image
Kai Walter Author

Sure. Gimme a few days as I'm still in vacation mode. What is the background of your request? If you operate state store with Redis in a docker container my sample can be extended easily - it is still plain vanilla Dapr. Or are you especially interested for the "w/o docker" part where some state and some pub/sub components live somewhere outside docker?

Collapse
razorscream profile image
RazorscreaM

Hi, already fixed the issues i was running into, however i would gladly read some post regarding development using dapr + docker (loacally), like having an image of a an api/mvc app (aka app1) that was build with DAPR, starting this image inside a docker container locally and then creating a fresh api (akk app2). And calling app1 from app2 using DAPR. (would be best in debug mode), and maybe some interaction between these apps with the pub/sub (redis or any other doesn't matter). If you would have some nice posts you can advice or do one yourself that would be great.