27 KiB
type | title | linkTitle | weight | description |
---|---|---|---|---|
docs | Quickstart: Publish and Subscribe | Publish and Subscribe | 70 | Get started with Dapr's Publish and Subscribe building block |
Let's take a look at Dapr's [Publish and Subscribe (Pub/sub) building block]({{< ref pubsub >}}). In this quickstart, you will run a publisher microservice and a subscriber microservice to demonstrate how Dapr enables a Pub/sub pattern.
- Using a publisher service, developers can repeatedly publish messages to a topic.
- A Pub/sub component queues or brokers those messages. Our example below uses Redis, you can use RabbitMQ, Kafka, etc.
- The subscriber to that topic pulls messages from the queue and processes them.

Select your preferred language-specific Dapr SDK before proceeding with the quickstart.
{{< tabs "Python" "JavaScript" ".NET" "Java" "Go" >}}
{{% codetab %}}
Pre-requisites
For this example, you will need:
Step 1: Set up the environment
Clone the sample we've provided.
git clone https://github.com/dapr/quickstarts.git
Step 2: Publish a topic
In a terminal window, navigate to the checkout
directory.
cd pub_sub/python/sdk/checkout
Install the dependencies:
pip3 install -r requirements.txt
Run the checkout
publisher service alongside a Dapr sidecar.
dapr run --app-id checkout --components-path ../../../components/ -- python3 app.py
In the checkout
publisher, we're publishing the orderId message to the Redis instance called order_pub_sub
[(as defined in the pubsub.yaml
component)]({{< ref "#pubsubyaml-component-file" >}}) and topic orders
. As soon as the service starts, it publishes in a loop:
while True:
order = {'orderid': random.randint(1, 1000)}
with DaprClient() as client:
# Publish an event/message using Dapr PubSub
result = client.publish_event(
pubsub_name='order_pub_sub',
topic_name='orders',
data=json.dumps(order),
data_content_type='application/json',
)
Step 3: Subscribe to topics
In a new terminal window, navigate to the order-processor
directory.
cd pub_sub/python/sdk/order-processor
Install the dependencies:
pip3 install -r requirements.txt
Run the order-processor
subscriber service alongside a Dapr sidecar.
dapr run --app-id order-processor --components-path ../../../components/ --app-port 5001 -- python3 app.py
In the order-processor
subscriber, we're subscribing to the Redis instance called order_pub_sub
[(as defined in the pubsub.yaml
component)]({{< ref "#pubsubyaml-component-file" >}}) and topic orders
. This enables your app code to talk to the Redis component instance through the Dapr sidecar.
# Register Dapr pub/sub subscriptions
@app.route('/dapr/subscribe', methods=['GET'])
def subscribe():
subscriptions = [{
'pubsubname': 'order_pub_sub',
'topic': 'orders',
'route': 'orders'
}]
print('Dapr pub/sub is subscribed to: ' + json.dumps(subscriptions))
return jsonify(subscriptions)
# Dapr subscription in /dapr/subscribe sets up this route
@app.route('/orders', methods=['POST'])
def orders_subscriber():
event = from_http(request.headers, request.get_data())
print('Subscriber received : ' + event.data['orderid'], flush=True)
return json.dumps({'success': True}), 200, {
'ContentType': 'application/json'}
app.run(port=5001)
Step 4: View the Pub/sub outputs
Notice, as specified in the code above, the publisher pushes a random number to the Dapr sidecar while the subscriber receives it.
Publisher output:
== APP == INFO:root:Published data: {"orderId": 1}
== APP == INFO:root:Published data: {"orderId": 2}
== APP == INFO:root:Published data: {"orderId": 3}
== APP == INFO:root:Published data: {"orderId": 4}
== APP == INFO:root:Published data: {"orderId": 5}
== APP == INFO:root:Published data: {"orderId": 6}
== APP == INFO:root:Published data: {"orderId": 7}
== APP == INFO:root:Published data: {"orderId": 8}
== APP == INFO:root:Published data: {"orderId": 9}
== APP == INFO:root:Published data: {"orderId": 10}
Subscriber output:
== APP == INFO:root:Subscriber received: {"orderId": 1}
== APP == INFO:root:Subscriber received: {"orderId": 2}
== APP == INFO:root:Subscriber received: {"orderId": 3}
== APP == INFO:root:Subscriber received: {"orderId": 4}
== APP == INFO:root:Subscriber received: {"orderId": 5}
== APP == INFO:root:Subscriber received: {"orderId": 6}
== APP == INFO:root:Subscriber received: {"orderId": 7}
== APP == INFO:root:Subscriber received: {"orderId": 8}
== APP == INFO:root:Subscriber received: {"orderId": 9}
== APP == INFO:root:Subscriber received: {"orderId": 10}
pubsub.yaml
component file
When you run dapr init
, Dapr creates a default Redis pubsub.yaml
and runs a Redis container on your local machine, located:
- On Windows, under
%UserProfile%\.dapr\components\pubsub.yaml
- On Linux/MacOS, under
~/.dapr/components/pubsub.yaml
With the pubsub.yaml
component, you can easily swap out underlying components without application code changes.
The Redis pubsub.yaml
file included for this quickstart contains the following:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: order_pub_sub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
In the YAML file:
metadata/name
is how your application talks to the component.spec/metadata
defines the connection to the instance of the component.scopes
specify which application can use the component.
{{% /codetab %}}
{{% codetab %}}
Pre-requisites
For this example, you will need:
Step 1: Set up the environment
Clone the sample we've set up:
git clone https://github.com/dapr/quickstarts.git
Step 2: Publish a topic
In a terminal window, navigate to the checkout
directory.
cd pub_sub/javascript/sdk/checkout
Install dependencies, which will include the dapr-client
package from the JavaScript SDK:
npm install
Verify you have the following files included in the service directory:
package.json
package-lock.json
Run the checkout
publisher service alongside a Dapr sidecar.
dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 --components-path ../../../components -- npm run start
In the checkout
publisher service, we're publishing the orderId message to the Redis instance called order_pub_sub
[(as defined in the pubsub.yaml
component)]({{< ref "#pubsubyaml-component-file" >}}) and topic orders
. As soon as the service starts, it publishes in a loop:
await client.pubsub.publish(PUBSUB_NAME, PUBSUB_TOPIC, order);
console.log("Published data: " + JSON.stringify(order));
Step 3: Subscribe to topics
In a new terminal window, navigate to the order-processor
directory.
cd pub_sub/javascript/sdk/order-processor
Install dependencies, which will include the dapr-client
package from the JavaScript SDK:
npm install
Verify you have the following files included in the service directory:
package.json
package-lock.json
Run the order-processor
subscriber service alongside a Dapr sidecar.
dapr run --app-port 5001 --app-id order-processing --app-protocol http --dapr-http-port 3501 --components-path ../../../components -- npm run start
In the order-processor
subscriber, we're subscribing to the Redis instance called order_pub_sub
[(as defined in the pubsub.yaml
component)]({{< ref "#pubsubyaml-component-file" >}}) and topic orders
. This enables your app code to talk to the Redis component instance through the Dapr sidecar.
server.pubsub.subscribe("order_pub_sub", "orders", (data) => console.log("Subscriber received: " + JSON.stringify(data)));
Step 4: View the Pub/sub outputs
Notice, as specified in the code above, the publisher pushes a random number to the Dapr sidecar while the subscriber receives it.
Publisher output:
== APP == Published data: {"orderId":612}
== APP == Published data: {"orderId":59}
== APP == Published data: {"orderId":75}
== APP == Published data: {"orderId":257}
== APP == Published data: {"orderId":606}
== APP == Published data: {"orderId":568}
== APP == Published data: {"orderId":581}
== APP == Published data: {"orderId":977}
== APP == Published data: {"orderId":92}
== APP == Published data: {"orderId":650}
== APP == Published data: {"orderId":225}
Subscriber output:
== APP == Subscriber received: {"orderId":612}
== APP == Subscriber received: {"orderId":59}
== APP == Subscriber received: {"orderId":75}
== APP == Subscriber received: {"orderId":257}
== APP == Subscriber received: {"orderId":606}
== APP == Subscriber received: {"orderId":568}
== APP == Subscriber received: {"orderId":581}
== APP == Subscriber received: {"orderId":977}
== APP == Subscriber received: {"orderId":92}
== APP == Subscriber received: {"orderId":650}
== APP == Subscriber received: {"orderId":225}
pubsub.yaml
component file
When you run dapr init
, Dapr creates a default Redis pubsub.yaml
and runs a Redis container on your local machine, located:
- On Windows, under
%UserProfile%\.dapr\components\pubsub.yaml
- On Linux/MacOS, under
~/.dapr/components/pubsub.yaml
With the pubsub.yaml
component, you can easily swap out underlying components without application code changes.
The Redis pubsub.yaml
file included for this quickstart contains the following:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: order_pub_sub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
In the YAML file:
metadata/name
is how your application talks to the component.spec/metadata
defines the connection to the instance of the component.scopes
specify which application can use the component.
{{% /codetab %}}
{{% codetab %}}
Pre-requisites
For this example, you will need:
Step 1: Set up the environment
Clone the sample we've set up:
git clone https://github.com/dapr/quickstarts.git
Step 2: Publish a topic
In a terminal window, navigate to the checkout
directory.
cd pub_sub/csharp/sdk/checkout
Recall NuGet packages:
dotnet restore
dotnet build
Run the checkout
publisher service alongside a Dapr sidecar.
dapr run --app-id checkout --components-path ../../components -- dotnet run
In the checkout
publisher, we're publishing the orderId message to the Redis instance called order_pub_sub
[(as defined in the pubsub.yaml
component)]({{< ref "#pubsubyaml-component-file" >}}) and topic orders
. As soon as the service starts, it publishes in a loop:
while(true) {
Random random = new Random();
var order = new Order(random.Next(1,1000));
using var client = new DaprClientBuilder().Build();
// Publish an event/message using Dapr PubSub
await client.PublishEventAsync("order_pub_sub", "orders", order);
Console.WriteLine("Published data: " + order);
await Task.Delay(TimeSpan.FromSeconds(1));
}
public record Order([property: JsonPropertyName("orderId")] int OrderId);
Step 3: Subscribe to topics
In a new terminal window, navigate to the order-processor
directory.
cd pub_sub/csharp/sdk/order-processor
Recall NuGet packages:
dotnet restore
dotnet build
Run the order-processor
subscriber service alongside a Dapr sidecar.
dapr run --app-id order-processor --components-path ../../components --app-port 5001 -- dotnet run
In the order-processor
subscriber, we're subscribing to the Redis instance called order_pub_sub
[(as defined in the pubsub.yaml
component)]({{< ref "#pubsubyaml-component-file" >}}) and topic orders
. This enables your app code to talk to the Redis component instance through the Dapr sidecar.
// Dapr subscription in [Topic] routes orders topic to this route
app.MapPost("/orders", [Topic("order_pub_sub", "orders")] (Order order) => {
Console.WriteLine("Subscriber received : " + order);
return Results.Ok(order);
});
public record Order([property: JsonPropertyName("orderId")] int OrderId);
Step 4: View the Pub/sub outputs
Notice, as specified in the code above, the publisher pushes a random number to the Dapr sidecar while the subscriber receives it.
Publisher output:
== APP == Published data: Order { OrderId = 381 }
== APP == Published data: Order { OrderId = 917 }
== APP == Published data: Order { OrderId = 292 }
== APP == Published data: Order { OrderId = 722 }
== APP == Published data: Order { OrderId = 262 }
== APP == Published data: Order { OrderId = 507 }
== APP == Published data: Order { OrderId = 73 }
== APP == Published data: Order { OrderId = 21 }
== APP == Published data: Order { OrderId = 983 }
== APP == Published data: Order { OrderId = 886 }
Subscriber output:
== APP == Subscriber received: Order { OrderId = 381 }
== APP == Subscriber received: Order { OrderId = 917 }
== APP == Subscriber received: Order { OrderId = 292 }
== APP == Subscriber received: Order { OrderId = 722 }
== APP == Subscriber received: Order { OrderId = 262 }
== APP == Subscriber received: Order { OrderId = 507 }
== APP == Subscriber received: Order { OrderId = 73 }
== APP == Subscriber received: Order { OrderId = 21 }
== APP == Subscriber received: Order { OrderId = 983 }
== APP == Subscriber received: Order { OrderId = 886 }
pubsub.yaml
component file
When you run dapr init
, Dapr creates a default Redis pubsub.yaml
and runs a Redis container on your local machine, located:
- On Windows, under
%UserProfile%\.dapr\components\pubsub.yaml
- On Linux/MacOS, under
~/.dapr/components/pubsub.yaml
With the pubsub.yaml
component, you can easily swap out underlying components without application code changes.
The Redis pubsub.yaml
file included for this quickstart contains the following:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: order_pub_sub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
In the YAML file:
metadata/name
is how your application talks to the component.spec/metadata
defines the connection to the instance of the component.scopes
specify which application can use the component.
{{% /codetab %}}
{{% codetab %}}
Pre-requisites
For this example, you will need:
- Dapr CLI and initialized environment.
- Java JDK 11 (or greater):
- Oracle JDK, or
- OpenJDK
- Apache Maven, version 3.x.
- Docker Desktop.
Step 1: Set up the environment
Clone the sample we've provided.
git clone https://github.com/dapr/quickstarts.git
Step 2: Publish a topic
In a terminal window, navigate to the checkout
directory.
cd pub_sub/java/sdk/checkout
Install the dependencies:
mvn clean install
Run the checkout
publisher service alongside a Dapr sidecar.
dapr run --app-id checkout --components-path ../../../components -- java -jar target/CheckoutService-0.0.1-SNAPSHOT.jar
In the checkout
publisher, we're publishing the orderId message to the Redis instance called order_pub_sub
[(as defined in the pubsub.yaml
component)]({{< ref "#pubsubyaml-component-file" >}}) and topic orders
. As soon as the service starts, it publishes in a loop:
public static void main(String[] args) throws InterruptedException{
String TOPIC_NAME = "orders";
String PUBSUB_NAME = "order_pub_sub";
for (int i = 0; i <= 10; i++) {
int orderId = i;
Order order = new Order(orderId);
DaprClient client = new DaprClientBuilder().build();
// Publish an event/message using Dapr PubSub
client.publishEvent(
PUBSUB_NAME,
TOPIC_NAME,
order).block();
logger.info("Published data: " + order.getOrderId());
TimeUnit.MILLISECONDS.sleep(5000);
}
Step 3: Subscribe to topics
In a new terminal window, navigate to the order-processor
directory.
cd pub_sub/java/sdk/order-processor
Install the dependencies:
mvn clean install
Run the order-processor
subscriber service alongside a Dapr sidecar.
dapr run --app-port 8080 --app-id order-processor --components-path ../../../components -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar
In the order-processor
subscriber, we're subscribing to the Redis instance called order_pub_sub
[(as defined in the pubsub.yaml
component)]({{< ref "#pubsubyaml-component-file" >}}) and topic orders
. This enables your app code to talk to the Redis component instance through the Dapr sidecar.
@Topic(name = "orders", pubsubName = "order_pub_sub")
@PostMapping(path = "/orders", consumes = MediaType.ALL_VALUE)
public Mono<ResponseEntity> getCheckout(@RequestBody(required = false) CloudEvent<Order> cloudEvent) {
return Mono.fromSupplier(() -> {
try {
logger.info("Subscriber received: " + cloudEvent.getData().getOrderId());
return ResponseEntity.ok("SUCCESS");
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
Step 4: View the Pub/sub outputs
Notice, as specified in the code above, the publisher pushes a random number to the Dapr sidecar while the subscriber receives it.
Publisher output:
== APP == 2171 [main] INFO com.service.CheckoutServiceApplication - Published data: 0
== APP == 7194 [main] INFO com.service.CheckoutServiceApplication - Published data: 1
== APP == 12213 [main] INFO com.service.CheckoutServiceApplication - Published data: 2
== APP == 17233 [main] INFO com.service.CheckoutServiceApplication - Published data: 3
== APP == 22252 [main] INFO com.service.CheckoutServiceApplication - Published data: 4
== APP == 27276 [main] INFO com.service.CheckoutServiceApplication - Published data: 5
== APP == 32320 [main] INFO com.service.CheckoutServiceApplication - Published data: 6
== APP == 37340 [main] INFO com.service.CheckoutServiceApplication - Published data: 7
== APP == 42356 [main] INFO com.service.CheckoutServiceApplication - Published data: 8
== APP == 47386 [main] INFO com.service.CheckoutServiceApplication - Published data: 9
== APP == 52410 [main] INFO com.service.CheckoutServiceApplication - Published data: 10
Subscriber output:
== APP == 2022-03-07 13:31:19.551 INFO 43512 --- [nio-8080-exec-1] c.s.c.OrderProcessingServiceController : Subscriber received: 0
== APP == 2022-03-07 13:31:19.551 INFO 43512 --- [nio-8080-exec-5] c.s.c.OrderProcessingServiceController : Subscriber received: 1
== APP == 2022-03-07 13:31:19.552 INFO 43512 --- [nio-8080-exec-9] c.s.c.OrderProcessingServiceController : Subscriber received: 2
== APP == 2022-03-07 13:31:19.551 INFO 43512 --- [nio-8080-exec-6] c.s.c.OrderProcessingServiceController : Subscriber received: 3
== APP == 2022-03-07 13:31:19.552 INFO 43512 --- [nio-8080-exec-2] c.s.c.OrderProcessingServiceController : Subscriber received: 4
== APP == 2022-03-07 13:31:19.553 INFO 43512 --- [nio-8080-exec-2] c.s.c.OrderProcessingServiceController : Subscriber received: 5
== APP == 2022-03-07 13:31:19.553 INFO 43512 --- [nio-8080-exec-9] c.s.c.OrderProcessingServiceController : Subscriber received: 6
== APP == 2022-03-07 13:31:22.849 INFO 43512 --- [nio-8080-exec-3] c.s.c.OrderProcessingServiceController : Subscriber received: 7
== APP == 2022-03-07 13:31:27.866 INFO 43512 --- [nio-8080-exec-6] c.s.c.OrderProcessingServiceController : Subscriber received: 8
== APP == 2022-03-07 13:31:32.895 INFO 43512 --- [nio-8080-exec-6] c.s.c.OrderProcessingServiceController : Subscriber received: 9
== APP == 2022-03-07 13:31:37.919 INFO 43512 --- [nio-8080-exec-2] c.s.c.OrderProcessingServiceController : Subscriber received: 10
pubsub.yaml
component file
When you run dapr init
, Dapr creates a default Redis pubsub.yaml
and runs a Redis container on your local machine, located:
- On Windows, under
%UserProfile%\.dapr\components\pubsub.yaml
- On Linux/MacOS, under
~/.dapr/components/pubsub.yaml
With the pubsub.yaml
component, you can easily swap out underlying components without application code changes.
The Redis pubsub.yaml
file included for this quickstart contains the following:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: order_pub_sub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
scopes:
- orderprocessing
- checkout
In the YAML file:
metadata/name
is how your application talks to the component.spec/metadata
defines the connection to the instance of the component.scopes
specify which application can use the component.
{{% /codetab %}}
{{% codetab %}}
Pre-requisites
For this example, you will need:
Step 1: Set up the environment
Clone the sample we've provided.
git clone https://github.com/dapr/quickstarts.git
Step 2: Publish a topic
In a terminal window, navigate to the checkout
directory.
cd pub_sub/go/sdk/checkout
Install the dependencies:
go build app.go
Run the checkout
publisher service alongside a Dapr sidecar.
dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 --components-path ../../../components -- go run app.go
In the checkout
publisher, we're publishing the orderId message to the Redis instance called order_pub_sub
[(as defined in the pubsub.yaml
component)]({{< ref "#pubsubyaml-component-file" >}}) and topic orders
. As soon as the service starts, it publishes in a loop:
var (
PUBSUB_NAME = "order_pub_sub"
PUBSUB_TOPIC = "orders"
)
func main() {
client, err := dapr.NewClient()
if err != nil {
panic(err)
}
defer client.Close()
ctx := context.Background()
for i := 1; i <= 10; i++ {
order := `{"orderId":` + strconv.Itoa(i) + `}`
// Publish an event using Dapr pub/sub
if err := client.PublishEvent(ctx, PUBSUB_NAME, PUBSUB_TOPIC, []byte(order)); err != nil {
panic(err)
}
fmt.Sprintf("Published data: ", order)
time.Sleep(1000)
}
}
Step 3: Subscribe to topics
In a new terminal window, navigate to the order-processor
directory.
cd pub_sub/go/sdk/order-processor
Install the dependencies:
go build app.go
Run the order-processor
subscriber service alongside a Dapr sidecar.
dapr run --app-port 6001 --app-id order-processor --app-protocol http --dapr-http-port 3501 --components-path ../../../components -- go run app.go
In the order-processor
subscriber, we're subscribing to the Redis instance called order_pub_sub
[(as defined in the pubsub.yaml
component)]({{< ref "#pubsubyaml-component-file" >}}) and topic orders
. This enables your app code to talk to the Redis component instance through the Dapr sidecar.
var sub = &common.Subscription{
PubsubName: "order_pub_sub",
Topic: "orders",
Route: "orders",
}
func main() {
s := daprd.NewService(":6001")
http.HandleFunc("/orders", handleRequest)
if err := s.AddTopicEventHandler(sub, eventHandler); err != nil {
log.Fatalf("error adding topic subscription: %v", err)
}
if err := s.Start(); err != nil && err != http.ErrServerClosed {
log.Fatalf("error listenning: %v", err)
}
}
func eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {
fmt.Println("Subscriber received: ", e.Data)
return false, nil
}
Step 4: View the Pub/sub outputs
Notice, as specified in the code above, the publisher pushes a numbered message to the Dapr sidecar while the subscriber receives it.
Publisher output:
== APP == dapr client initializing for: 127.0.0.1:63293
Subscriber output:
== APP == Subscriber received: {"orderId":1}
== APP == Subscriber received: {"orderId":2}
== APP == Subscriber received: {"orderId":3}
== APP == Subscriber received: {"orderId":4}
== APP == Subscriber received: {"orderId":5}
== APP == Subscriber received: {"orderId":6}
== APP == Subscriber received: {"orderId":7}
== APP == Subscriber received: {"orderId":8}
== APP == Subscriber received: {"orderId":9}
== APP == Subscriber received: {"orderId":10}
pubsub.yaml
component file
When you run dapr init
, Dapr creates a default Redis pubsub.yaml
and runs a Redis container on your local machine, located:
- On Windows, under
%UserProfile%\.dapr\components\pubsub.yaml
- On Linux/MacOS, under
~/.dapr/components/pubsub.yaml
With the pubsub.yaml
component, you can easily swap out underlying components without application code changes.
The Redis pubsub.yaml
file included for this quickstart contains the following:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: order_pub_sub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
scopes:
- orderprocessing
- checkout
In the YAML file:
metadata/name
is how your application talks to the component.spec/metadata
defines the connection to the instance of the component.scopes
specify which application can use the component.
{{% /codetab %}}
{{< /tabs >}}
Next steps
- Set up Pub/sub using HTTP instead of an SDK.
- Learn about [Pub/sub routing]({{< ref howto-route-messages >}})
- Learn about [topic scoping]({{< ref pubsub-scopes.md >}})
- Learn about [message time-to-live]({{< ref pubsub-message-ttl.md >}})
- Learn [how to configure Pub/sub components with multiple namespaces]({{< ref pubsub-namespaces.md >}})
- List of [Pub/sub components]({{< ref setup-pubsub >}})
- Read the [API reference]({{< ref pubsub_api.md >}})
{{< button text="Explore Dapr tutorials >>" page="getting-started/tutorials/_index.md" >}}