This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Bindings

Interface with or be triggered from external systems

1 - Bindings overview

Overview of the bindings API building block

Using Dapr’s bindings API, you can trigger your app with events coming in from external systems and interface with external systems. With the bindings API, you can:

  • Avoid the complexities of connecting to and polling from messaging systems, such as queues and message buses.
  • Focus on business logic, instead of the implementation details of interacting with a system.
  • Keep your code free from SDKs or libraries.
  • Handle retries and failure recovery.
  • Switch between bindings at runtime.
  • Build portable applications with environment-specific bindings set-up and no required code changes.

For example, with bindings, your application can respond to incoming Twilio/SMS messages without:

  • Adding or configuring a third-party Twilio SDK
  • Worrying about polling from Twilio (or using WebSockets, etc.)
Diagram showing bindings

In the above diagram:

  • The input binding triggers a method on your application.
  • Execute output binding operations on the component, such as "create".

Bindings are developed independently of Dapr runtime. You can view and contribute to the bindings.

Input bindings

With input bindings, you can trigger your application when an event from an external resource occurs. An optional payload and metadata may be sent with the request.

The following overview video and demo demonstrates how Dapr input binding works.

To receive events from an input binding:

  1. Define the component YAML that describes the binding type and its metadata (connection info, etc.).
  2. Listen for the incoming event using:
    • An HTTP endpoint
    • The gRPC proto library to get incoming events.

Read the Create an event-driven app using input bindings guide to get started with input bindings.

Output bindings

With output bindings, you can invoke external resources. An optional payload and metadata can be sent with the invocation request.

The following overview video and demo demonstrates how Dapr output binding works.

To invoke an output binding:

  1. Define the component YAML that describes the binding type and its metadata (connection info, etc.).
  2. Use the HTTP endpoint or gRPC method to invoke the binding with an optional payload.
  3. Specify an output operation. Output operations depend on the binding component you use, and can include:
    • "create"
    • "update"
    • "delete"
    • "exec"

Read the Use output bindings to interface with external resources guide to get started with output bindings.

Binding directions (optional)

You can provide the direction metadata field to indicate the direction(s) supported by the binding component. In doing so, the Dapr sidecar avoids the "wait for the app to become ready" state, reducing the lifecycle dependency between the Dapr sidecar and the application:

  • "input"
  • "output"
  • "input, output"

See a full example of the bindings direction metadata.

Try out bindings

Quickstarts and tutorials

Want to put the Dapr bindings API to the test? Walk through the following quickstart and tutorials to see bindings in action:

Quickstart/tutorial Description
Bindings quickstart Work with external systems using input bindings to respond to events and output bindings to call operations.
Bindings tutorial Demonstrates how to use Dapr to create input and output bindings to other components. Uses bindings to Kafka.

Start using bindings directly in your app

Want to skip the quickstarts? Not a problem. You can try out the bindings building block directly in your application to invoke output bindings and trigger input bindings. After Dapr is installed, you can begin using the bindings API starting with the input bindings how-to guide.

Next Steps

2 - How-To: Trigger your application with input bindings

Use Dapr input bindings to trigger event driven applications

With input bindings, you can trigger your application when an event from an external resource occurs. An external resource could be a queue, messaging pipeline, cloud-service, filesystem, etc. An optional payload and metadata may be sent with the request.

Input bindings are ideal for event-driven processing, data pipelines, or generally reacting to events and performing further processing. Dapr input bindings allow you to:

  • Receive events without including specific SDKs or libraries
  • Replace bindings without changing your code
  • Focus on business logic and not the event resource implementation
Diagram showing bindings of example service

This guide uses a Kafka binding as an example. You can find your preferred binding spec from the list of bindings components. In this guide:

  1. The example invokes the /binding endpoint with checkout, the name of the binding to invoke.
  2. The payload goes inside the mandatory data field, and can be any JSON serializable value.
  3. The operation field tells the binding what action it needs to take. For example, the Kafka binding supports the create operation.

