mirror of https://github.com/dapr/docs.git
Added bindings documentation
Signed-off-by: Amulya Varote <amulyavarote@MININT-8V4CL0S.redmond.corp.microsoft.com>
This commit is contained in:
parent
331dfd5231
commit
a29dd2233d
|
@ -9,11 +9,11 @@ weight: 300
|
|||
Output bindings enable you to invoke external resources without taking dependencies on special SDK or libraries.
|
||||
For a complete sample showing output bindings, visit this [link](https://github.com/dapr/quickstarts/tree/master/bindings).
|
||||
|
||||
Watch this [video](https://www.youtube.com/watch?v=ysklxm81MTs&feature=youtu.be&t=1960) on how to use bi-directional output bindings.
|
||||
## Example:
|
||||
|
||||
<div class="embed-responsive embed-responsive-16by9">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/ysklxm81MTs?start=1960" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
The below code example loosely describes an application that processes orders. In the example, there is an order processing service which has a Dapr sidecar. The order processing service uses Dapr to invoke external resources(output binding).
|
||||
|
||||
<img src="/images/building-block-bindings-example.png" width=1000 alt="Diagram showing bindings of example service">
|
||||
|
||||
## 1. Create a binding
|
||||
|
||||
|
@ -21,7 +21,7 @@ An output binding represents a resource that Dapr uses to invoke and send messag
|
|||
|
||||
For the purpose of this guide, you'll use a Kafka binding. You can find a list of the different binding specs [here]({{< ref setup-bindings >}}).
|
||||
|
||||
Create a new binding component with the name of `myevent`.
|
||||
Create a new binding component with the name of `checkout`.
|
||||
|
||||
Inside the `metadata` section, configure Kafka related properties such as the topic to publish the message to and the broker.
|
||||
|
||||
|
@ -36,16 +36,24 @@ Create the following YAML file, named `binding.yaml`, and save this to a `compon
|
|||
apiVersion: dapr.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: myevent
|
||||
namespace: default
|
||||
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: topic1
|
||||
value: sample
|
||||
- name: authRequired
|
||||
value: "false"
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
@ -59,38 +67,228 @@ To deploy this into a Kubernetes cluster, fill in the `metadata` connection deta
|
|||
apiVersion: dapr.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: myevent
|
||||
namespace: default
|
||||
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: topic1
|
||||
value: sample
|
||||
- name: authRequired
|
||||
value: "false"
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{< /tabs >}}
|
||||
|
||||
## 2. Send an event
|
||||
## 2. Send an event(Output Binding)
|
||||
|
||||
Below are code examples that leverage Dapr SDKs for saving and retrieving a single state.
|
||||
|
||||
{{< tabs Dotnet Java Python Go Javascript>}}
|
||||
|
||||
{{% codetab %}}
|
||||
|
||||
```csharp
|
||||
//dependencies
|
||||
using Dapr.Client;
|
||||
|
||||
//code
|
||||
namespace EventService
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
string BINDING_NAME = "checkout";
|
||||
string BINDING_OPERATION = "create";
|
||||
int orderId = 100;
|
||||
using var client = new DaprClientBuilder().Build();
|
||||
//Using Dapr SDK to invoke output binding
|
||||
await client.InvokeBindingAsync(BINDING_NAME, BINDING_OPERATION, orderId);
|
||||
Console.WriteLine("Sending message: " + orderId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application:
|
||||
|
||||
```bash
|
||||
dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 --app-ssl dotnet run
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{% codetab %}}
|
||||
|
||||
```java
|
||||
//dependencies
|
||||
import io.dapr.client.DaprClient;
|
||||
import io.dapr.client.DaprClientBuilder;
|
||||
import io.dapr.client.domain.HttpExtension;
|
||||
|
||||
//code
|
||||
@SpringBootApplication
|
||||
public class OrderProcessingServiceApplication {
|
||||
|
||||
public static void main(String[] args) throws InterruptedException{
|
||||
String BINDING_NAME = "checkout";
|
||||
String BINDING_OPERATION = "create";
|
||||
int orderId = 100;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application:
|
||||
|
||||
```bash
|
||||
dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 mvn spring-boot:run
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{% codetab %}}
|
||||
|
||||
```python
|
||||
#dependencies
|
||||
from dapr.clients import DaprClient
|
||||
|
||||
#code
|
||||
BINDING_NAME = 'checkout'
|
||||
BINDING_OPERATION = 'create'
|
||||
|
||||
orderId = 100
|
||||
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))
|
||||
|
||||
```
|
||||
|
||||
Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application:
|
||||
|
||||
```bash
|
||||
dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 -- python3 OrderProcessingService.py
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{% codetab %}}
|
||||
|
||||
```go
|
||||
//dependencies
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
dapr "github.com/dapr/go-sdk/client"
|
||||
|
||||
)
|
||||
|
||||
//code
|
||||
func main() {
|
||||
BINDING_NAME := "checkout";
|
||||
BINDING_OPERATION := "create";
|
||||
orderId := 100
|
||||
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))
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application:
|
||||
|
||||
```bash
|
||||
dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 go run OrderProcessingService.go
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{% codetab %}}
|
||||
|
||||
```javascript
|
||||
//dependencies
|
||||
import { DaprServer, DaprClient, CommunicationProtocolEnum } from 'dapr-client';
|
||||
|
||||
//code
|
||||
const daprHost = "127.0.0.1";
|
||||
|
||||
var main = function() {
|
||||
var orderId = 100;
|
||||
start(orderId).catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
async function start(orderId) {
|
||||
const BINDING_NAME = "checkout";
|
||||
const BINDING_OPERATION = "create";
|
||||
const client = new DaprClient(daprHost, process.env.DAPR_HTTP_PORT, CommunicationProtocolEnum.HTTP);
|
||||
//Using Dapr SDK to invoke output binding
|
||||
const result = await client.binding.send(BINDING_NAME, BINDING_OPERATION, { orderId: orderId });
|
||||
console.log("Sending message: " + orderId);
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
```
|
||||
|
||||
Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application:
|
||||
|
||||
```bash
|
||||
dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 npm start
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{< /tabs >}}
|
||||
|
||||
All that's left now is to invoke the output bindings endpoint on a running Dapr instance.
|
||||
|
||||
You can do so using HTTP:
|
||||
You can also invoke the output bindings endpoint using HTTP:
|
||||
|
||||
```bash
|
||||
curl -X POST -H 'Content-Type: application/json' http://localhost:3500/v1.0/bindings/myevent -d '{ "data": { "message": "Hi!" }, "operation": "create" }'
|
||||
curl -X POST -H 'Content-Type: application/json' http://localhost:3601/v1.0/bindings/checkout -d '{ "data": { "orderId": "100" }, "operation": "create" }'
|
||||
```
|
||||
|
||||
As seen above, you invoked the `/binding` endpoint with the name of the binding to invoke, in our case its `myevent`.
|
||||
As seen above, you invoked the `/binding` endpoint with the name of the binding to invoke, in our case its `checkout`.
|
||||
The payload goes inside the mandatory `data` field, and can be any JSON serializable value.
|
||||
|
||||
You'll also notice that there's an `operation` field that tells the binding what you need it to do.
|
||||
You can check [here]({{< ref supported-bindings >}}) which operations are supported for every output binding.
|
||||
|
||||
Watch this [video](https://www.youtube.com/watch?v=ysklxm81MTs&feature=youtu.be&t=1960) on how to use bi-directional output bindings.
|
||||
|
||||
<div class="embed-responsive embed-responsive-16by9">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/ysklxm81MTs?start=1960" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
## References
|
||||
|
||||
- [Binding API]({{< ref bindings_api.md >}})
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
type: docs
|
||||
title: "How-To: Trigger your application with input bindings"
|
||||
linkTitle: "How-To: Triggers"
|
||||
linkTitle: "How-To: Input bindings"
|
||||
description: "Use Dapr input bindings to trigger event driven applications"
|
||||
weight: 200
|
||||
---
|
||||
|
@ -18,79 +18,265 @@ Dapr bindings allow you to:
|
|||
|
||||
For more info on bindings, read [this overview]({{<ref bindings-overview.md>}}).
|
||||
|
||||
For a quickstart sample showing bindings, visit this [link](https://github.com/dapr/quickstarts/tree/master/bindings).
|
||||
## Example:
|
||||
|
||||
The below code example loosely describes an application that processes orders. In the example, there is an order processing service which has a Dapr sidecar. The checkout service uses Dapr to trigger the application(input binding).
|
||||
|
||||
<img src="/images/building-block-bindings-example.png" width=1000 alt="Diagram showing bindings of example service">
|
||||
|
||||
## 1. Create a binding
|
||||
|
||||
An input binding represents an event resource that Dapr uses to read events from and push to your application.
|
||||
An output binding represents a resource that Dapr uses to invoke and send messages to.
|
||||
|
||||
For the purpose of this HowTo, we'll use a Kafka binding. You can find a list of the different binding specs [here]({{< ref supported-bindings >}}).
|
||||
For the purpose of this guide, you'll use a Kafka binding. You can find a list of the different binding specs [here]({{< ref setup-bindings >}}).
|
||||
|
||||
Create the following YAML file, named binding.yaml, and save this to a `components` sub-folder in your application directory.
|
||||
Create a new binding component with the name of `checkout`.
|
||||
|
||||
Inside the `metadata` section, configure Kafka related properties such as the topic to publish the message to and the broker.
|
||||
|
||||
{{< tabs "Self-Hosted (CLI)" Kubernetes >}}
|
||||
|
||||
{{% codetab %}}
|
||||
|
||||
Create the following YAML file, named `binding.yaml`, and save this to a `components` sub-folder in your application directory.
|
||||
(Use the `--components-path` flag with `dapr run` to point to your custom components dir)
|
||||
|
||||
*Note: When running in Kubernetes, apply this file to your cluster using `kubectl apply -f binding.yaml`*
|
||||
|
||||
```yaml
|
||||
apiVersion: dapr.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: myevent
|
||||
namespace: default
|
||||
name: checkout
|
||||
spec:
|
||||
type: bindings.kafka
|
||||
version: v1
|
||||
metadata:
|
||||
- name: topics
|
||||
value: topic1
|
||||
# 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"
|
||||
```
|
||||
|
||||
Here, you create a new binding component with the name of `myevent`.
|
||||
{{% /codetab %}}
|
||||
|
||||
Inside the `metadata` section, configure the Kafka related properties such as the topics to listen on, the brokers and more.
|
||||
{{% codetab %}}
|
||||
|
||||
## 2. Listen for incoming events
|
||||
To deploy this into a Kubernetes cluster, fill in the `metadata` connection details of your [desired binding component]({{< ref setup-bindings >}}) in the yaml below (in this case kafka), save as `binding.yaml`, and run `kubectl apply -f binding.yaml`.
|
||||
|
||||
Now configure your application to receive incoming events. If using HTTP, you need to listen on a `POST` endpoint with the name of the binding as specified in `metadata.name` in the file. In this example, this is `myevent`.
|
||||
|
||||
*The following example shows how you would listen for the event in Node.js, but this is applicable to any programming language*
|
||||
```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"
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{< /tabs >}}
|
||||
|
||||
## 2. Listen for incoming events(Input Binding)
|
||||
|
||||
Now configure your application to receive incoming events. If using HTTP, you need to listen on a `POST` endpoint with the name of the binding as specified in `metadata.name` in the file.
|
||||
|
||||
Below are code examples that leverage Dapr SDKs for output binding.
|
||||
|
||||
{{< tabs Dotnet Java Python Go Javascript>}}
|
||||
|
||||
{{% codetab %}}
|
||||
|
||||
```csharp
|
||||
//dependencies
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
//code
|
||||
namespace CheckoutService.controller
|
||||
{
|
||||
[ApiController]
|
||||
public class CheckoutServiceController : Controller
|
||||
{
|
||||
[HttpPost("/checkout")]
|
||||
public ActionResult<string> getCheckout([FromBody] int orderId)
|
||||
{
|
||||
Console.WriteLine("Received Message: " + orderId);
|
||||
return "CID" + orderId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application:
|
||||
|
||||
```bash
|
||||
dapr run --app-id checkout --app-port 6002 --dapr-http-port 3602 --dapr-grpc-port 60002 --app-ssl dotnet run
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{% codetab %}}
|
||||
|
||||
```java
|
||||
//dependencies
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
//code
|
||||
@RestController
|
||||
@RequestMapping("/")
|
||||
public class CheckoutServiceController {
|
||||
@PostMapping(path = "/checkout")
|
||||
public Mono<String> getCheckout(@RequestBody(required = false) byte[] body) {
|
||||
return Mono.fromRunnable(() ->
|
||||
log.info("Received Message: " + new String(body)));
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application:
|
||||
|
||||
```bash
|
||||
dapr run --app-id checkout --app-port 6002 --dapr-http-port 3602 --dapr-grpc-port 60002 mvn spring-boot:run
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{% codetab %}}
|
||||
|
||||
```python
|
||||
#dependencies
|
||||
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)
|
||||
|
||||
```
|
||||
|
||||
Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application:
|
||||
|
||||
```bash
|
||||
dapr run --app-id checkout --app-port 6002 --dapr-http-port 3602 --dapr-grpc-port 60002 -- python3 CheckoutService.py
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{% codetab %}}
|
||||
|
||||
```go
|
||||
//dependencies
|
||||
import (
|
||||
"encoding/json"
|
||||
"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)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application:
|
||||
|
||||
```bash
|
||||
dapr run --app-id checkout --app-port 6002 --dapr-http-port 3602 --dapr-grpc-port 60002 go run CheckoutService.go
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{% codetab %}}
|
||||
|
||||
```javascript
|
||||
const express = require('express')
|
||||
const bodyParser = require('body-parser')
|
||||
const app = express()
|
||||
app.use(bodyParser.json())
|
||||
//dependencies
|
||||
import { DaprServer, CommunicationProtocolEnum } from 'dapr-client';
|
||||
|
||||
const port = 3000
|
||||
//code
|
||||
const daprHost = "127.0.0.1";
|
||||
const serverHost = "127.0.0.1";
|
||||
const serverPort = "6002";
|
||||
const daprPort = "3602";
|
||||
|
||||
app.post('/myevent', (req, res) => {
|
||||
console.log(req.body)
|
||||
res.status(200).send()
|
||||
})
|
||||
start().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
async function start() {
|
||||
const server = new DaprServer(serverHost, serverPort, daprHost, daprPort, CommunicationProtocolEnum.HTTP);
|
||||
await server.binding.receive('checkout', async (orderId) => console.log(`Received Message: ${JSON.stringify(orderId)}`));
|
||||
await server.startServer();
|
||||
}
|
||||
|
||||
app.listen(port, () => console.log(`Kafka consumer app listening on port ${port}!`))
|
||||
```
|
||||
|
||||
Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application:
|
||||
|
||||
```bash
|
||||
dapr run --app-id checkout --app-port 6002 --dapr-http-port 3602 --dapr-grpc-port 60002 dotnet npm start
|
||||
```
|
||||
|
||||
{{% /codetab %}}
|
||||
|
||||
{{< /tabs >}}
|
||||
|
||||
### ACK-ing an event
|
||||
|
||||
In order to tell Dapr that you successfully processed an event in your application, return a `200 OK` response from your HTTP handler.
|
||||
|
||||
```javascript
|
||||
res.status(200).send()
|
||||
```
|
||||
|
||||
### Rejecting an event
|
||||
|
||||
In order to tell Dapr that the event wasn't processed correctly in your application and schedule it for redelivery, return any response different from `200 OK`. For example, a `500 Error`.
|
||||
|
||||
```javascript
|
||||
res.status(500).send()
|
||||
```
|
||||
|
||||
### Specifying a custom route
|
||||
|
||||
By default, incoming events will be sent to an HTTP endpoint that corresponds to the name of the input binding.
|
||||
|
@ -108,7 +294,6 @@ spec:
|
|||
### 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
|
||||
|
||||
* [Bindings building block]({{< ref bindings >}})
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 148 KiB |
Loading…
Reference in New Issue