diff --git a/daprdocs/content/en/concepts/faq.md b/daprdocs/content/en/concepts/faq.md index fbe8a3219..e63045df8 100644 --- a/daprdocs/content/en/concepts/faq.md +++ b/daprdocs/content/en/concepts/faq.md @@ -25,7 +25,7 @@ Virtual actor capabilities are one of the building blocks that Dapr provides in Creating a new actor follows a local call like `http://localhost:3500/v1.0/actors///…`. For example, `http://localhost:3500/v1.0/actors/myactor/50/method/getData` calls the `getData` method on the newly created `myactor` with id `50`. -The Dapr runtime SDKs have language-specific actor frameworks. For example, the .NET SDK has C# actors. The goal is for all the Dapr language SDKs to have an actor framework. Currently .NET, Java and Python SDK have actor frameworks. +The Dapr runtime SDKs have language-specific actor frameworks. For example, the .NET SDK has C# actors. The goal is for all the Dapr language SDKs to have an actor framework. Currently .NET, Java, Go and Python SDK have actor frameworks. ## Developer language SDKs and frameworks diff --git a/daprdocs/content/en/developing-applications/building-blocks/configuration/app-configuration-overview.md b/daprdocs/content/en/developing-applications/building-blocks/configuration/app-configuration-overview.md deleted file mode 100644 index 5fd34c9bb..000000000 --- a/daprdocs/content/en/developing-applications/building-blocks/configuration/app-configuration-overview.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -type: docs -title: "Configuration overview" -linkTitle: "Configuration overview" -weight: 1000 -description: "Use Dapr to get and watch application configuration" ---- - -Consuming application configuration is a common task when writing applications and frequently configuration stores are used to manage this configuration data. A configuration item is often dynamic in nature and is tightly coupled to the needs of the application that consumes it. For example, common uses for application configuration include names of secrets that need to be retrieved, different identifiers, partition or consumer IDs, names of databased to connect to etc. These configuration items are typically stored as key-value items in a database. - -Dapr provides a [State Management API]({{}}) that is based on key-value stores. However, application configuration can be changed by either developers or operators at runtime and the developer needs to be notified of these changes in order to take the required action and load the new configuration. Also the configuration data may want to be read only. Dapr's Configuration API allows developers to consume configuration items that are returned as key/value pair and subscribe to changes whenever a configuration item changes. - -*This API is currently in `Alpha state` and only available on gRPC. An HTTP1.1 supported version with this URL `/v1.0/configuration` will be available before the API becomes stable.* - -## References - -- [How-To: Manage application configuration]({{< ref howto-manage-configuration.md >}}) - diff --git a/daprdocs/content/en/developing-applications/building-blocks/secrets/howto-secrets.md b/daprdocs/content/en/developing-applications/building-blocks/secrets/howto-secrets.md index 4ada756ba..73565e055 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/secrets/howto-secrets.md +++ b/daprdocs/content/en/developing-applications/building-blocks/secrets/howto-secrets.md @@ -8,17 +8,25 @@ description: "Use the secret store building block to securely retrieve a secret" This article provides guidance on using Dapr's secrets API in your code to leverage the [secrets store building block]({{}}). The secrets API allows you to easily retrieve secrets in your application code from a configured secret store. +## 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 order processing service uses Dapr to store a secret in a local secret store. + +Diagram showing secrets management of example service + ## Set up a secret store Before retrieving secrets in your application's code, you must have a secret store component configured. For the purposes of this guide, as an example you will configure a local secret store which uses a local JSON file to store secrets. ->Note: The component used in this example is not secured and is not recommended for production deployments. You can find other alternatives [here]({{}}). +{{% alert title="Warning" color="warning" %}} +In a production-grade application, local secret stores are not recommended. You can find other alternatives [here]({{}}) to securely manage your secrets. +{{% /alert %}} -Create a file named `mysecrets.json` with the following contents: +Create a file named `secrets.json` with the following contents: ```json { - "my-secret" : "I'm Batman" + "secret": "Order Processing pass key" } ``` @@ -28,146 +36,226 @@ Create a directory for your components file named `components` and inside it cre apiVersion: dapr.io/v1alpha1 kind: Component metadata: - name: my-secrets-store + name: localsecretstore namespace: default spec: type: secretstores.local.file version: v1 metadata: - name: secretsFile - value: /mysecrets.json + value: secrets.json #path to secrets file - name: nestedSeparator value: ":" ``` -Make sure to replace `` with the path to the JSON file you just created. - >Note: the path to the secret store JSON is relative to where you call `dapr run` from. - To configure a different kind of secret store see the guidance on [how to configure a secret store]({{}}) and review [supported secret stores]({{}}) to see specific details required for different secret store solutions. ## Get a secret -Now run the Dapr sidecar (with no application) +Run the Dapr sidecar with the application. + +{{< tabs Dotnet Java Python Go Javascript>}} + +{{% codetab %}} ```bash -dapr run --app-id my-app --dapr-http-port 3500 --components-path ./components +dapr run --app-id orderprocessingservice --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 --components-path ./components dotnet run ``` +{{% /codetab %}} -And now you can get the secret by calling the Dapr sidecar using the secrets API: + +{{% codetab %}} +```bash +dapr run --app-id orderprocessingservice --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 --components-path ./components mvn spring-boot:run +``` +{{% /codetab %}} + + +{{% codetab %}} +```bash +dapr run --app-id orderprocessingservice --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 --components-path ./components python3 OrderProcessingService.py +``` +{{% /codetab %}} + + +{{% codetab %}} +```bash +dapr run --app-id orderprocessingservice --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 --components-path ./components go run OrderProcessingService.go +``` +{{% /codetab %}} + + +{{% codetab %}} +```bash +dapr run --app-id orderprocessingservice --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 --components-path ./components npm start +``` +{{% /codetab %}} + +{{< /tabs >}} + +Get the secret by calling the Dapr sidecar using the secrets API: ```bash -curl http://localhost:3500/v1.0/secrets/my-secrets-store/my-secret +curl http://localhost:3601/v1.0/secrets/localsecretstore/secret ``` For a full API reference, go [here]({{< ref secrets_api.md >}}). ## Calling the secrets API from your code -Once you have a secret store set up, you can call Dapr to get the secrets from your application code. Here are a few examples in different programming languages: +Once you have a secret store, call Dapr to get the secrets from your application code. Below are code examples that leverage Dapr SDKs for retrieving a secret. -{{< tabs "Go" "Javascript" "Python" "Rust" "C#" "PHP" >}} +{{< tabs Dotnet Java Python Go Javascript>}} {{% codetab %}} -```Go -import ( - "fmt" - "net/http" -) +```csharp -func main() { - url := "http://localhost:3500/v1.0/secrets/my-secrets-store/my-secret" +//dependencies +using Dapr.Client; - res, err := http.Get(url) - if err != nil { - panic(err) - } - defer res.Body.Close() - - body, _ := ioutil.ReadAll(res.Body) - fmt.Println(string(body)) +//code +namespace EventService +{ + class Program + { + static async Task Main(string[] args) + { + string SECRET_STORE_NAME = "localsecretstore"; + using var client = new DaprClientBuilder().Build(); + //Using Dapr SDK to get a secret + var secret = await client.GetSecretAsync(SECRET_STORE_NAME, "secret"); + Console.WriteLine($"Result: {string.Join(", ", secret)}"); + Console.WriteLine($"Result for bulk: {string.Join(", ", secret.Keys)}"); + } + } } -``` +``` {{% /codetab %}} {{% codetab %}} -```javascript -require('isomorphic-fetch'); -const secretsUrl = `http://localhost:3500/v1.0/secrets`; +```java + +//dependencies +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; + +//code +@SpringBootApplication +public class OrderProcessingServiceApplication { + + private static final Logger log = LoggerFactory.getLogger(OrderProcessingServiceApplication.class); + private static final ObjectMapper JSON_SERIALIZER = new ObjectMapper(); + + private static final String SECRET_STORE_NAME = "localsecretstore"; + + public static void main(String[] args) throws InterruptedException, JsonProcessingException { + DaprClient client = new DaprClientBuilder().build(); + //Using Dapr SDK to get a secret + Map secret = client.getSecret(SECRET_STORE_NAME, "secret").block(); + log.info("Result: " + JSON_SERIALIZER.writeValueAsString(secret)); + } +} -fetch(`${secretsUrl}/my-secrets-store/my-secret`) - .then((response) => { - if (!response.ok) { - throw "Could not get secret"; - } - return response.text(); - }).then((secret) => { - console.log(secret); - }); ``` - {{% /codetab %}} {{% codetab %}} ```python -import requests as req -resp = req.get("http://localhost:3500/v1.0/secrets/my-secrets-store/my-secret") -print(resp.text) +#dependencies +from dapr.clients import DaprClient +from dapr.clients.grpc._state import StateItem +from dapr.clients.grpc._request import TransactionalStateOperation, TransactionOperationType + +#code +logging.basicConfig(level = logging.INFO) + +DAPR_STORE_NAME = "localsecretstore" +key = 'secret' + +with DaprClient() as client: + #Using Dapr SDK to get a secret + secret = client.get_secret(store_name=DAPR_STORE_NAME, key=key) + logging.info('Result: ') + logging.info(secret.secret) + secret = client.get_bulk_secret(store_name=DAPR_STORE_NAME) + logging.info('Result for bulk secret: ') + logging.info(sorted(secret.secrets.items())) + ``` - {{% /codetab %}} - {{% codetab %}} -```rust -#![deny(warnings)] -use std::{thread}; +```go -#[tokio::main] -async fn main() -> Result<(), reqwest::Error> { - let res = reqwest::get("http://localhost:3500/v1.0/secrets/my-secrets-store/my-secret").await?; - let body = res.text().await?; - println!("Secret:{}", body); +//dependencies +import ( + "context" + "log" + dapr "github.com/dapr/go-sdk/client" +) - thread::park(); +//code +func main() { + client, err := dapr.NewClient() + SECRET_STORE_NAME := "localsecretstore" + if err != nil { + panic(err) + } + defer client.Close() + ctx := context.Background() - Ok(()) + secret, err := client.GetSecret(ctx, SECRET_STORE_NAME, "secret", nil) + if err != nil { + return nil, errors.Wrap(err, "Got error for accessing key") + } + + if secret != nil { + log.Println("Result : ") + log.Println(secret) + } + + secretRandom, err := client.GetBulkSecret(ctx, SECRET_STORE_NAME, nil) + if err != nil { + return nil, errors.Wrap(err, "Got error for accessing key") + } + + if secret != nil { + log.Println("Result for bulk: ") + log.Println(secretRandom) + } } -``` -{{% /codetab %}} - -{{% codetab %}} - -```csharp -var client = new HttpClient(); -var response = await client.GetAsync("http://localhost:3500/v1.0/secrets/my-secrets-store/my-secret"); -response.EnsureSuccessStatusCode(); - -string secret = await response.Content.ReadAsStringAsync(); -Console.WriteLine(secret); ``` {{% /codetab %}} {{% codetab %}} -```php -run(function(\Dapr\SecretManager $secretManager, \Psr\Log\LoggerInterface $logger) { - $secret = $secretManager->retrieve(secret_store: 'my-secret-store', name: 'my-secret'); - $logger->alert('got secret: {secret}', ['secret' => $secret]); -}); ``` - {{% /codetab %}} {{< /tabs >}} @@ -179,4 +267,4 @@ $app->run(function(\Dapr\SecretManager $secretManager, \Psr\Log\LoggerInterface - [Configure a secret store]({{}}) - [Supported secrets]({{}}) - [Using secrets in components]({{}}) -- [Secret stores quickstart](https://github.com/dapr/quickstarts/tree/master/secretstore) +- [Secret stores quickstart](https://github.com/dapr/quickstarts/tree/master/secretstore) \ No newline at end of file diff --git a/daprdocs/content/en/developing-applications/building-blocks/service-invocation/howto-invoke-discover-services.md b/daprdocs/content/en/developing-applications/building-blocks/service-invocation/howto-invoke-discover-services.md index 6e99d4a04..4f0cb2127 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/service-invocation/howto-invoke-discover-services.md +++ b/daprdocs/content/en/developing-applications/building-blocks/service-invocation/howto-invoke-discover-services.md @@ -10,9 +10,9 @@ This article describe how to deploy services each with an unique application ID, ## Example: -The below code examples loosely describe an application that processes orders. In the examples, there are two services - an order processing service and a checkout service. Both services have Dapr sidecars and the order processing service uses Dapr to invoke the checkout method in the checkout service. +The below code examples loosely describes an application that processes orders. In the examples, there are two services - an order processing service and a checkout service. Both services have Dapr sidecars and the order processing service uses Dapr to invoke the checkout method in the checkout service. -Diagram showing service invocation of example service +Diagram showing service invocation of example service ## Step 1: Choose an ID for your service @@ -184,18 +184,26 @@ Below are code examples that leverage Dapr SDKs for service invocation. {{% codetab %}} ```csharp -//headers - +//dependencies using Dapr.Client; -using System.Net.Http; //code - -CancellationTokenSource source = new CancellationTokenSource(); -CancellationToken cancellationToken = source.Token; -using var client = new DaprClientBuilder().Build(); -var result = client.CreateInvokeMethodRequest(HttpMethod.Get, "checkout", "checkout/" + orderId, cancellationToken); -await client.InvokeMethodAsync(result); +namespace EventService +{ + class Program + { + static async Task Main(string[] args) + { + int orderId = 100; + CancellationTokenSource source = new CancellationTokenSource(); + CancellationToken cancellationToken = source.Token; + //Using Dapr SDK to invoke a method + using var client = new DaprClientBuilder().Build(); + var result = client.CreateInvokeMethodRequest(HttpMethod.Get, "checkout", "checkout/" + orderId, cancellationToken); + await client.InvokeMethodAsync(result); + } + } +} ``` {{% /codetab %}} @@ -204,22 +212,27 @@ await client.InvokeMethodAsync(result); {{% codetab %}} ```java -//headers - +//dependencies import io.dapr.client.DaprClient; import io.dapr.client.DaprClientBuilder; import io.dapr.client.domain.HttpExtension; //code - -DaprClient daprClient = new DaprClientBuilder().build(); -var result = daprClient.invokeMethod( - "checkout", - "checkout/" + orderId, - null, - HttpExtension.GET, - String.class -); +@SpringBootApplication +public class OrderProcessingServiceApplication { + public static void main(String[] args) throws InterruptedException { + int orderId = 100; + //Using Dapr SDK to invoke a method + DaprClient client = new DaprClientBuilder().build(); + var result = client.invokeMethod( + "checkout", + "checkout/" + orderId, + null, + HttpExtension.GET, + String.class + ); + } +} ``` {{% /codetab %}} @@ -227,19 +240,19 @@ var result = daprClient.invokeMethod( {{% codetab %}} ```python -//headers - +#dependencies from dapr.clients import DaprClient -//code - -with DaprClient() as daprClient: - result = daprClient.invoke_method( - "checkout", - f"checkout/{orderId}", - data=b'', - http_verb="GET" - ) +#code +orderId = 100 +#Using Dapr SDK to invoke a method +with DaprClient() as client: + result = client.invoke_method( + "checkout", + f"checkout/{orderId}", + data=b'', + http_verb="GET" + ) ``` {{% /codetab %}} @@ -247,21 +260,25 @@ with DaprClient() as daprClient: {{% codetab %}} ```go -//headers +//dependencies import ( - dapr "github.com/dapr/go-sdk/client" + "strconv" + dapr "github.com/dapr/go-sdk/client" + ) //code - -client, err := dapr.NewClient() -if err != nil { - panic(err) +func main() { + orderId := 100 + //Using Dapr SDK to invoke a method + client, err := dapr.NewClient() + if err != nil { + panic(err) + } + defer client.Close() + ctx := context.Background() + result, err := client.InvokeMethod(ctx, "checkout", "checkout/" + strconv.Itoa(orderId), "get") } -defer client.Close() -ctx := context.Background() - -result, err := client.InvokeMethod(ctx, "checkout", "checkout/" + strconv.Itoa(orderId), "get") ``` {{% /codetab %}} @@ -269,15 +286,20 @@ result, err := client.InvokeMethod(ctx, "checkout", "checkout/" + strconv.Itoa(o {{% codetab %}} ```javascript -//headers - +//dependencies import { DaprClient, HttpMethod, CommunicationProtocolEnum } from 'dapr-client'; //code +const daprHost = "127.0.0.1"; -const daprHost = "127.0.0.1"; -const client = new DaprClient(daprHost, process.env.DAPR_HTTP_PORT, CommunicationProtocolEnum.HTTP); -const result = await client.invoker.invoke('checkout' , "checkout/" + orderId , HttpMethod.GET); +var main = function() { + var orderId = 100; + //Using Dapr SDK to invoke a method + const client = new DaprClient(daprHost, process.env.DAPR_HTTP_PORT, CommunicationProtocolEnum.HTTP); + const result = await client.invoker.invoke('checkout' , "checkout/" + orderId , HttpMethod.GET); +} + +main(); ``` {{% /codetab %}} @@ -341,4 +363,4 @@ For more information on tracing and logs see the [observability]({{< ref observa ## Related Links * [Service invocation overview]({{< ref service-invocation-overview.md >}}) -* [Service invocation API specification]({{< ref service_invocation_api.md >}}) \ No newline at end of file +* [Service invocation API specification]({{< ref service_invocation_api.md >}}) diff --git a/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-get-save-state.md b/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-get-save-state.md index d00c1ea61..1684b7ddb 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-get-save-state.md +++ b/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-get-save-state.md @@ -19,6 +19,12 @@ In this guide we'll start of with the basics: Using the key/value state API to a - [Dapr CLI]({{< ref install-dapr-cli.md >}}) - Initialized [Dapr environment]({{< ref install-dapr-selfhost.md >}}) +## Example: + +The below code examples loosely describe an application that processes orders. In the examples, there is an order processing service which has a Dapr sidecar. The order processing service uses Dapr to store state in a Redis state store. + +Diagram showing state management of example service + ## Step 1: Setup a state store A state store component represents a resource that Dapr uses to communicate with a database. @@ -60,136 +66,255 @@ See the instructions [here]({{< ref "setup-state-store" >}}) on how to setup dif ## Step 2: Save and retrieve a single state -The following example shows how to a single key/value pair using the Dapr state building block. +The following example shows how to save and retrieve a single key/value pair using the Dapr state building block. {{% alert title="Note" color="warning" %}} It is important to set an app-id, as the state keys are prefixed with this value. If you don't set it one is generated for you at runtime, and the next time you run the command a new one will be generated and you will no longer be able to access previously saved state. {{% /alert %}} -{{< tabs "HTTP API (Bash)" "HTTP API (PowerShell)" "Python SDK" "PHP SDK">}} +Below are code examples that leverage Dapr SDKs for saving and retrieving a single state. -{{% codetab %}} -Begin by launching a Dapr sidecar: - -```bash -dapr run --app-id myapp --dapr-http-port 3500 -``` - -Then in a separate terminal save a key/value pair into your statestore: -```bash -curl -X POST -H "Content-Type: application/json" -d '[{ "key": "key1", "value": "value1"}]' http://localhost:3500/v1.0/state/statestore -``` - -Now get the state you just saved: -```bash -curl http://localhost:3500/v1.0/state/statestore/key1 -``` - -You can also restart your sidecar and try retrieving state again to see that state persists separate from the app. -{{% /codetab %}} +{{< tabs Dotnet Java Python Go Javascript "HTTP API (Bash)" "HTTP API (PowerShell)">}} {{% codetab %}} -Begin by launching a Dapr sidecar: +```csharp + +//dependencies +using Dapr.Client; + +//code +namespace EventService +{ + class Program + { + static async Task Main(string[] args) + { + string DAPR_STORE_NAME = "statestore"; + + int orderId = 100; + //Using Dapr SDK to save and get state + using var client = new DaprClientBuilder().Build(); + await client.SaveStateAsync(DAPR_STORE_NAME, "order_1", orderId.ToString()); + await client.SaveStateAsync(DAPR_STORE_NAME, "order_2", orderId.ToString()); + var result = await client.GetStateAsync(DAPR_STORE_NAME, orderId.ToString()); + } + } +} + +``` + +Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application: ```bash -dapr --app-id myapp --port 3500 run +dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 dotnet run ``` -Then in a separate terminal save a key/value pair into your statestore: -```powershell -Invoke-RestMethod -Method Post -ContentType 'application/json' -Body '[{"key": "key1", "value": "value1"}]' -Uri 'http://localhost:3500/v1.0/state/statestore' -``` - -Now get the state you just saved: -```powershell -Invoke-RestMethod -Uri 'http://localhost:3500/v1.0/state/statestore/key1' -``` - -You can also restart your sidecar and try retrieving state again to see that state persists separate from the app. - {{% /codetab %}} + {{% codetab %}} -Save the following to a file named `pythonState.py`: +```java + +//dependencies +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; +import io.dapr.client.domain.State; +import reactor.core.publisher.Mono; + +//code +@SpringBootApplication +public class OrderProcessingServiceApplication { + + private static final Logger log = LoggerFactory.getLogger(OrderProcessingServiceApplication.class); + + public static void main(String[] args) throws InterruptedException { + String STATE_STORE_NAME = "statestore"; + + int orderId = 100; + //Using Dapr SDK to save and get state + DaprClient client = new DaprClientBuilder().build(); + client.saveState(STATE_STORE_NAME, "order_1", Integer.toString(orderId)).block(); + client.saveState(STATE_STORE_NAME, "order_2", Integer.toString(orderId)).block(); + Mono> result = client.getState(STATE_STORE_NAME, "order_1", String.class); + } +} + +``` + +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 -with DaprClient() as d: - d.save_state(store_name="statestore", key="myFirstKey", value="myFirstValue" ) - print("State has been stored") +#code +logging.basicConfig(level = logging.INFO) + +DAPR_STORE_NAME = "statestore" - data = d.get_state(store_name="statestore", key="myFirstKey").data - print(f"Got value: {data}") +orderId = 100 +#Using Dapr SDK to save and get state +with DaprClient() as client: + client.save_state(DAPR_STORE_NAME, "order_1", str(orderId)) + result = client.get_state(DAPR_STORE_NAME, "order_1") + logging.info('Result after get: ' + str(result)) ``` -Once saved run the following command to launch a Dapr sidecar and run the Python application: +Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application: ```bash -dapr --app-id myapp run python pythonState.py -``` - -You should get an output similar to the following, which will show both the Dapr and app logs: - -```md -== DAPR == time="2021-01-06T21:34:33.7970377-08:00" level=info msg="starting Dapr Runtime -- version 0.11.3 -- commit a1a8e11" app_id=Braidbald-Boot scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T21:34:33.8040378-08:00" level=info msg="standalone mode configured" app_id=Braidbald-Boot scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T21:34:33.8040378-08:00" level=info msg="app id: Braidbald-Boot" app_id=Braidbald-Boot scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T21:34:33.9750400-08:00" level=info msg="component loaded. name: statestore, type: state.redis" app_id=Braidbald-Boot scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T21:34:33.9760387-08:00" level=info msg="API gRPC server is running on port 51656" app_id=Braidbald-Boot scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T21:34:33.9770372-08:00" level=info msg="dapr initialized. Status: Running. Init Elapsed 172.9994ms" app_id=Braidbald-Boot scope=dapr. - -Checking if Dapr sidecar is listening on GRPC port 51656 -Dapr sidecar is up and running. -Updating metadata for app command: python pythonState.py -You are up and running! Both Dapr and your app logs will appear here. - -== APP == State has been stored -== APP == Got value: b'myFirstValue' +dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 -- python3 OrderProcessingService.py ``` {{% /codetab %}} + {{% codetab %}} -Save the following in `state-example.php`: +```go -```php -run(function(\Dapr\State\StateManager $stateManager, \Psr\Log\LoggerInterface $logger) { - $stateManager->save_state(store_name: 'statestore', item: new \Dapr\State\StateItem( - key: 'myFirstKey', - value: 'myFirstValue' - )); - $logger->alert('State has been stored'); +) + +//code +func main() { + + STATE_STORE_NAME := "statestore" + + orderId := 100 + + //Using Dapr SDK to save and get state + client, err := dapr.NewClient() + if err != nil { + panic(err) + } + defer client.Close() + ctx := context.Background() + + if err := client.SaveState(ctx, STATE_STORE_NAME, "order_1", []byte(strconv.Itoa(orderId))); err != nil { + panic(err) + } + + result, err := client.GetState(ctx, STATE_STORE_NAME, "order_1") + if err != nil { + panic(err) + } +} - $data = $stateManager->load_state(store_name: 'statestore', key: 'myFirstKey')->value; - $logger->alert("Got value: {data}", ['data' => $data]); -}); ``` -Once saved run the following command to launch a Dapr sidecar and run the PHP application: +Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application: ```bash -dapr --app-id myapp run -- php state-example.php +dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 go run OrderProcessingService.go ``` -You should get an output similar to the following, which will show both the Dapr and app logs: +{{% /codetab %}} -```md -✅ You're up and running! Both Dapr and your app logs will appear here. -== APP == [2021-02-12T16:30:11.078777+01:00] APP.ALERT: State has been stored [] [] +{{% codetab %}} + +```javascript + +//dependencies +import { DaprClient, HttpMethod, CommunicationProtocolEnum } from 'dapr-client'; + +//code +const daprHost = "127.0.0.1"; + +var main = function() { + const STATE_STORE_NAME = "statestore"; + + var orderId = 100; + //Using Dapr SDK to save and get state + const client = new DaprClient(daprHost, process.env.DAPR_HTTP_PORT, CommunicationProtocolEnum.HTTP); + await client.state.save(STATE_STORE_NAME, [ + { + key: "order_1", + value: orderId.toString() + }, + { + key: "order_2", + value: orderId.toString() + } + ]); + var result = await client.state.get(STATE_STORE_NAME, "order_1"); +} + +main(); -== APP == [2021-02-12T16:30:11.082620+01:00] APP.ALERT: Got value: myFirstValue {"data":"myFirstValue"} [] ``` +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 %}} + + +{{% codetab %}} +Begin by launching a Dapr sidecar: + +```bash +dapr run --app-id orderprocessing --dapr-http-port 3601 +``` + +Then in a separate terminal save a key/value pair into your statestore: +```bash +curl -X POST -H "Content-Type: application/json" -d '[{ "key": "order_1", "value": "250"}]' http://localhost:3601/v1.0/state/statestore +``` + +Now get the state you just saved: +```bash +curl http://localhost:3601/v1.0/state/statestore/order_1 +``` + +Restart your sidecar and try retrieving state again to observe that state persists separately from the app. +{{% /codetab %}} + +{{% codetab %}} + +Begin by launching a Dapr sidecar: + +```bash +dapr --app-id orderprocessing --port 3601 run +``` + +Then in a separate terminal save a key/value pair into your statestore: +```powershell +Invoke-RestMethod -Method Post -ContentType 'application/json' -Body '[{"key": "order_1", "value": "250"}]' -Uri 'http://localhost:3601/v1.0/state/statestore' +``` + +Now get the state you just saved: +```powershell +Invoke-RestMethod -Uri 'http://localhost:3601/v1.0/state/statestore/order_1' +``` + +Restart your sidecar and try retrieving state again to observe that state persists separately from the app. + {{% /codetab %}} {{< /tabs >}} @@ -197,243 +322,327 @@ You should get an output similar to the following, which will show both the Dapr ## Step 3: Delete state -The following example shows how to delete an item by using a key with the state management API: +Below are code examples that leverage Dapr SDKs for deleting the state. -{{< tabs "HTTP API (Bash)" "HTTP API (PowerShell)" "Python SDK" "PHP SDK">}} +{{< tabs Dotnet Java Python Go Javascript "HTTP API (Bash)" "HTTP API (PowerShell)">}} {{% codetab %}} -With the same dapr instance running from above run: + +```csharp + +//dependencies +using Dapr.Client; + +//code +namespace EventService +{ + class Program + { + static async Task Main(string[] args) + { + string DAPR_STORE_NAME = "statestore"; + //Using Dapr SDK to delete the state + using var client = new DaprClientBuilder().Build(); + await client.DeleteStateAsync(DAPR_STORE_NAME, "order_1", cancellationToken: cancellationToken); + } + } +} + +``` + +Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application: + ```bash -curl -X DELETE 'http://localhost:3500/v1.0/state/statestore/key1' +dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 dotnet run ``` -Try getting state again and note that no value is returned. + {{% /codetab %}} -{{% codetab %}} -With the same dapr instance running from above run: -```powershell -Invoke-RestMethod -Method Delete -Uri 'http://localhost:3500/v1.0/state/statestore/key1' -``` -Try getting state again and note that no value is returned. -{{% /codetab %}} {{% codetab %}} -Update `pythonState.py` with: +```java + +//dependencies +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; + +//code +@SpringBootApplication +public class OrderProcessingServiceApplication { + public static void main(String[] args) throws InterruptedException{ + String STATE_STORE_NAME = "statestore"; + + //Using Dapr SDK to delete the state + DaprClient client = new DaprClientBuilder().build(); + String storedEtag = client.getState(STATE_STORE_NAME, "order_1", String.class).block().getEtag(); + client.deleteState(STATE_STORE_NAME, "order_1", storedEtag, null).block(); + } +} + +``` + +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 -from dapr.clients import DaprClient -with DaprClient() as d: - d.save_state(store_name="statestore", key="key1", value="value1" ) - print("State has been stored") +#dependencies +from dapr.clients.grpc._request import TransactionalStateOperation, TransactionOperationType - data = d.get_state(store_name="statestore", key="key1").data - print(f"Got value: {data}") +#code +logging.basicConfig(level = logging.INFO) + +DAPR_STORE_NAME = "statestore" - d.delete_state(store_name="statestore", key="key1") +#Using Dapr SDK to delete the state +with DaprClient() as client: + client.delete_state(store_name=DAPR_STORE_NAME, key="order_1") - data = d.get_state(store_name="statestore", key="key1").data - print(f"Got value after delete: {data}") ``` -Now run your program with: +Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application: ```bash -dapr --app-id myapp run python pythonState.py +dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 -- python3 OrderProcessingService.py ``` -You should see an output similar to the following: - -```md -Starting Dapr with id Yakchocolate-Lord. HTTP Port: 59457. gRPC Port: 59458 - -== DAPR == time="2021-01-06T22:55:36.5570696-08:00" level=info msg="starting Dapr Runtime -- version 0.11.3 -- commit a1a8e11" app_id=Yakchocolate-Lord scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T22:55:36.5690367-08:00" level=info msg="standalone mode configured" app_id=Yakchocolate-Lord scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T22:55:36.7220140-08:00" level=info msg="component loaded. name: statestore, type: state.redis" app_id=Yakchocolate-Lord scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T22:55:36.7230148-08:00" level=info msg="API gRPC server is running on port 59458" app_id=Yakchocolate-Lord scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T22:55:36.7240207-08:00" level=info msg="dapr initialized. Status: Running. Init Elapsed 154.984ms" app_id=Yakchocolate-Lord scope=dapr.runtime type=log ver=0.11.3 - -Checking if Dapr sidecar is listening on GRPC port 59458 -Dapr sidecar is up and running. -Updating metadata for app command: python pythonState.py -You're up and running! Both Dapr and your app logs will appear here. - -== APP == State has been stored -== APP == Got value: b'value1' -== APP == Got value after delete: b'' -``` {{% /codetab %}} + {{% codetab %}} -Update `state-example.php` with the following contents: +```go -```php -run(function(\Dapr\State\StateManager $stateManager, \Psr\Log\LoggerInterface $logger) { - $stateManager->save_state(store_name: 'statestore', item: new \Dapr\State\StateItem( - key: 'myFirstKey', - value: 'myFirstValue' - )); - $logger->alert('State has been stored'); +) - $data = $stateManager->load_state(store_name: 'statestore', key: 'myFirstKey')->value; - $logger->alert("Got value: {data}", ['data' => $data]); +//code +func main() { + + STATE_STORE_NAME := "statestore" + + //Using Dapr SDK to delete the state + client, err := dapr.NewClient() + if err != nil { + panic(err) + } + defer client.Close() + ctx := context.Background() + + if err := client.DeleteState(ctx, STATE_STORE_NAME, "order_1"); err != nil { + panic(err) + } +} - $stateManager->delete_keys(store_name: 'statestore', keys: ['myFirstKey']); - $data = $stateManager->load_state(store_name: 'statestore', key: 'myFirstKey')->value; - $logger->alert("Got value after delete: {data}", ['data' => $data]); -}); ``` -Now run it with: +Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application: ```bash -dapr --app-id myapp run -- php state-example.php +dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 go run OrderProcessingService.go ``` -You should see something similar the following output: +{{% /codetab %}} -```md -✅ You're up and running! Both Dapr and your app logs will appear here. -== APP == [2021-02-12T16:38:00.839201+01:00] APP.ALERT: State has been stored [] [] +{{% codetab %}} -== APP == [2021-02-12T16:38:00.841997+01:00] APP.ALERT: Got value: myFirstValue {"data":"myFirstValue"} [] +```javascript + +//dependencies +import { DaprClient, HttpMethod, CommunicationProtocolEnum } from 'dapr-client'; + +//code +const daprHost = "127.0.0.1"; + +var main = function() { + const STATE_STORE_NAME = "statestore"; + + //Using Dapr SDK to save and get state + const client = new DaprClient(daprHost, process.env.DAPR_HTTP_PORT, CommunicationProtocolEnum.HTTP); + await client.state.delete(STATE_STORE_NAME, "order_1"); +} + +main(); -== APP == [2021-02-12T16:38:00.845721+01:00] APP.ALERT: Got value after delete: {"data":null} [] ``` +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 %}} + +{{% codetab %}} +With the same Dapr instance running from above run: +```bash +curl -X DELETE 'http://localhost:3601/v1.0/state/statestore/order_1' +``` +Try getting state again and note that no value is returned. +{{% /codetab %}} + +{{% codetab %}} +With the same Dapr instance running from above run: +```powershell +Invoke-RestMethod -Method Delete -Uri 'http://localhost:3601/v1.0/state/statestore/order_1' +``` +Try getting state again and note that no value is returned. {{% /codetab %}} {{< /tabs >}} ## Step 4: Save and retrieve multiple states -Dapr also allows you to save and retrieve multiple states in the same call. +Below are code examples that leverage Dapr SDKs for saving and retrieving multiple states. -{{< tabs "HTTP API (Bash)" "HTTP API (PowerShell)" "Python SDK" "PHP SDK">}} +{{< tabs Java Python Javascript "HTTP API (Bash)" "HTTP API (PowerShell)">}} {{% codetab %}} -With the same dapr instance running from above save two key/value pairs into your statestore: + +```java + +//dependencies +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; +import io.dapr.client.domain.State; + +//code +@SpringBootApplication +public class OrderProcessingServiceApplication { + + private static final Logger log = LoggerFactory.getLogger(OrderProcessingServiceApplication.class); + + public static void main(String[] args) throws InterruptedException{ + String STATE_STORE_NAME = "statestore"; + + int orderId = 100; + //Using Dapr SDK to retrieve multiple states + DaprClient client = new DaprClientBuilder().build(); + Mono>> resultBulk = client.getBulkState(STATE_STORE_NAME, + Arrays.asList("order_1", "order_2"), String.class); + } +} + +``` + +Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application: + ```bash -curl -X POST -H "Content-Type: application/json" -d '[{ "key": "key1", "value": "value1"}, { "key": "key2", "value": "value2"}]' http://localhost:3500/v1.0/state/statestore -``` - -Now get the states you just saved: -```bash -curl -X POST -H "Content-Type: application/json" -d '{"keys":["key1", "key2"]}' http://localhost:3500/v1.0/state/statestore/bulk -``` -{{% /codetab %}} - -{{% codetab %}} -With the same dapr instance running from above save two key/value pairs into your statestore: -```powershell -Invoke-RestMethod -Method Post -ContentType 'application/json' -Body '[{ "key": "key1", "value": "value1"}, { "key": "key2", "value": "value2"}]' -Uri 'http://localhost:3500/v1.0/state/statestore' -``` - -Now get the states you just saved: -```powershell -Invoke-RestMethod -Method Post -ContentType 'application/json' -Body '{"keys":["key1", "key2"]}' -Uri 'http://localhost:3500/v1.0/state/statestore/bulk' +dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 mvn spring-boot:run ``` {{% /codetab %}} + {{% codetab %}} -The `StateItem` object can be used to store multiple Dapr states with the `save_states` and `get_states` methods. - -Update your `pythonState.py` file with the following code: - ```python + +#dependencies from dapr.clients import DaprClient from dapr.clients.grpc._state import StateItem -with DaprClient() as d: - s1 = StateItem(key="key1", value="value1") - s2 = StateItem(key="key2", value="value2") +#code +logging.basicConfig(level = logging.INFO) + +DAPR_STORE_NAME = "statestore" - d.save_bulk_state(store_name="statestore", states=[s1,s2]) - print("States have been stored") +orderId = 100 +#Using Dapr SDK to save and retrieve multiple states +with DaprClient() as client: + client.save_bulk_state(store_name=DAPR_STORE_NAME, states=[StateItem(key="order_2", value=str(orderId))]) + result = client.get_bulk_state(store_name=DAPR_STORE_NAME, keys=["order_1", "order_2"], states_metadata={"metakey": "metavalue"}).items + logging.info('Result after get bulk: ' + str(result)) - items = d.get_bulk_state(store_name="statestore", keys=["key1", "key2"]).items - print(f"Got items: {[i.data for i in items]}") ``` -Now run your program with: +Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application: ```bash -dapr --app-id myapp run python pythonState.py +dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 -- python3 OrderProcessingService.py ``` -You should see an output similar to the following: +{{% /codetab %}} -```md -== DAPR == time="2021-01-06T21:54:56.7262358-08:00" level=info msg="starting Dapr Runtime -- version 0.11.3 -- commit a1a8e11" app_id=Musesequoia-Sprite scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T21:54:56.7401933-08:00" level=info msg="standalone mode configured" app_id=Musesequoia-Sprite scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T21:54:56.8754240-08:00" level=info msg="Initialized name resolution to standalone" app_id=Musesequoia-Sprite scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T21:54:56.8844248-08:00" level=info msg="component loaded. name: statestore, type: state.redis" app_id=Musesequoia-Sprite scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T21:54:56.8854273-08:00" level=info msg="API gRPC server is running on port 60614" app_id=Musesequoia-Sprite scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T21:54:56.8854273-08:00" level=info msg="dapr initialized. Status: Running. Init Elapsed 145.234ms" app_id=Musesequoia-Sprite scope=dapr.runtime type=log ver=0.11.3 -Checking if Dapr sidecar is listening on GRPC port 60614 -Dapr sidecar is up and running. -Updating metadata for app command: python pythonState.py -You're up and running! Both Dapr and your app logs will appear here. -== APP == States have been stored -== APP == Got items: [b'value1', b'value2'] +{{% codetab %}} + +```javascript + +//dependencies +import { DaprClient, HttpMethod, CommunicationProtocolEnum } from 'dapr-client'; + +//code +const daprHost = "127.0.0.1"; + +var main = function() { + const STATE_STORE_NAME = "statestore"; + + var orderId = 100; + //Using Dapr SDK to save and retrieve multiple states + const client = new DaprClient(daprHost, process.env.DAPR_HTTP_PORT, CommunicationProtocolEnum.HTTP); + await client.state.save(STATE_STORE_NAME, [ + { + key: "order_1", + value: orderId.toString() + }, + { + key: "order_2", + value: orderId.toString() + } + ]); + result = await client.state.getBulk(STATE_STORE_NAME, ["order_1", "order_2"]); +} + +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 %}} {{% codetab %}} - -To batch load and save state with PHP, just create a "Plain Ole' PHP Object" (POPO) and annotate it with -the StateStore annotation. - -Update the `state-example.php` file: - -```php -run(function(\Dapr\State\StateManager $stateManager, \Psr\Log\LoggerInterface $logger) { - $obj = new MyState(); - $stateManager->save_object(item: $obj); - $logger->alert('States have been stored'); - - $stateManager->load_object(into: $obj); - $logger->alert("Got value: {data}", ['data' => $obj]); -}); -``` - -Run the app: - +With the same Dapr instance running from above save two key/value pairs into your statestore: ```bash -dapr --app-id myapp run -- php state-example.php +curl -X POST -H "Content-Type: application/json" -d '[{ "key": "order_1", "value": "250"}, { "key": "order_2", "value": "550"}]' http://localhost:3601/v1.0/state/statestore ``` -And see the following output: +Now get the states you just saved: +```bash +curl -X POST -H "Content-Type: application/json" -d '{"keys":["order_1", "order_2"]}' http://localhost:3601/v1.0/state/statestore/bulk +``` +{{% /codetab %}} -```md -✅ You're up and running! Both Dapr and your app logs will appear here. +{{% codetab %}} +With the same Dapr instance running from above save two key/value pairs into your statestore: +```powershell +Invoke-RestMethod -Method Post -ContentType 'application/json' -Body '[{ "key": "order_1", "value": "250"}, { "key": "order_2", "value": "550"}]' -Uri 'http://localhost:3601/v1.0/state/statestore' +``` -== APP == [2021-02-12T16:55:02.913801+01:00] APP.ALERT: States have been stored [] [] - -== APP == [2021-02-12T16:55:02.917850+01:00] APP.ALERT: Got value: [object MyState] {"data":{"MyState":{"key1":"value1","key2":"value2"}}} [] +Now get the states you just saved: +```powershell +Invoke-RestMethod -Method Post -ContentType 'application/json' -Body '{"keys":["order_1", "order_2"]}' -Uri 'http://localhost:3601/v1.0/state/statestore/bulk' ``` {{% /codetab %}} @@ -446,138 +655,202 @@ And see the following output: State transactions require a state store that supports multi-item transactions. Visit the [supported state stores page]({{< ref supported-state-stores >}}) page for a full list. Note that the default Redis container created in a self-hosted environment supports them. {{% /alert %}} -{{< tabs "HTTP API (Bash)" "HTTP API (PowerShell)" "Python SDK" "PHP SDK">}} +Below are code examples that leverage Dapr SDKs for performing state transactions. + +{{< tabs Dotnet Java Python Javascript "HTTP API (Bash)" "HTTP API (PowerShell)">}} {{% codetab %}} -With the same dapr instance running from above perform two state transactions: + +```csharp + +//dependencies +using Dapr.Client; + +//code +namespace EventService +{ + class Program + { + static async Task Main(string[] args) + { + string DAPR_STORE_NAME = "statestore"; + + int orderId = 100; + //Using Dapr SDK to perform the state transactions + using var client = new DaprClientBuilder().Build(); + var requests = new List() + { + new StateTransactionRequest("order_3", JsonSerializer.SerializeToUtf8Bytes(orderId.ToString()), StateOperationType.Upsert), + new StateTransactionRequest("order_2", null, StateOperationType.Delete) + }; + CancellationTokenSource source = new CancellationTokenSource(); + CancellationToken cancellationToken = source.Token; + await client.ExecuteStateTransactionAsync(DAPR_STORE_NAME, requests, cancellationToken: cancellationToken); + } + } +} + +``` + +Navigate to the directory containing the above code, then run the following command to launch a Dapr sidecar and run the application: + ```bash -curl -X POST -H "Content-Type: application/json" -d '{"operations": [{"operation":"upsert", "request": {"key": "key1", "value": "newValue1"}}, {"operation":"delete", "request": {"key": "key2"}}]}' http://localhost:3500/v1.0/state/statestore/transaction -``` - -Now see the results of your state transactions: -```bash -curl -X POST -H "Content-Type: application/json" -d '{"keys":["key1", "key2"]}' http://localhost:3500/v1.0/state/statestore/bulk -``` -{{% /codetab %}} - -{{% codetab %}} -With the same dapr instance running from above save two key/value pairs into your statestore: -```powershell -Invoke-RestMethod -Method Post -ContentType 'application/json' -Body '{"operations": [{"operation":"upsert", "request": {"key": "key1", "value": "newValue1"}}, {"operation":"delete", "request": {"key": "key2"}}]}' -Uri 'http://localhost:3500/v1.0/state/statestore' -``` - -Now see the results of your state transactions: -```powershell -Invoke-RestMethod -Method Post -ContentType 'application/json' -Body '{"keys":["key1", "key2"]}' -Uri 'http://localhost:3500/v1.0/state/statestore/bulk' +dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 dotnet run ``` {{% /codetab %}} + {{% codetab %}} -The `TransactionalStateOperation` can perform a state transaction if your state stores need to be transactional. +```java -Update your `pythonState.py` file with the following code: +//dependencies +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; +import io.dapr.client.domain.State; +import io.dapr.client.domain.TransactionalStateOperation; + + +//code +@SpringBootApplication +public class OrderProcessingServiceApplication { + + private static final Logger log = LoggerFactory.getLogger(OrderProcessingServiceApplication.class); + + public static void main(String[] args) throws InterruptedException{ + String STATE_STORE_NAME = "statestore"; + + int orderId = 100; + //Using Dapr SDK to perform the state transactions + DaprClient client = new DaprClientBuilder().build(); + List> operationList = new ArrayList<>(); + operationList.add(new TransactionalStateOperation<>(TransactionalStateOperation.OperationType.UPSERT, + new State<>("order_3", Integer.toString(orderId), ""))); + operationList.add(new TransactionalStateOperation<>(TransactionalStateOperation.OperationType.DELETE, + new State<>("order_2"))); + client.executeStateTransaction(STATE_STORE_NAME, operationList).block(); + } +} + +``` + +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 from dapr.clients.grpc._state import StateItem from dapr.clients.grpc._request import TransactionalStateOperation, TransactionOperationType -with DaprClient() as d: - s1 = StateItem(key="key1", value="value1") - s2 = StateItem(key="key2", value="value2") +#code +logging.basicConfig(level = logging.INFO) + +DAPR_STORE_NAME = "statestore" - d.save_bulk_state(store_name="statestore", states=[s1,s2]) - print("States have been stored") +orderId = 100 +#Using Dapr SDK to perform the state transactions +with DaprClient() as client: + client.execute_state_transaction(store_name=DAPR_STORE_NAME, operations=[ + TransactionalStateOperation( + operation_type=TransactionOperationType.upsert, + key="order_3", + data=str(orderId)), + TransactionalStateOperation(key="order_3", data=str(orderId)), + TransactionalStateOperation( + operation_type=TransactionOperationType.delete, + key="order_2", + data=str(orderId)), + TransactionalStateOperation(key="order_2", data=str(orderId)) + ]) - d.execute_state_transaction( - store_name="statestore", - operations=[ - TransactionalStateOperation(key="key1", data="newValue1", operation_type=TransactionOperationType.upsert), - TransactionalStateOperation(key="key2", data="value2", operation_type=TransactionOperationType.delete) - ] - ) - print("State transactions have been completed") - - items = d.get_bulk_state(store_name="statestore", keys=["key1", "key2"]).items - print(f"Got items: {[i.data for i in items]}") ``` -Now run your program with: +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 python pythonState.py +dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 -- python3 OrderProcessingService.py ``` -You should see an output similar to the following: +{{% /codetab %}} -```md -Starting Dapr with id Singerchecker-Player. HTTP Port: 59533. gRPC Port: 59534 -== DAPR == time="2021-01-06T22:18:14.1246721-08:00" level=info msg="starting Dapr Runtime -- version 0.11.3 -- commit a1a8e11" app_id=Singerchecker-Player scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T22:18:14.1346254-08:00" level=info msg="standalone mode configured" app_id=Singerchecker-Player scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T22:18:14.2747063-08:00" level=info msg="component loaded. name: statestore, type: state.redis" app_id=Singerchecker-Player scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T22:18:14.2757062-08:00" level=info msg="API gRPC server is running on port 59534" app_id=Singerchecker-Player scope=dapr.runtime type=log ver=0.11.3 -== DAPR == time="2021-01-06T22:18:14.2767059-08:00" level=info msg="dapr initialized. Status: Running. Init Elapsed 142.0805ms" app_id=Singerchecker-Player scope=dapr.runtime type=log ver=0.11.3 -Checking if Dapr sidecar is listening on GRPC port 59534 -Dapr sidecar is up and running. -Updating metadata for app command: python pythonState.py -You're up and running! Both Dapr and your app logs will appear here. +{{% codetab %}} -== APP == State transactions have been completed -== APP == Got items: [b'value1', b''] +```javascript + +//dependencies +import { DaprClient, HttpMethod, CommunicationProtocolEnum } from 'dapr-client'; + +//code +const daprHost = "127.0.0.1"; + +var main = function() { + const STATE_STORE_NAME = "statestore"; + + var orderId = 100; + //Using Dapr SDK to save and retrieve multiple states + const client = new DaprClient(daprHost, process.env.DAPR_HTTP_PORT, CommunicationProtocolEnum.HTTP); + await client.state.transaction(STATE_STORE_NAME, [ + { + operation: "upsert", + request: { + key: "order_3", + value: orderId.toString() + } + }, + { + operation: "delete", + request: { + key: "order_2" + } + } + ]); +} + +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 %}} {{% codetab %}} - -Transactional state is supported by extending `TransactionalState` base object which hooks into your -object via setters and getters to provide a transaction. Before you created your own transactional object, -but now you'll ask the Dependency Injection framework to build one for you. - -Modify the `state-example.php` file again: - -```php -run(function(MyState $obj, \Psr\Log\LoggerInterface $logger, \Dapr\State\StateManager $stateManager) { - $obj->begin(); - $obj->key1 = 'hello world'; - $obj->key2 = 'value3'; - $obj->commit(); - $logger->alert('Transaction committed!'); - - // begin a new transaction which reloads from the store - $obj->begin(); - $logger->alert("Got value: {key1}, {key2}", ['key1' => $obj->key1, 'key2' => $obj->key2]); -}); -``` - -Run the application: - +With the same Dapr instance running from above perform two state transactions: ```bash -dapr --app-id myapp run -- php state-example.php +curl -X POST -H "Content-Type: application/json" -d '{"operations": [{"operation":"upsert", "request": {"key": "order_1", "value": "250"}}, {"operation":"delete", "request": {"key": "order_2"}}]}' http://localhost:3601/v1.0/state/statestore/transaction ``` -Observe the following output: +Now see the results of your state transactions: +```bash +curl -X POST -H "Content-Type: application/json" -d '{"keys":["order_1", "order_2"]}' http://localhost:3601/v1.0/state/statestore/bulk +``` +{{% /codetab %}} -```md -✅ You're up and running! Both Dapr and your app logs will appear here. +{{% codetab %}} +With the same Dapr instance running from above save two key/value pairs into your statestore: +```powershell +Invoke-RestMethod -Method Post -ContentType 'application/json' -Body '{"operations": [{"operation":"upsert", "request": {"key": "order_1", "value": "250"}}, {"operation":"delete", "request": {"key": "order_2"}}]}' -Uri 'http://localhost:3601/v1.0/state/statestore' +``` -== APP == [2021-02-12T17:10:06.837110+01:00] APP.ALERT: Transaction committed! [] [] - -== APP == [2021-02-12T17:10:06.840857+01:00] APP.ALERT: Got value: hello world, value3 {"key1":"hello world","key2":"value3"} [] +Now see the results of your state transactions: +```powershell +Invoke-RestMethod -Method Post -ContentType 'application/json' -Body '{"keys":["order_1", "order_2"]}' -Uri 'http://localhost:3601/v1.0/state/statestore/bulk' ``` {{% /codetab %}} @@ -588,4 +861,4 @@ Observe the following output: - Read the full [State API reference]({{< ref state_api.md >}}) - Try one of the [Dapr SDKs]({{< ref sdks >}}) -- Build a [stateful service]({{< ref howto-stateful-service.md >}}) +- Build a [stateful service]({{< ref howto-stateful-service.md >}}) \ No newline at end of file diff --git a/daprdocs/content/en/developing-applications/building-blocks/state-management/state-store-ttl.md b/daprdocs/content/en/developing-applications/building-blocks/state-management/state-store-ttl.md index 14f0034f0..b1e349f3a 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/state-management/state-store-ttl.md +++ b/daprdocs/content/en/developing-applications/building-blocks/state-management/state-store-ttl.md @@ -1,7 +1,7 @@ --- type: docs -title: "State Time-to-Live (TTL)" -linkTitle: "State TTL" +title: "How-To: Set state Time-to-Live (TTL)" +linkTitle: "How-To: Set state TTL" weight: 500 description: "Manage state with time-to-live." --- @@ -31,12 +31,38 @@ Please refer to the TTL column in the tables at [state store components]({{< ref State TTL can be set in the metadata as part of the state store set request: -{{< tabs "HTTP API (Bash)" "HTTP API (PowerShell)" "Python SDK" "PHP SDK">}} +{{< tabs Python "HTTP API (Bash)" "HTTP API (PowerShell)">}} + +{{% codetab %}} + +```python +#dependencies + +from dapr.clients import DaprClient + +#code + +DAPR_STORE_NAME = "statestore" + +with DaprClient() as client: + client.save_state(DAPR_STORE_NAME, "order_1", str(orderId), metadata=( + ('ttlInSeconds', '120') + )) + +``` + +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 %}} ```bash -curl -X POST -H "Content-Type: application/json" -d '[{ "key": "key1", "value": "value1", "metadata": { "ttlInSeconds": "120" } }]' http://localhost:3500/v1.0/state/statestore +curl -X POST -H "Content-Type: application/json" -d '[{ "key": "order_1", "value": "250", "metadata": { "ttlInSeconds": "120" } }]' http://localhost:3601/v1.0/state/statestore ``` {{% /codetab %}} @@ -44,48 +70,7 @@ curl -X POST -H "Content-Type: application/json" -d '[{ "key": "key1", "value": {{% codetab %}} ```powershell -Invoke-RestMethod -Method Post -ContentType 'application/json' -Body '[{"key": "key1", "value": "value1", "metadata": {"ttlInSeconds": "120"}}]' -Uri 'http://localhost:3500/v1.0/state/statestore' -``` - -{{% /codetab %}} - -{{% codetab %}} - -```python -from dapr.clients import DaprClient - -with DaprClient() as d: - d.save_state( - store_name="statestore", - key="myFirstKey", - value="myFirstValue", - metadata=( - ('ttlInSeconds', '120') - ) - ) - print("State has been stored") - -``` - -{{% /codetab %}} - -{{% codetab %}} - -Save the following in `state-example.php`: - -```php -run(function(\Dapr\State\StateManager $stateManager, \Psr\Log\LoggerInterface $logger) { - $stateManager->save_state(store_name: 'statestore', item: new \Dapr\State\StateItem( - key: 'myFirstKey', - value: 'myFirstValue', - metadata: ['ttlInSeconds' => '120'] - )); - $logger->alert('State has been stored'); -}); +Invoke-RestMethod -Method Post -ContentType 'application/json' -Body '[{"key": "order_1", "value": "250", "metadata": {"ttlInSeconds": "120"}}]' -Uri 'http://localhost:3601/v1.0/state/statestore' ``` {{% /codetab %}} @@ -98,4 +83,4 @@ See [this guide]({{< ref state_api.md >}}) for a reference on the state API. - Learn [how to use key value pairs to persist a state]({{< ref howto-get-save-state.md >}}) - List of [state store components]({{< ref supported-state-stores >}}) -- Read the [API reference]({{< ref state_api.md >}}) +- Read the [API reference]({{< ref state_api.md >}}) \ No newline at end of file diff --git a/daprdocs/content/en/operations/configuration/configuration-overview.md b/daprdocs/content/en/operations/configuration/configuration-overview.md index a9b941a30..55f5742a1 100644 --- a/daprdocs/content/en/operations/configuration/configuration-overview.md +++ b/daprdocs/content/en/operations/configuration/configuration-overview.md @@ -16,7 +16,7 @@ In self hosted mode the Dapr configuration is a configuration file, for example A Dapr sidecar can also apply a configuration by using a ```--config``` flag to the file path with ```dapr run``` CLI command. #### Kubernetes sidecar -In Kubernetes mode the Dapr configuration is a Configuration CRD, that is applied to the cluster. For example; +In Kubernetes mode the Dapr configuration is a Configuration CRD, that is applied to the cluster. For example: ```bash kubectl apply -f myappconfig.yaml @@ -41,7 +41,7 @@ Note: There are more [Kubernetes annotations]({{< ref "arguments-annotations-ove ### Sidecar configuration settings -The following configuration settings can be applied to Dapr application sidecars; +The following configuration settings can be applied to Dapr application sidecars: - [Tracing](#tracing) - [Metrics](#metrics) - [Middleware](#middleware) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/cosmosdb.md b/daprdocs/content/en/reference/components-reference/supported-bindings/cosmosdb.md index 163fe8d3d..57d71e8b9 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/cosmosdb.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/cosmosdb.md @@ -46,7 +46,7 @@ The above example uses secrets as plain strings. It is recommended to use a secr | masterKey | Y | Output | The CosmosDB account master key | `"master-key"` | | database | Y | Output | The name of the CosmosDB database | `"OrderDb"` | | collection | Y | Output | The name of the container inside the database. | `"Orders"` | -| partitionKey | Y | Output | The name of the partitionKey to extract from the payload and is used in the container | `"OrderId"`, `"message"` | +| partitionKey | Y | Output | The name of the key to extract from the payload (document to be created) that is used as the partition key. This name must match the partition key specified upon creation of the Cosmos DB container. | `"OrderId"`, `"message"` | For more information see [Azure Cosmos DB resource model](https://docs.microsoft.com/azure/cosmos-db/account-databases-containers-items). @@ -56,6 +56,31 @@ This component supports **output binding** with the following operations: - `create` +## Best Practices for Production Use + +Azure Cosmos DB shares a strict metadata request rate limit across all databases in a single Azure Cosmos DB account. New connections to Azure Cosmos DB assume a large percentage of the allowable request rate limit. (See the [CosmosDB documentation](https://docs.microsoft.com/azure/cosmos-db/sql/troubleshoot-request-rate-too-large#recommended-solution-3)) + +Therefore several strategies must be applied to avoid simultaneous new connections to Azure Cosmos DB: + +- Ensure sidecars of applications only load the Azure Cosmos DB component when they require it to avoid unnecessary database connections. This can be done by [scoping your components to specific applications]({{< ref component-scopes.md >}}#application-access-to-components-with-scopes). +- Choose deployment strategies that sequentially deploy or start your applications to minimize bursts in new connections to your Azure Cosmos DB accounts. +- Avoid reusing the same Azure Cosmos DB account for unrelated databases or systems (even outside of Dapr). Distinct Azure Cosmos DB accounts have distinct rate limits. +- Increase the `initTimeout` value to allow the component to retry connecting to Azure Cosmos DB during side car initialization for up to 5 minutes. The default value is `5s` and should be increased. When using Kubernetes, increasing this value may also require an update to your [Readiness and Liveness probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/). + +```yaml +spec: + type: bindings.azure.cosmosdb + version: v1 + initTimeout: 5m + metadata: +``` + +## Data format + +The **output binding** `create` operation requires the following keys to exist in the payload of every document to be created: +- `id`: a unique ID for the document to be created +- ``: the name of the partition key specified via the `spec.partitionKey` in the component definition. This must also match the partition key specified upon creation of the Cosmos DB container. + ## Related links - [Basic schema for a Dapr component]({{< ref component-schema >}}) diff --git a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-jetstream.md b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-jetstream.md index 61e4711cb..791a2f02b 100644 --- a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-jetstream.md +++ b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-jetstream.md @@ -86,7 +86,7 @@ with NATS, find the service with: `kubectl get svc my-nats`. - [Basic schema for a Dapr component]({{< ref component-schema >}}) - Read [this guide]({{< ref "howto-publish-subscribe.md#step-2-publish-a-topic" >}}) for instructions on configuring pub/sub components - [Pub/Sub building block]({{< ref pubsub >}}) -- [JetStream Documentation](https://docs.nats.io/jetstream/jetstream) +- [JetStream Documentation](https://docs.nats.io/nats-concepts/jetstream) - [NATS CLI](https://github.com/nats-io/natscli) diff --git a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-nats-streaming.md b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-nats-streaming.md index 38c281b4f..e9f7f4079 100644 --- a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-nats-streaming.md +++ b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-nats-streaming.md @@ -92,7 +92,7 @@ You can then interact with the server using the client port: `localhost:4222`. {{% /codetab %}} {{% codetab %}} -Install NATS on Kubernetes by using the [kubectl](https://docs.nats.io/nats-on-kubernetes/minimal-setup): +Install NATS on Kubernetes by using the [kubectl](https://docs.nats.io/running-a-nats-service/introduction/running/nats-kubernetes/minimal-setup#minimal-nats-setup): ```bash # Single server NATS diff --git a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-azure-cosmosdb.md b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-azure-cosmosdb.md index db53ee78b..f1d3c3450 100644 --- a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-azure-cosmosdb.md +++ b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-azure-cosmosdb.md @@ -64,6 +64,25 @@ In order to setup CosmosDB as a state store, you need the following properties: - **Database**: The name of the database - **Collection**: The name of the collection +## Best Practices for Production Use + +Azure Cosmos DB shares a strict metadata request rate limit across all databases in a single Azure Cosmos DB account. New connections to Azure Cosmos DB assume a large percentage of the allowable request rate limit. (See the [CosmosDB documentation](https://docs.microsoft.com/azure/cosmos-db/sql/troubleshoot-request-rate-too-large#recommended-solution-3)) + +Therefore several strategies must be applied to avoid simultaneous new connections to Azure Cosmos DB: + +- Ensure sidecars of applications only load the Azure Cosmos DB component when they require it to avoid unnecessary database connections. This can be done by [scoping your components to specific applications]({{< ref component-scopes.md >}}#application-access-to-components-with-scopes). +- Choose deployment strategies that sequentially deploy or start your applications to minimize bursts in new connections to your Azure Cosmos DB accounts. +- Avoid reusing the same Azure Cosmos DB account for unrelated databases or systems (even outside of Dapr). Distinct Azure Cosmos DB accounts have distinct rate limits. +- Increase the `initTimeout` value to allow the component to retry connecting to Azure Cosmos DB during side car initialization for up to 5 minutes. The default value is `5s` and should be increased. When using Kubernetes, increasing this value may also require an update to your [Readiness and Liveness probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/). + +```yaml +spec: + type: state.azure.cosmosdb + version: v1 + initTimeout: 5m + metadata: +``` + ## Data format To use the CosmosDB state store, your data must be sent to Dapr in JSON-serialized. Having it just JSON *serializable* will not work. diff --git a/daprdocs/layouts/partials/navbar.html b/daprdocs/layouts/partials/navbar.html index e1dc682d4..5a2bc8fd6 100644 --- a/daprdocs/layouts/partials/navbar.html +++ b/daprdocs/layouts/partials/navbar.html @@ -14,7 +14,7 @@ {{ end }} {{ $url := urls.Parse .URL }} {{ $baseurl := urls.Parse $.Site.Params.Baseurl }} - {{ .Name }} + {{ .Name }} {{ end }} {{ if .Site.Params.versions }} diff --git a/daprdocs/layouts/partials/page-meta-links.html b/daprdocs/layouts/partials/page-meta-links.html index bd55efdf3..6bf0d95a1 100644 --- a/daprdocs/layouts/partials/page-meta-links.html +++ b/daprdocs/layouts/partials/page-meta-links.html @@ -21,8 +21,8 @@ {{ $newPageQS := querify "value" $newPageStub.Content "filename" "change-me.md" | safeURL }} {{ $newPageURL := printf "%s/new/%s?%s" $gh_repo $gh_repo_path $newPageQS }} - {{ T "post_edit_this" }} - {{ T "post_create_issue" }} + {{ T "post_edit_this" }} + {{ T "post_create_issue" }} {{ end }} {{ end }} diff --git a/daprdocs/layouts/partials/version-banner.html b/daprdocs/layouts/partials/version-banner.html index 0436da130..09b705b07 100644 --- a/daprdocs/layouts/partials/version-banner.html +++ b/daprdocs/layouts/partials/version-banner.html @@ -8,7 +8,7 @@ {{ with $current_version }}

The documentation you are viewing is for Dapr {{ . | markdownify }} which is an older version of Dapr. {{ with $latest_version }}For up-to-date documentation, see the - latest version.

+ latest version.

{{ end }} {{ end }} diff --git a/daprdocs/static/images/building-block-secrets-management-example.png b/daprdocs/static/images/building-block-secrets-management-example.png new file mode 100644 index 000000000..77c450d81 Binary files /dev/null and b/daprdocs/static/images/building-block-secrets-management-example.png differ diff --git a/daprdocs/static/images/service_invocation_eg.png b/daprdocs/static/images/building-block-service-invocation-example.png similarity index 100% rename from daprdocs/static/images/service_invocation_eg.png rename to daprdocs/static/images/building-block-service-invocation-example.png diff --git a/daprdocs/static/images/building-block-state-management-example.png b/daprdocs/static/images/building-block-state-management-example.png new file mode 100644 index 000000000..87b3b9b32 Binary files /dev/null and b/daprdocs/static/images/building-block-state-management-example.png differ