Create a binding

Create a binding.yaml file and save to a components sub-folder in your application directory.

Create a new binding component named checkout. Within the metadata section, configure the following Kafka-related properties:

  • The topic to which you’ll publish the message
  • The broker

When creating the binding component, specify the supported direction of the binding.

Use the --resources-path flag with the dapr run command to point to your custom resources directory.

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: checkout
spec:
  type: bindings.kafka
  version: v1
  metadata:
  # Kafka broker connection setting
  - name: brokers
    value: localhost:9092
  # consumer configuration: topic and consumer group
  - name: topics
    value: sample
  - name: consumerGroup
    value: group1
  # publisher configuration: topic
  - name: publishTopic
    value: sample
  - name: authRequired
    value: false
  - name: direction
    value: input

To deploy into a Kubernetes cluster, run kubectl apply -f binding.yaml.

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: checkout
spec:
  type: bindings.kafka
  version: v1
  metadata:
  # Kafka broker connection setting
  - name: brokers
    value: localhost:9092
  # consumer configuration: topic and consumer group
  - name: topics
    value: sample
  - name: consumerGroup
    value: group1
  # publisher configuration: topic
  - name: publishTopic
    value: sample
  - name: authRequired
    value: false
  - name: direction
    value: input

Listen for incoming events (input binding)

Configure your application to receive incoming events. If you’re using HTTP, you need to:

  • Listen on a POST endpoint with the name of the binding, as specified in metadata.name in the binding.yaml file.
  • Verify your application allows Dapr to make an OPTIONS request for this endpoint.

Below are code examples that leverage Dapr SDKs to demonstrate an input binding.

The following example demonstrates how to configure an input binding using ASP.NET Core controllers.

The following example demonstrates how to configure an input binding using ASP.NET Core controllers.

using System.Collections.Generic;
using System.Threading.Tasks;
using System;
using Microsoft.AspNetCore.Mvc;

namespace CheckoutService.controller;

[ApiController]
public sealed class CheckoutServiceController : ControllerBase
{
    [HttpPost("/checkout")]
    public ActionResult<string> getCheckout([FromBody] int orderId)
    {
        Console.WriteLine($"Received Message: {orderId}");
        return $"CID{orderId}";
    }
}

The following example demonstrates how to configure the same input binding using a minimal API approach:

app.MapPost("checkout", ([FromBody] int orderId) =>
{
    Console.WriteLine($"Received Message: {orderId}");
    return $"CID{orderId}"
});
//dependencies
import org.springframework.web.bind.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;

//code
@RestController
@RequestMapping("/")
public class CheckoutServiceController {
    private static final Logger log = LoggerFactory.getLogger(CheckoutServiceController.class);
        @PostMapping(path = "/checkout")
        public Mono<String> getCheckout(@RequestBody(required = false) byte[] body) {
            return Mono.fromRunnable(() ->
                    log.info("Received Message: " + new String(body)));
        }
}
#dependencies
import logging
from dapr.ext.grpc import App, BindingRequest

#code
app = App()

@app.binding('checkout')
def getCheckout(request: BindingRequest):
    logging.basicConfig(level = logging.INFO)
    logging.info('Received Message : ' + request.text())

app.run(6002)
//dependencies
import (
	"encoding/json"
	"log"
	"net/http"
	"github.com/gorilla/mux"
)

//code
func getCheckout(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	var orderId int
	err := json.NewDecoder(r.Body).Decode(&orderId)
	log.Println("Received Message: ", orderId)
	if err != nil {
		log.Printf("error parsing checkout input binding payload: %s", err)
		w.WriteHeader(http.StatusOK)
		return
	}
}

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/checkout", getCheckout).Methods("POST", "OPTIONS")
	http.ListenAndServe(":6002", r)
}
//dependencies 
import { DaprServer, CommunicationProtocolEnum } from '@dapr/dapr'; 

