From 1dc60a47a87bfcfcadf75038116d1e292d9e4bc1 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Thu, 16 Jun 2022 15:33:57 -0500 Subject: [PATCH] revisions based on changes to code; add image Signed-off-by: Hannah Hunter --- .../en/getting-started/quickstarts/_index.md | 2 +- .../quickstarts/bindings-quickstart.md | 648 ++++++++++-------- .../bindings-quickstart.png | Bin 0 -> 23076 bytes 3 files changed, 354 insertions(+), 296 deletions(-) create mode 100644 daprdocs/static/images/bindings-quickstart/bindings-quickstart.png diff --git a/daprdocs/content/en/getting-started/quickstarts/_index.md b/daprdocs/content/en/getting-started/quickstarts/_index.md index c01d2851f..cd4a5dff7 100644 --- a/daprdocs/content/en/getting-started/quickstarts/_index.md +++ b/daprdocs/content/en/getting-started/quickstarts/_index.md @@ -26,7 +26,7 @@ Hit the ground running with our Dapr quickstarts, complete with code samples aim | [State Management]({{< ref statemanagement-quickstart.md >}}) | Create stateful applications using the state management API. | | [Publish and Subscribe]({{< ref pubsub-quickstart.md >}}) | Send and receive messages using the publish and subscribe API. | | [Secrets Management]({{< ref secrets-quickstart.md >}}) | Retrieve secrets in the application code from a configured secret store using the secrets management API. | -| [Bindings]({{< ref bindings-quickstart.md >}}) | Use Dapr's input and output bindings to schedule a database insert job. | +| [Bindings]({{< ref bindings-quickstart.md >}}) | Schedule a database insert job using the input and output bindings API. | | Actors | Coming soon. | | Observability | Coming soon. | | Configuration | Coming soon. | \ No newline at end of file diff --git a/daprdocs/content/en/getting-started/quickstarts/bindings-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/bindings-quickstart.md index 708a1252f..9a7469b0d 100644 --- a/daprdocs/content/en/getting-started/quickstarts/bindings-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/bindings-quickstart.md @@ -1,20 +1,19 @@ --- type: docs -title: "Quickstart: Bindings" -linkTitle: "Dapr Bindings" +title: "Quickstart: Input & Output Bindings" +linkTitle: "Bindings" weight: 70 description: "Get started with Dapr's Binding building block" --- -Let's take a look at Dapr's [Binding building block]({{< ref bindings >}}). In this Quickstart, you will schedule a batch script to run every 10 seconds using an input [Cron](https://docs.dapr.io/reference/components-reference/supported-bindings/cron/) binding. The script will process a Json file and output data to an external SQL database using the [PostgreSQL](https://docs.dapr.io/reference/components-reference/supported-bindings/postgres) Dapr binding. +Let's take a look at Dapr's [Bindings building block]({{< ref bindings >}}). Using bindings, you can: -Using bindings in Dapr, you can easily interface with a number of external systems. E.g. you can trigger your app with events coming in from external systems, or interface with external systems. Bindings provide several benefits for you and your code: - - Remove the complexities of connecting to, and polling from, messaging systems such as system resources, database, queues and message buses. - - Focus on business logic and not implementation details of how to interact with a system, keeping your code free from SDKs or SDK specific logic. - - Add best practices like resiliency to your interactions with external systems. - - Build portable applications where environment-specific bindings are set-up and no code changes are required to switch between bindings at runtime. +- Trigger your app with events coming in from external systems. +- Interface with external systems. - +In this Quickstart, you will schedule a batch script to run every 10 seconds using an input [Cron](https://docs.dapr.io/reference/components-reference/supported-bindings/cron/) binding. The script will process a JSON file and output data to an external SQL database using the [PostgreSQL](https://docs.dapr.io/reference/components-reference/supported-bindings/postgres) Dapr binding. + + Select your preferred language-specific Dapr SDK before proceeding with the Quickstart. @@ -33,27 +32,35 @@ For this example, you will need: ### Step 1: Set up the environment Clone the [sample we've provided in our Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/bindings). + ```bash git clone https://github.com/dapr/quickstarts.git ``` -### Step 2: Run PostgreSQL Docker Container Locally -In order to run the PostgreSQL bindings quickstart locally, you will run the [PostgreSQL instance](https://www.postgresql.org/) in a docker container on your machine. For convenience, we provided a Docker Compose file to customize, build, run, and initialize the `postgres` container with a default `orders` table for you locally. +### Step 2: Run PostgreSQL Docker container locally -In a terminal window, from the root of the Quickstarts clone directory -navigate to the `bindings\db` directory. +You will run the [PostgreSQL instance](https://www.postgresql.org/) locally in a Docker container on your machine. The Quickstart sample includes a Docker Compose file to locally customize, build, run, and initialize the `postgres` container with a default `orders` table. + +In a terminal window, from the root of the Quickstarts clone directory, navigate to the `bindings\db` directory. + +```bash +cd quickstarts/bindings/db +``` + +Run the following command to set up the container: -To run the container locally, in another new terminal window: ```bash docker compose up ``` -To see the container running locally, run: +Verify that the container is running locally. + ```bash docker ps ``` -The output should be similar to this: +The output should include: + ```bash CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db @@ -61,7 +68,7 @@ CONTAINER ID IMAGE COMMAND CREATED STATUS ### Step 3: Schedule a Cron job and write to the database -In a new terminal window, navigate to the `quickstarts/bindings/python/sdk` directory. +In a new terminal window, navigate to the SDK directory. ```bash cd quickstarts/bindings/python/sdk @@ -76,10 +83,10 @@ pip3 install -r requirements.txt Run the `python-quickstart-binding-sdk` service alongside a Dapr sidecar. ```bash -dapr run --app-id python-quickstart-binding-sdk --app-protocol grpc --app-port 50051 --components-path ../../components python3 batch.py +dapr run --app-id python-quickstart-binding-sdk --app-port 50051 --components-path ../../components python3 batch.py ``` -Code inside the `process_batch` function is executed every 10 seconds (defined in [`cron.yaml`]({{< ref "#cronyaml-component-file" >}}) in the `components` directory). The binding trigger looks simply like a route getting called via HTTP POST in your Flask application, which is called by the Dapr sidecar. +The code inside the `process_batch` function is executed every 10 seconds (defined in [`cron.yaml`]({{< ref "#cronyaml-component-file" >}}) in the `components` directory). The binding trigger looks like a route called via HTTP POST in your Flask application by the Dapr sidecar. ```python # Triggered by Dapr input binding @@ -87,58 +94,80 @@ Code inside the `process_batch` function is executed every 10 seconds (defined i def process_batch(): ``` -The `python-quickstart-binding-sdk` uses the PostgreSQL Output Binding defined in the [`bindings.yaml`]({{< ref "#bindingsyaml-component-file" >}}) component to insert the `OrderId`, `Customer` and `Price` records into the `orders` table. +The `python-quickstart-binding-sdk` service uses the PostgreSQL output binding defined in the [`bindings.yaml`]({{< ref "#bindingsyaml-component-file" >}}) component to insert the `OrderId`, `Customer`, and `Price` records into the `orders` table. ```python - with DaprClient() as d: - sqlCmd = ('insert into orders (orderid, customer, price) values' + - '(%s, \'%s\', %s)' % (order_line['orderid'], - order_line['customer'], - order_line['price'])) - payload = {'sql': sqlCmd} +with DaprClient() as d: + sqlCmd = ('insert into orders (orderid, customer, price) values' + + '(%s, \'%s\', %s)' % (order_line['orderid'], + order_line['customer'], + order_line['price'])) + payload = {'sql': sqlCmd} - print(sqlCmd, flush=True) + print(sqlCmd, flush=True) - try: - # Insert order using Dapr output binding via HTTP Post - resp = d.invoke_binding(binding_name=sql_binding, operation='exec', - binding_metadata=payload, data='') - return resp - except Exception as e: - print(e, flush=True) - raise SystemExit(e) + try: + # Insert order using Dapr output binding via HTTP Post + resp = d.invoke_binding(binding_name=sql_binding, operation='exec', + binding_metadata=payload, data='') + return resp + except Exception as e: + print(e, flush=True) + raise SystemExit(e) ``` ### Step 4: View the output of the job -Notice, as specified above, the code invokes the Output Binding with the `OrderId`, `Customer` and `Price` as a payload. +Notice, as specified above, the code invokes the Output Binding with the `OrderId`, `Customer`, and `Price` as a payload. + +Your output binding's `print` statement output: -Output Binding `print` statement output in your application: ``` == APP == {"sql": "insert into orders (orderid, customer, price) values (1, 'John Smith', 100.32);"} == APP == {"sql": "insert into orders (orderid, customer, price) values (2, 'Jane Bond', 15.4);"} == APP == {"sql": "insert into orders (orderid, customer, price) values (3, 'Tony James', 35.56);"} ``` -You can also see the same data has been inserted into the database. To inspect the `postgres` container, run the following in a new terminal to start the interactive Postgres CLI +In a new terminal, verify the same data has been inserted into the database. Navigate to the `bindings/db` directory. + +```bash +cd quickstarts/bindings/db +``` + +Run the following to start the interactive Postgres CLI: ```bash docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password ``` At the `admin=#` prompt, change to the `orders` table: + ```bash \c orders; ``` At the `orders=#` prompt, select all rows: + ```bash select * from orders; ``` +The output should look like this: + +``` + orderid | customer | price +---------+------------+-------- + 1 | John Smith | 100.32 + 2 | Jane Bond | 15.4 + 3 | Tony James | 35.56 +``` + #### `components\binding-cron.yaml` component file -When you execute the `dapr run` command and specify the location of the component file, the Dapr sidecar initiates the Cron [Binding building block]({{< ref bindings >}}) and calls the binding endpoint (`batch`) every 10 seconds. +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: + +- Initiates the Cron [Binding building block]({{< ref bindings >}}) +- Calls the binding endpoint (`batch`) every 10 seconds The Cron `binding-cron.yaml` file included for this Quickstart contains the following: @@ -156,11 +185,14 @@ spec: value: "@every 10s" # valid cron schedule ``` -**Note:** The `metadata` section of `binding-cron.yaml` contains a [cron expression](/reference/components-reference/supported-bindings/cron/) that specifies how often the binding will be invoked. +**Note:** The `metadata` section of `binding-cron.yaml` contains a [Cron expression](/reference/components-reference/supported-bindings/cron/) that specifies how often the binding will be invoked. #### `component\bindings-postgres.yaml` component file -When you execute the `dapr run` command and specify the location of the component file, the Dapr sidecar initiates the PostgreSQL [Binding building block](/reference/components-reference/supported-bindings/postgres/) and connects to PostgreSQL using the settings specified in the `bindings-postgres.yaml` file. +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: + +- Initiates the PostgreSQL [Binding building block]({{< ref /reference/components-reference/supported-bindings/postgres.md >}}) +- Connects to PostgreSQL using the settings specified in the `bindings-postgres.yaml` file With the `bindings-postgres.yaml` component, you can easily swap out the backend database [binding](/reference/components-reference/supported-bindings/) without making code changes. @@ -177,7 +209,7 @@ spec: version: v1 metadata: - name: url # Required - value: "user=admin password=admin host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" + value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" ``` In the YAML file: @@ -201,60 +233,46 @@ For this example, you will need: ### Step 1: Set up the environment Clone the [sample we've provided in our Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/bindings). + ```bash git clone https://github.com/dapr/quickstarts.git ``` -### Step 2: Run PostgreSQL Docker Container Locally +### Step 2: Run PostgreSQL Docker container locally -In order to run the PostgreSQL bindings quickstart locally, you will run the [PostgreSQL instance](https://www.postgresql.org/) in a docker container on your machine. +You will run the [PostgreSQL instance](https://www.postgresql.org/) locally in a Docker container on your machine. The Quickstart sample includes a Docker Compose file to locally customize, build, run, and initialize the `postgres` container with a default `orders` table. -To run the container locally, run: +In a terminal window, from the root of the Quickstarts clone directory, navigate to the `bindings\db` directory. ```bash -docker run --name sql_db -p 5432:5432 -e POSTGRES_PASSWORD=admin -e POSTGRES_USER=admin -d postgres +cd quickstarts/bindings/db ``` -To see the container running locally, run: +Run the following command to set up the container: + +```bash +docker compose up +``` + +Verify that the container is running locally. + ```bash docker ps ``` -The output should be similar to this: +The output should include: + ```bash CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db ``` -### Step 3: Setup the database schema +### Step 3: Schedule a Cron job and write to the database -Connect to the local PostgreSQL instance. -```bash -docker exec -i -t sql_db psql --username admin -p 5432 -h localhost --no-password -``` -This will launch the PostgreSQL cli. -```bash -psql (14.2 (Debian 14.2-1.pgdg110+1)) -Type "help" for help. - -admin=# -``` -Create a new `orders` database. -```bash -create database orders; -``` -Connect to the new database and create the `orders` table. -```bash -\c orders; -create table orders ( orderid int, customer text, price float ); select * from orders; -``` - -### Step 4: Schedule a Cron job and write to the database - -In a terminal window, navigate to the `sdk` directory. +In a new terminal window, navigate to the SDK directory. ```bash -cd bindings/javascript/sdk +cd quickstarts/bindings/javascript/sdk ``` Install the dependencies: @@ -266,11 +284,18 @@ npm install Run the `javascript-quickstart-binding-sdk` service alongside a Dapr sidecar. ```bash -dapr run --app-id javascript-quickstart-binding-sdk --app-port 3500 --dapr-http-port 5051 node batch.js --components-path ../../components +dapr run --app-id javascript-quickstart-binding-sdk --app-port 3500 --dapr-http-port 5051 --components-path ../../components node batch.js ``` -The `javascript-quickstart-binding-sdk` uses the PostgreSQL Output Binding defined in the [`bindings.yaml`]({{< ref "#bindingsyaml-component-file" >}}) component to insert the `OrderId`, `Customer` and `Price` records into the `orders` table. This code is executed every 10 seconds (defined in [`cron.yaml`]({{< ref "#cronyaml-component-file" >}}) in the `components` directory). -```js +The code inside the `process_batch` function is executed every 10 seconds (defined in [`cron.yaml`]({{< ref "#cronyaml-component-file" >}}) in the `components` directory). The binding trigger looks like a route called via HTTP POST in your Flask application by the Dapr sidecar. + +```javascript + +``` + +The `javascript-quickstart-binding-sdk` service uses the PostgreSQL output binding defined in the [`bindings.yaml`]({{< ref "#bindingsyaml-component-file" >}}) component to insert the `OrderId`, `Customer`, and `Price` records into the `orders` table. + +```javascript async function processBatch(){ const loc = '../../orders.json'; fs.readFile(loc, 'utf8', (err, data) => { @@ -287,43 +312,67 @@ async function processBatch(){ } ``` -### Step 5: View the Output Binding log +### Step 4: View the output of the job -Notice, as specified above, the code invokes the Output Binding with the `OrderId`, `Customer` and `Price` as a payload. +Notice, as specified above, the code invokes the Output Binding with the `OrderId`, `Customer`, and `Price` as a payload. + +Your output binding's `print` statement output: -Output Binding `console.log` statement output: ``` -== APP == [Dapr-JS] Server Started -== APP == { "sql": "insert into orders (orderid, customer, price) values (1, 'John Smith', 100.32);" } -== APP == { "sql": "insert into orders (orderid, customer, price) values (2, 'Jane Bond', 15.4);" } -== APP == { "sql": "insert into orders (orderid, customer, price) values (3, 'Tony James', 35.56);" } -== APP == Finished processing batch +== APP == Processing batch.. +== APP == insert into orders (orderid, customer, price) values(1, 'John Smith', 100.32) +== APP == insert into orders (orderid, customer, price) values(2, 'Jane Bond', 15.4) +== APP == insert into orders (orderid, customer, price) values(3, 'Tony James', 35.56) ``` -### Step 6: Process the `orders.json` file on a schedule +In a new terminal, verify the same data has been inserted into the database. Navigate to the `bindings/db` directory. -The `javascript-quickstart-binding-sdk` uses the Cron Input Binding defined in the [`cron.yaml`]({{< ref "#cronyaml-component-file" >}}) component to process a json file containing order information. - -```js -const server = new DaprServer(serverHost, serverPort, daprHost, daprPort); - -async function start() { - await server.binding.receive(cronBindingName,processBatch); - await server.start(); -} +```bash +cd quickstarts/bindings/db ``` -#### `cron.yaml` component file +Run the following to start the interactive Postgres CLI: -When you execute the `dapr run` command and specify the location of the component file, the Dapr sidecar initiates the Cron [Binding building block]({{< ref bindings >}}) and calls the binding endpoint (`batch`) every 10 seconds. +```bash +docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password +``` -The Cron `cron.yaml` file included for this Quickstart contains the following: +At the `admin=#` prompt, change to the `orders` table: + +```bash +\c orders; +``` + +At the `orders=#` prompt, select all rows: + +```bash +select * from orders; +``` + +The output should look like this: + +``` + orderid | customer | price +---------+------------+-------- + 1 | John Smith | 100.32 + 2 | Jane Bond | 15.4 + 3 | Tony James | 35.56 +``` + +#### `components\binding-cron.yaml` component file + +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: + +- Initiates the Cron [Binding building block]({{< ref bindings >}}) +- Calls the binding endpoint (`batch`) every 10 seconds + +The Cron `binding-cron.yaml` file included for this Quickstart contains the following: ```yaml apiVersion: dapr.io/v1alpha1 kind: Component metadata: - name: batch + name: cron namespace: quickstarts spec: type: bindings.cron @@ -333,28 +382,31 @@ spec: value: "@every 10s" # valid cron schedule ``` -**Note:** The `metadata` section of `cron.yaml` contains a [cron expression](/reference/components-reference/supported-bindings/cron/) that specifies how often the binding will be invoked. +**Note:** The `metadata` section of `binding-cron.yaml` contains a [Cron expression](/reference/components-reference/supported-bindings/cron/) that specifies how often the binding will be invoked. -#### `bindings.yaml` component file +#### `component\bindings-postgres.yaml` component file -When you execute the `dapr run` command and specify the location of the component file, the Dapr sidecar initiates the PostgreSQL [Binding building block](/reference/components-reference/supported-bindings/postgres/) and connects to PostgreSQL using the settings specified in the `bindings.yaml` file. +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: -With the `bindings.yaml` component, you can easily swap out the backend database [binding](/reference/components-reference/supported-bindings/) without making code changes. +- Initiates the PostgreSQL [Binding building block]({{< ref /reference/components-reference/supported-bindings/postgres.md >}}) +- Connects to PostgreSQL using the settings specified in the `bindings-postgres.yaml` file -The PostgreSQL `bindings.yaml` file included for this Quickstart contains the following: +With the `bindings-postgres.yaml` component, you can easily swap out the backend database [binding](/reference/components-reference/supported-bindings/) without making code changes. + +The PostgreSQL `bindings-postgres.yaml` file included for this Quickstart contains the following: ```yaml apiVersion: dapr.io/v1alpha1 kind: Component metadata: - name: SqlDB + name: sqldb namespace: quickstarts spec: type: bindings.postgres version: v1 metadata: - name: url # Required - value: "user=admin password=admin host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" + value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" ``` In the YAML file: @@ -378,60 +430,46 @@ For this example, you will need: ### Step 1: Set up the environment Clone the [sample we've provided in our Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/bindings). + ```bash git clone https://github.com/dapr/quickstarts.git ``` -### Step 2: Run PostgreSQL Docker Container Locally +### Step 2: Run PostgreSQL Docker container locally -In order to run the PostgreSQL bindings quickstart locally, you will run the [PostgreSQL instance](https://www.postgresql.org/) in a docker container on your machine. +You will run the [PostgreSQL instance](https://www.postgresql.org/) locally in a Docker container on your machine. The Quickstart sample includes a Docker Compose file to locally customize, build, run, and initialize the `postgres` container with a default `orders` table. -To run the container locally, run: +In a terminal window, from the root of the Quickstarts clone directory, navigate to the `bindings\db` directory. ```bash -docker run --name sql_db -p 5432:5432 -e POSTGRES_PASSWORD=admin -e POSTGRES_USER=admin -d postgres +cd quickstarts/bindings/db ``` -To see the container running locally, run: +Run the following command to set up the container: + +```bash +docker compose up +``` + +Verify that the container is running locally. + ```bash docker ps ``` -The output should be similar to this: +The output should include: + ```bash CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db ``` -### Step 3: Setup the database schema +### Step 3: Schedule a Cron job and write to the database -Connect to the local PostgreSQL instance. -```bash -docker exec -i -t sql_db psql --username admin -p 5432 -h localhost --no-password -``` -This will launch the PostgreSQL cli. -```bash -psql (14.2 (Debian 14.2-1.pgdg110+1)) -Type "help" for help. - -admin=# -``` -Create a new `orders` database. -```bash -create database orders; -``` -Connect to the new database and create the `orders` table. -```bash -\c orders; -create table orders ( orderid int, customer text, price float ); select * from orders; -``` - -### Step 4: Schedule a Cron job and write to the database - -In a terminal window, navigate to the `sdk` directory. +In a new terminal window, navigate to the SDK directory. ```bash -cd bindings/csharp/sdk +cd quickstarts/bindings/csharp/sdk ``` Install the dependencies: @@ -446,57 +484,89 @@ Run the `csharp-quickstart-binding-sdk` service alongside a Dapr sidecar. ```bash dapr run --app-id csharp-quickstart-binding-sdk --app-port 7001 --components-path ../../components -- dotnet run --project batch.csproj ``` -The `csharp-quickstart-binding-sdk` uses the PostgreSQL Output Binding defined in the [`bindings.yaml`]({{< ref "#bindingsyaml-component-file" >}}) component to insert the `OrderId`, `Customer` and `Price` records into the `orders` table. This code is executed every 10 seconds (defined in [`cron.yaml`]({{< ref "#cronyaml-component-file" >}}) in the `components` directory). -```cs -foreach( Order ord in ordersArr.orders){ - var sqlText = $"insert into orders (orderid, customer, price) values ({ord.OrderId}, '{ord.Customer}', {ord.Price});"; - var command = new Dictionary(){ - {"sql", - sqlText} - }; - await client.InvokeBindingAsync(sqlBindingName, "exec", command,command); - Console.WriteLine(sqlText); -} +The code inside the `process_batch` function is executed every 10 seconds (defined in [`cron.yaml`]({{< ref "#cronyaml-component-file" >}}) in the `components` directory). The binding trigger looks like a route called via HTTP POST in your Flask application by the Dapr sidecar. + +```csharp +Console.WriteLine("Processing batch.."); +string jsonFile = File.ReadAllText("../../orders.json"); +var ordersArray = JsonSerializer.Deserialize(jsonFile); ``` -### Step 5: View the Output Binding log +The `csharp-quickstart-binding-sdk` service uses the PostgreSQL output binding defined in the [`bindings.yaml`]({{< ref "#bindingsyaml-component-file" >}}) component to insert the `OrderId`, `Customer`, and `Price` records into the `orders` table. -Notice, as specified above, the code invokes the Output Binding with the `OrderId`, `Customer` and `Price` as a payload. - -Output Binding `Console.WriteLine` statement output: -``` -== APP == insert into orders (orderid, customer, price) values (1, 'John Smith', 100.32); -== APP == insert into orders (orderid, customer, price) values (2, 'Jane Bond', 15.4); -== APP == insert into orders (orderid, customer, price) values (3, 'Tony James', 35.56); +```csharp +using var client = new DaprClientBuilder().Build(); + foreach(Order ord in ordersArray?.orders ?? new Order[] {}){ + var sqlText = $"insert into orders (orderid, customer, price) values ({ord.OrderId}, '{ord.Customer}', {ord.Price});"; + var command = new Dictionary(){ + {"sql", + sqlText} + }; + Console.WriteLine(sqlText); ``` -### Step 6: Process the `orders.json` file on a schedule +### Step 4: View the output of the job -The `csharp-quickstart-binding-sdk` uses the Cron Input Binding defined in the [`cron.yaml`]({{< ref "#cronyaml-component-file" >}}) component to process a json file containing order information. +Notice, as specified above, the code invokes the Output Binding with the `OrderId`, `Customer`, and `Price` as a payload. -```cs -app.MapPost(cronBindingName, async () => { +Your output binding's `print` statement output: - string text = File.ReadAllText("../../orders.json"); - var ordersArr = JsonSerializer.Deserialize(text); - using var client = new DaprClientBuilder().Build(); - ... - } -}); +``` +== APP == Processing batch.. +== APP == insert into orders (orderid, customer, price) values(1, 'John Smith', 100.32) +== APP == insert into orders (orderid, customer, price) values(2, 'Jane Bond', 15.4) +== APP == insert into orders (orderid, customer, price) values(3, 'Tony James', 35.56) ``` -#### `cron.yaml` component file +In a new terminal, verify the same data has been inserted into the database. Navigate to the `bindings/db` directory. -When you execute the `dapr run` command and specify the location of the component file, the Dapr sidecar initiates the Cron [Binding building block]({{< ref bindings >}}) and calls the binding endpoint (`batch`) every 10 seconds. +```bash +cd quickstarts/bindings/db +``` -The Cron `cron.yaml` file included for this Quickstart contains the following: +Run the following to start the interactive Postgres CLI: + +```bash +docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password +``` + +At the `admin=#` prompt, change to the `orders` table: + +```bash +\c orders; +``` + +At the `orders=#` prompt, select all rows: + +```bash +select * from orders; +``` + +The output should look like this: + +``` + orderid | customer | price +---------+------------+-------- + 1 | John Smith | 100.32 + 2 | Jane Bond | 15.4 + 3 | Tony James | 35.56 +``` + +#### `components\binding-cron.yaml` component file + +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: + +- Initiates the Cron [Binding building block]({{< ref bindings >}}) +- Calls the binding endpoint (`batch`) every 10 seconds + +The Cron `binding-cron.yaml` file included for this Quickstart contains the following: ```yaml apiVersion: dapr.io/v1alpha1 kind: Component metadata: - name: batch + name: cron namespace: quickstarts spec: type: bindings.cron @@ -506,28 +576,31 @@ spec: value: "@every 10s" # valid cron schedule ``` -**Note:** The `metadata` section of `cron.yaml` contains a [cron expression](/reference/components-reference/supported-bindings/cron/) that specifies how often the binding will be invoked. +**Note:** The `metadata` section of `binding-cron.yaml` contains a [Cron expression](/reference/components-reference/supported-bindings/cron/) that specifies how often the binding will be invoked. -#### `bindings.yaml` component file +#### `component\bindings-postgres.yaml` component file -When you execute the `dapr run` command and specify the location of the component file, the Dapr sidecar initiates the PostgreSQL [Binding building block](/reference/components-reference/supported-bindings/postgres/) and connects to PostgreSQL using the settings specified in the `bindings.yaml` file. +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: -With the `bindings.yaml` component, you can easily swap out the backend database [binding](/reference/components-reference/supported-bindings/) without making code changes. +- Initiates the PostgreSQL [Binding building block]({{< ref /reference/components-reference/supported-bindings/postgres.md >}}) +- Connects to PostgreSQL using the settings specified in the `bindings-postgres.yaml` file -The PostgreSQL `bindings.yaml` file included for this Quickstart contains the following: +With the `bindings-postgres.yaml` component, you can easily swap out the backend database [binding](/reference/components-reference/supported-bindings/) without making code changes. + +The PostgreSQL `bindings-postgres.yaml` file included for this Quickstart contains the following: ```yaml apiVersion: dapr.io/v1alpha1 kind: Component metadata: - name: SqlDB + name: sqldb namespace: quickstarts spec: type: bindings.postgres version: v1 metadata: - name: url # Required - value: "user=admin password=admin host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" + value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" ``` In the YAML file: @@ -551,59 +624,46 @@ For this example, you will need: ### Step 1: Set up the environment Clone the [sample we've provided in our Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/bindings). + ```bash git clone https://github.com/dapr/quickstarts.git ``` -### Step 2: Run PostgreSQL Docker Container Locally -In order to run the PostgreSQL bindings quickstart locally, you will run the [PostgreSQL instance](https://www.postgresql.org/) in a docker container on your machine. +### Step 2: Run PostgreSQL Docker container locally -To run the container locally, run: +You will run the [PostgreSQL instance](https://www.postgresql.org/) locally in a Docker container on your machine. The Quickstart sample includes a Docker Compose file to locally customize, build, run, and initialize the `postgres` container with a default `orders` table. + +In a terminal window, from the root of the Quickstarts clone directory, navigate to the `bindings\db` directory. ```bash -docker run --name sql_db -p 5432:5432 -e POSTGRES_PASSWORD=admin -e POSTGRES_USER=admin -d postgres +cd quickstarts/bindings/db ``` -To see the container running locally, run: +Run the following command to set up the container: + +```bash +docker compose up +``` + +Verify that the container is running locally. + ```bash docker ps ``` -The output should be similar to this: +The output should include: + ```bash CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db ``` -### Step 3: Setup the database schema +### Step 3: Schedule a Cron job and write to the database -Connect to the local PostgreSQL instance. -```bash -docker exec -i -t sql_db psql --username admin -p 5432 -h localhost --no-password -``` -This will launch the PostgreSQL cli. -```bash -psql (14.2 (Debian 14.2-1.pgdg110+1)) -Type "help" for help. - -admin=# -``` -Create a new `orders` database. -```bash -create database orders; -``` -Connect to the new database and create the `orders` table. -```bash -\c orders; -create table orders ( orderid int, customer text, price float ); select * from orders; -``` - -### Step 4: Schedule a Cron job and write to the database - -In a terminal window, navigate to the `sdk` directory. +In a new terminal window, navigate to the SDK directory. ```bash -cd bindings/go/sdk +cd quickstarts/bindings/go/sdk ``` Install the dependencies: @@ -612,102 +672,96 @@ Install the dependencies: go build batch.go ``` -Run the `go-input-binding-sdk` service alongside a Dapr sidecar. +Run the `go-quickstart-binding-sdk` service alongside a Dapr sidecar. ```bash -dapr run --app-id go-input-binding-sdk --app-port 6002 --dapr-http-port 6003 --dapr-grpc-port 60002 go run batch.go --components-path ../../components -``` -The `go-input-binding-sdk` uses the PostgreSQL Output Binding defined in the [`bindings.yaml`]({{< ref "#bindingsyaml-component-file" >}}) component to insert the `OrderId`, `Customer` and `Price` records into the `orders` table. This code is executed every 10 seconds (defined in [`cron.yaml`]({{< ref "#cronyaml-component-file" >}}) in the `components` directory). - -```go -func sqlBindings(order Order) (err error) { - - bindingName := "SqlDB" - - client, err := dapr.NewClient() - if err != nil { - return err - } - - ctx := context.Background() - - sqlCmd := fmt.Sprintf("insert into orders (orderid, customer, price) values (%d, '%s', %s);", order.OrderId, order.Customer, strconv.FormatFloat(order.Price, 'f', 2, 64)) - fmt.Println(sqlCmd) - in := &dapr.InvokeBindingRequest{ - Name: bindingName, - Operation: "exec", - Data: []byte(""), - Metadata: map[string]string{"sql": sqlCmd}, - } - err = client.InvokeOutputBinding(ctx, in) - if err != nil { - return err - } - return nil -} +dapr run --app-id go-input-binding-sdk --app-port 6002 --dapr-http-port 6003 --dapr-grpc-port 60002 --components-path ../../components go run batch.go ``` -### Step 5: View the Output Binding log - -Notice, as specified above, the code invokes the Output Binding with the `OrderId`, `Customer` and `Price` as a payload. - -Output Binding `console.log` statement output: -``` -== APP == The File is opened successfully... -== APP == dapr client initializing for: 127.0.0.1:60002 -== APP == insert into orders (orderid, customer, price) values (1, 'John Smith', 100.32); -== APP == insert into orders (orderid, customer, price) values (2, 'Jane Bond', 15.40); -== APP == insert into orders (orderid, customer, price) values (3, 'Tony James', 35.56); -== APP == Finished processing batch -``` - -### Step 6: Process the `orders.json` file on a schedule - -The `go-input-binding-sdk` uses the Cron Input Binding defined in the [`cron.yaml`]({{< ref "#cronyaml-component-file" >}}) component to process a json file containing order information. +The code inside the `process_batch` function is executed every 10 seconds (defined in [`cron.yaml`]({{< ref "#cronyaml-component-file" >}}) in the `components` directory). The binding trigger looks like a route called via HTTP POST in your Flask application by the Dapr sidecar. ```go func processCron(w http.ResponseWriter, r *http.Request) { - fileContent, err := os.Open("../../orders.json") - if err != nil { - log.Fatal(err) - return - } - - fmt.Println("The File is opened successfully...") - - defer fileContent.Close() - - byteResult, _ := ioutil.ReadAll(fileContent) - - var orders Orders - - json.Unmarshal(byteResult, &orders) - - for i := 0; i < len(orders.Orders); i++ { - err := sqlBindings(orders.Orders[i]) - if err != nil { - log.Fatal(err) - os.Exit(1) - } - } - fmt.Println("Finished processing batch") - os.Exit(0) } - ``` -#### `cron.yaml` component file +The `go-quickstart-binding-sdk` service uses the PostgreSQL output binding defined in the [`bindings.yaml`]({{< ref "#bindingsyaml-component-file" >}}) component to insert the `OrderId`, `Customer`, and `Price` records into the `orders` table. -When you execute the `dapr run` command and specify the location of the component file, the Dapr sidecar initiates the Cron [Binding building block]({{< ref bindings >}}) and calls the binding endpoint (`batch`) every 10 seconds. +```go +client, err := dapr.NewClient() + // ... +sqlCmd := fmt.Sprintf("insert into orders (orderid, customer, price) values (%d, '%s', %s);", order.OrderId, order.Customer, strconv.FormatFloat(order.Price, 'f', 2, 64)) +fmt.Println(sqlCmd) +in := &dapr.InvokeBindingRequest{ + Name: bindingName, + Operation: "exec", + Data: []byte(""), + Metadata: map[string]string{"sql": sqlCmd}, +} +``` -The Cron `cron.yaml` file included for this Quickstart contains the following: +### Step 4: View the output of the job + +Notice, as specified above, the code invokes the Output Binding with the `OrderId`, `Customer`, and `Price` as a payload. + +Your output binding's `print` statement output: + +``` +== APP == Processing batch.. +== APP == insert into orders (orderid, customer, price) values(1, 'John Smith', 100.32) +== APP == insert into orders (orderid, customer, price) values(2, 'Jane Bond', 15.4) +== APP == insert into orders (orderid, customer, price) values(3, 'Tony James', 35.56) +``` + +In a new terminal, verify the same data has been inserted into the database. Navigate to the `bindings/db` directory. + +```bash +cd quickstarts/bindings/db +``` + +Run the following to start the interactive Postgres CLI: + +```bash +docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password +``` + +At the `admin=#` prompt, change to the `orders` table: + +```bash +\c orders; +``` + +At the `orders=#` prompt, select all rows: + +```bash +select * from orders; +``` + +The output should look like this: + +``` + orderid | customer | price +---------+------------+-------- + 1 | John Smith | 100.32 + 2 | Jane Bond | 15.4 + 3 | Tony James | 35.56 +``` + +#### `components\binding-cron.yaml` component file + +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: + +- Initiates the Cron [Binding building block]({{< ref bindings >}}) +- Calls the binding endpoint (`batch`) every 10 seconds + +The Cron `binding-cron.yaml` file included for this Quickstart contains the following: ```yaml apiVersion: dapr.io/v1alpha1 kind: Component metadata: - name: batch + name: cron namespace: quickstarts spec: type: bindings.cron @@ -717,28 +771,31 @@ spec: value: "@every 10s" # valid cron schedule ``` -**Note:** The `metadata` section of `cron.yaml` contains a [cron expression](/reference/components-reference/supported-bindings/cron/) that specifies how often the binding will be invoked. +**Note:** The `metadata` section of `binding-cron.yaml` contains a [Cron expression](/reference/components-reference/supported-bindings/cron/) that specifies how often the binding will be invoked. -#### `bindings.yaml` component file +#### `component\bindings-postgres.yaml` component file -When you execute the `dapr run` command and specify the location of the component file, the Dapr sidecar initiates the PostgreSQL [Binding building block](/reference/components-reference/supported-bindings/postgres/) and connects to PostgreSQL using the settings specified in the `bindings.yaml` file. +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: -With the `bindings.yaml` component, you can easily swap out the backend database [binding](/reference/components-reference/supported-bindings/) without making code changes. +- Initiates the PostgreSQL [Binding building block]({{< ref /reference/components-reference/supported-bindings/postgres.md >}}) +- Connects to PostgreSQL using the settings specified in the `bindings-postgres.yaml` file -The PostgreSQL `bindings.yaml` file included for this Quickstart contains the following: +With the `bindings-postgres.yaml` component, you can easily swap out the backend database [binding](/reference/components-reference/supported-bindings/) without making code changes. + +The PostgreSQL `bindings-postgres.yaml` file included for this Quickstart contains the following: ```yaml apiVersion: dapr.io/v1alpha1 kind: Component metadata: - name: SqlDB + name: sqldb namespace: quickstarts spec: type: bindings.postgres version: v1 metadata: - name: url # Required - value: "user=admin password=admin host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" + value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" ``` In the YAML file: @@ -751,6 +808,7 @@ In the YAML file: {{< /tabs >}} ## Tell us what you think! + We're continuously working to improve our Quickstart examples and value your feedback. Did you find this quickstart helpful? Do you have suggestions for improvement? Join the discussion in our [discord channel](https://discord.gg/22ZtJrNe). diff --git a/daprdocs/static/images/bindings-quickstart/bindings-quickstart.png b/daprdocs/static/images/bindings-quickstart/bindings-quickstart.png new file mode 100644 index 0000000000000000000000000000000000000000..842ad2387991aa6cd7123b69ab0260e3484ee7de GIT binary patch literal 23076 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 CD8?QD literal 0 HcmV?d00001