Examples of sync and async microservice messaging with Dapr and JavaScript.
The demo in this repo is in the context of a student registration service that needs to communicate with a payment service. The registration service will receive a payload from a client that contains the student name, email, class name, and the cost of the class.
Ensure you have the following installed before proceeding:
- Docker Engine
- Dapr CLI
- NodeJS
- Optional: VS Code with the REST client extension.
Run the following command in a terminal in the root of the repo to install the dependencies:
For macOS & Linux
npm run install:mac
For Windows
npm run install:win
Wait until all dependencies are installed.
Dapr services can communicate with each other using synchronous messaging. This is known as service-to-service invocation using a request/response model over HTTP or GRPC. Service A uses the Dapr API to post a request and uses the app-id to identify the receiving service. First this request arrives at the Dapr sidecar of service A. The sidecar will perform a name resolution to find out where the receiving Dapr sidecar is for service B. The service A sidecar then forwards the request to the sidecar of service B which in turn delivers the request to service B itself. The response from service B flows through the sidecars back to service A.
The following diagram shows the flow of a synchronous request/response between two Dapr services, a Registration service and a Payment service:
-
Open a terminal and navigate to the
sync
folder. -
Run the apps with Dapr multi-app run (macOS or Linux only):
dapr run -f .
Or run the apps separately with the Dapr CLI:
Navigate to the
payment
folder and run:dapr run --app-port 5502 --app-id payment --app-protocol http --dapr-http-port 3502 -- npm start
Open a new terminal and navigate to the
registration
folder and run:dapr run --app-port 5501 --app-id registration --app-protocol http --dapr-http-port 3501 -- npm start
-
Make a request to the
register
endpoint of the registration service.If you're using VS Code with the REST client you can use the local-sync-test.http file.
curl --request POST \ --url http://localhost:5501/register \ --header 'content-type: application/json' \ --data '{"name": "Stu Dent","email": "stu@dent.com","class": "digital media","cost": 500}'
Expected response:
Registration received for Stu Dent
The application log of the payment service should show the following:
== APP == Payment received: { email: 'stu@dent.com', cost: 500 }
You've now successfully made a request to the registration service and that service made a synchronous call to the payment service.
The sync/resources
folder contains a resiliency.yaml
file that contains resiliency policies that the registration service will use when making requests to the payment service.
-
Inspect the
resiliency.yaml
file and check the scope, policies, and target elements. -
Open a terminal and navigate to the
registration
folder and run:dapr run --app-port 5501 --app-id registration --app-protocol http --dapr-http-port 3501 --resources-path ../resources -- npm start
Note that the
--resources-path
argument is used to specify the location of theresiliency.yaml
file. -
Ensure the
payment
service is no longer running. If it is running, stop the service to simulate an error with the service in order to trigger the resiliency policy. -
Make a request to the
register
endpoint of the registration service.If you're using VS Code with the REST client you can use the local-sync-test.http file.
curl --request POST \ --url http://localhost:5501/register \ --header 'content-type: application/json' \ --data '{"name": "Stu Dent","email": "stu@dent.com","class": "digital media","cost": 500}'
Expected response:
INFO[XXXX] Error processing operation endpoint[payment, payment:pay]. Retrying in 5s…
-
Before the request times out, open a new terminal and navigate to the
payment
folder and start the payment service:dapr run --app-port 5502 --app-id payment --app-protocol http --dapr-http-port 3502 -- npm start
Expected application log for the registration service:
INFO[XXX] Recovered processing operation endpoint[payment, payment:pay]
Now you know how to use resiliency policies to handle transient errors when doing synchronous messaging with Dapr.
Dapr services can communicate asynchronously with each other using a publish/subscribe model. This involves an intermediary service known as a message broker. Service A uses the Dapr API to publish an message. The message arrives at the sidecar of service A which publishes the message to a topic on the message broker. The sidecar of service B is subscribed to that topic, receives the message and forwards it to service B. The following diagram shows the flow of an asynchronous publish/subscribe between two Dapr services, a Register service and a Payment service:
-
Open a terminal and navigate to the
async
folder. -
Run the apps with Dapr multi-app run (macOS or Linux only):
dapr run -f .
Or run the apps separately with the Dapr CLI:
Navigate to the
payment
folder and run:dapr run --app-port 5512 --app-id payment --app-protocol http --dapr-http-port 3512 --resources-path ../resources/ -- npm start
Open a new terminal and navigate to the
registration
folder and run:dapr run --app-port 5511 --app-id registration --app-protocol http --dapr-http-port 3511 --resources-path ../resources/ -- npm start
-
Make a request to the
register
endpoint of the registration service.If you're using VS Code with the REST client you can use the local-async-test.http file.
curl --request POST \ --url http://localhost:5511/register \ --header 'content-type: application/json' \ --data '{"name": "Stu Dent","email": "stu@dent.com","class": "digital media","cost": 500}'
Expected response:
Registration received for Stu Dent
The application log of the payment service should show the following:
== APP == Payment received: { email: 'stu@dent.com', cost: 500 }
You've now successfully made a request to the registration service and that service published a message to the
newstudents
topic of the message broker. Since the payment service is subscribed to this topic it received the event and made a log statement to the console. -
Open the Zipkin dashboard at http://localhost:9411/zipkin/ to inspect the traces.
-
In the menu, go to dependencies and click the search button (you might need to update the start time to before you started to make the requests). You should now see a visual representation of the communication between the services.
-
To analyze this further you can:
- Click on the
registration
service and click the Traces button.
- Click Run query. If there are no results you probably need to update the lookback range under the settings cog.
- The page should now lists the traces of the registration service.
- Find the trace that has the root:
registration: /v1.0/publish/studentpubsub/newstudents
and click on the SHOW button.
- Now you see a timeline that shows both the
registration
and thepayment
services.
- Click on the
-
The
async/resources
folder contains asubscriptions._yaml
file that contains the subscription configuration for thepayment
service. Inspect this file. -
Remove the underscore from the
subscriptions._yaml
file extension so it will be loaded whendapr run
is called. -
Open the
index.js
file of the payment service and comment out theapp.get('/dapr/subscribe'...
function since thesubscription.yaml
file is now used to configure the subscription. -
Run the apps with Dapr multi-app run (macOS or Linux only):
dapr run -f .
Or run the apps separately with the Dapr CLI:
Navigate to the
payment
folder and run:dapr run --app-port 5512 --app-id payment --app-protocol http --dapr-http-port 3512 --resources-path ../resources/ -- npm start
Open a new terminal and navigate to the
registration
folder and run:dapr run --app-port 5511 --app-id registration --app-protocol http --dapr-http-port 3511 --resources-path ../resources/ -- npm start
-
Make a request to the
register
endpoint of the registration service.If you're using VS Code with the REST client you can use the local-async-test.http file.
curl --request POST \ --url http://localhost:5511/register \ --header 'content-type: application/json' \ --data '{"name": "Stu Dent","email": "stu@dent.com","class": "digital media","cost": 500}'
Expected response:
Registration received for Stu Dent
The application log of the payment service should show the following:
== APP == Payment received: { email: 'stu@dent.com', cost: 500 }
Dead letter topics are used for messages that cannot be delivered to the subscriber or the subscriber can't process them. Dapr can be configured to send these messages to a dead letter topic and be handled by another endpoint.
-
The
async/resources
folder contains adeadletter._yaml
file that contains the dead letter configuration for thepayment
service. Inspect this file. -
Remove the underscore from the
deadletter._yaml
file extension so it will be loaded whendapr run
is called. -
Run the apps with Dapr multi-app run (macOS or Linux only):
dapr run -f .
Or run the apps separately with the Dapr CLI:
Navigate to the
payment
folder and run:dapr run --app-port 5512 --app-id payment --app-protocol http --dapr-http-port 3512 --resources-path ../resources/ -- npm start
Open a new terminal and navigate to the
registration
folder and run:dapr run --app-port 5511 --app-id registration --app-protocol http --dapr-http-port 3511 --resources-path ../resources/ -- npm start
-
Make a request to the
register
endpoint of the registration service and make theemail
field empty to trigger the dead letter handling.If you're using VS Code with the REST client you can use the local-async-test.http file.
curl --request POST \ --url http://localhost:5511/register \ --header 'content-type: application/json' \ --data '{"name": "Stu Dent","email": "","class": "digital media","cost": 500}'
Expected response:
Registration received for Stu Dent
The application log of the payment service should show the following:
== APP == Invalid message received: {"email":"","cost":500}