//code
const daprHost = "127.0.0.1"; 
const serverHost = "127.0.0.1";
const serverPort = "6002"; 
const daprPort = "3602"; 

start().catch((e) => {
    console.error(e);
    process.exit(1);
});

async function start() {
    const server = new DaprServer({
        serverHost,
        serverPort,
        communicationProtocol: CommunicationProtocolEnum.HTTP,
        clientOptions: {
            daprHost,
            daprPort, 
        }
    });
    await server.binding.receive('checkout', async (orderId) => console.log(`Received Message: ${JSON.stringify(orderId)}`));
    await server.start();
}

ACK an event

Tell Dapr you’ve successfully processed an event in your application by returning a 200 OK response from your HTTP handler.

Reject an event

Tell Dapr the event was not processed correctly in your application and schedule it for redelivery by returning any response other than 200 OK. For example, a 500 Error.

Specify a custom route

By default, incoming events will be sent to an HTTP endpoint that corresponds to the name of the input binding. You can override this by setting the following metadata property in binding.yaml:

name: mybinding
spec:
  type: binding.rabbitmq
  metadata:
  - name: route
    value: /onevent

Event delivery Guarantees

Event delivery guarantees are controlled by the binding implementation. Depending on the binding implementation, the event delivery can be exactly once or at least once.

References

3 - How-To: Use output bindings to interface with external resources

Invoke external systems with output bindings

With output bindings, you can invoke external resources. An optional payload and metadata can be sent with the invocation request.

Diagram showing bindings of example service

This guide uses a Kafka binding as an example. You can find your preferred binding spec from the list of bindings components. In this guide:

  1. The example invokes the /binding endpoint with checkout, the name of the binding to invoke.
  2. The payload goes inside the mandatory data field, and can be any JSON serializable value.
  3. The operation field tells the binding what action it needs to take. For example, the Kafka binding supports the create operation.

Create a binding

Create a binding.yaml file and save to a components sub-folder in your application directory.

Create a new binding component named checkout. Within the metadata section, configure the following Kafka-related properties:

  • The topic to which you’ll publish the message
  • The broker

When creating the binding component, specify the supported direction of the binding.

Use the --resources-path flag with dapr run to point to your custom resources directory.

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: checkout
spec:
  type: bindings.kafka
  version: v1
  metadata:
  # Kafka broker connection setting
  - name: brokers
    value: localhost:9092
  # consumer configuration: topic and consumer group
  - name: topics
    value: sample
  - name: consumerGroup
    value: group1
  # publisher configuration: topic
  - name: publishTopic
    value: sample
  - name: authRequired
    value: false
  - name: direction
    value: output

To deploy the following binding.yaml file into a Kubernetes cluster, run kubectl apply -f binding.yaml.

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: checkout
spec:
  type: bindings.kafka
  version: v1
  metadata:
  # Kafka broker connection setting
  - name: brokers
    value: localhost:9092
  # consumer configuration: topic and consumer group
  - name: topics
    value: sample
  - name: consumerGroup
    value: group1
  # publisher configuration: topic
  - name: publishTopic
    value: sample
  - name: authRequired
    value: false
  - name: direction
    value: output

Send an event (output binding)

The code examples below leverage Dapr SDKs to invoke the output bindings endpoint on a running Dapr instance.

Here’s an example of using a console app with top-level statements in .NET 6+:

Here’s an example of using a console app with top-level statements in .NET 6+:

using System.Text;
using System.Threading.Tasks;
using Dapr.Client;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprClient();
var app = builder.Build();

const string BINDING_NAME = "checkout";
const string BINDING_OPERATION = "create";

var random = new Random();
using var daprClient = app.Services.GetRequiredService<DaprClient>();

while (true)
{
    await Task.Delay(TimeSpan.FromSeconds(5));
    var orderId = random.Next(1, 1000);
    await client.InvokeBindingAsync(BINDING_NAME, BINDING_OPERATION, orderId);
    Console.WriteLine($"Sending message: {orderId}"); 
}
//dependencies
import io.dapr.client.DaprClient;
import io.dapr.client.DaprClientBuilder;
import io.dapr.client.domain.HttpExtension;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Random;
import java.util.concurrent.TimeUnit;

//code
@SpringBootApplication
public class OrderProcessingServiceApplication {

	private static final Logger log = LoggerFactory.getLogger(OrderProcessingServiceApplication.class);

	public static void main(String[] args) throws InterruptedException{
		String BINDING_NAME = "checkout";
		String BINDING_OPERATION = "create";
		while(true) {
			TimeUnit.MILLISECONDS.sleep(5000);
			Random random = new Random();
			int orderId = random.nextInt(1000-1) + 1;
			DaprClient client = new DaprClientBuilder().build();
          //Using Dapr SDK to invoke output binding
			client.invokeBinding(BINDING_NAME, BINDING_OPERATION, orderId).block();
			log.info("Sending message: " + orderId);
		}
	}
}
#dependencies
import random
from time import sleep    
import requests
import logging
import json
from dapr.clients import DaprClient

#code
logging.basicConfig(level = logging.INFO)
BINDING_NAME = 'checkout'
BINDING_OPERATION = 'create' 
while True:
    sleep(random.randrange(50, 5000) / 1000)
    orderId = random.randint(1, 1000)
    with DaprClient() as client:
        #Using Dapr SDK to invoke output binding
        resp = client.invoke_binding(BINDING_NAME, BINDING_OPERATION, json.dumps(orderId))
    logging.basicConfig(level = logging.INFO)
    logging.info('Sending message: ' + str(orderId))
    
//dependencies
import (
	"context"
	"log"
	"math/rand"
	"time"
	"strconv"
	dapr "github.com/dapr/go-sdk/client"

)

//code
func main() {
	BINDING_NAME := "checkout";
	BINDING_OPERATION := "create";
	for i := 0; i < 10; i++ {
		time.Sleep(5000)
		orderId := rand.Intn(1000-1) + 1
		client, err := dapr.NewClient()
		if err != nil {
			panic(err)
		}
		defer client.Close()
		ctx := context.Background()
        //Using Dapr SDK to invoke output binding
		in := &dapr.InvokeBindingRequest{ Name: BINDING_NAME, Operation: BINDING_OPERATION , Data: []byte(strconv.Itoa(orderId))}
		err = client.InvokeOutputBinding(ctx, in)
		log.Println("Sending message: " + strconv.Itoa(orderId))
	}
}
    
//dependencies
import { DaprClient, CommunicationProtocolEnum } from "@dapr/dapr";

//code
const daprHost = "127.0.0.1";

(async function () {
    for (var i = 0; i < 10; i++) {
        await sleep(2000);
        const orderId = Math.floor(Math.random() * (1000 - 1) + 1);
        try {
            await sendOrder(orderId)
        } catch (err) {
            console.error(e);
            process.exit(1);
        }
    }
})();

async function sendOrder(orderId) {
    const BINDING_NAME = "checkout";
    const BINDING_OPERATION = "create";
    const client = new DaprClient({
        daprHost,
        daprPort: process.env.DAPR_HTTP_PORT,
        communicationProtocol: CommunicationProtocolEnum.HTTP,
    });
    //Using Dapr SDK to invoke output binding
    const result = await client.binding.send(BINDING_NAME, BINDING_OPERATION, orderId);
    console.log("Sending message: " + orderId);
}

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

You can also invoke the output bindings endpoint using HTTP:

curl -X POST -H 'Content-Type: application/json' http://localhost:3601/v1.0/bindings/checkout -d '{ "data": 100, "operation": "create" }'

Watch this video on how to use bi-directional output bindings.

References