diff --git a/daprdocs/content/en/developing-applications/develop-components/pluggable-components/develop-pluggable.md b/daprdocs/content/en/developing-applications/develop-components/pluggable-components/develop-pluggable.md index e3f92b1e5..d25ff95d7 100644 --- a/daprdocs/content/en/developing-applications/develop-components/pluggable-components/develop-pluggable.md +++ b/daprdocs/content/en/developing-applications/develop-components/pluggable-components/develop-pluggable.md @@ -1,7 +1,7 @@ --- type: docs title: "How to: Implement pluggable components" -linkTitle: "Pluggable components" +linkTitle: "Implement pluggable components" weight: 1100 description: "Learn how to author and implement pluggable components" --- @@ -105,6 +105,201 @@ After generating the above state store example's service scaffolding code using This concrete implementation and auxiliary code are the **core** of your pluggable component. They define how your component behaves when handling gRPC requests from Dapr. +## Returning semantic errors + +Returning semantic errors are also part of the pluggable component protocol. The component must return specific gRPC codes that have semantic meaning for the user application, those errors are used to a variety of situations from concurrency requirements to informational only. + +| Error | gRPC error code | Source component | Description | +| ------------------------ | ------------------------------- | ---------------- | ----------- | +| ETag Mismatch | `codes.FailedPrecondition` | State store | Error mapping to meet concurrency requirements | +| ETag Invalid | `codes.InvalidArgument` | State store | | +| Bulk Delete Row Mismatch | `codes.Internal` | State store | | + +Learn more about concurrency requirements in the [State Management overview]({{< ref "state-management-overview.md#concurrency" >}}). + +The following examples demonstrate how to return an error in your own pluggable component, changing the messages to suit your needs. + +{{< tabs ".NET" "Java" "Go" >}} + +{{% codetab %}} + +> **Important:** In order to use .NET for error mapping, first install the [`Google.Api.CommonProtos` NuGet package](https://www.nuget.org/packages/Google.Api.CommonProtos/). + +**Etag Mismatch** + +```csharp +var badRequest = new BadRequest(); +var des = "The ETag field provided does not match the one in the store"; +badRequest.FieldViolations.Add(    + new Google.Rpc.BadRequest.Types.FieldViolation +    {        + Field = "etag", + Description = des +    }); + +var baseStatusCode = Grpc.Core.StatusCode.FailedPrecondition; +var status = new Google.Rpc.Status{    + Code = (int)baseStatusCode +}; + +status.Details.Add(Google.Protobuf.WellKnownTypes.Any.Pack(badRequest)); + +var metadata = new Metadata(); +metadata.Add("grpc-status-details-bin", status.ToByteArray()); +throw new RpcException(new Grpc.Core.Status(baseStatusCode, "fake-err-msg"), metadata); +``` + +**Etag Invalid** + +```csharp +var badRequest = new BadRequest(); +var des = "The ETag field must only contain alphanumeric characters"; +badRequest.FieldViolations.Add( + new Google.Rpc.BadRequest.Types.FieldViolation + { + Field = "etag", + Description = des + }); + +var baseStatusCode = Grpc.Core.StatusCode.InvalidArgument; +var status = new Google.Rpc.Status +{ + Code = (int)baseStatusCode +}; + +status.Details.Add(Google.Protobuf.WellKnownTypes.Any.Pack(badRequest)); + +var metadata = new Metadata(); +metadata.Add("grpc-status-details-bin", status.ToByteArray()); +throw new RpcException(new Grpc.Core.Status(baseStatusCode, "fake-err-msg"), metadata); +``` + +**Bulk Delete Row Mismatch** + +```csharp +var errorInfo = new Google.Rpc.ErrorInfo(); + +errorInfo.Metadata.Add("expected", "100"); +errorInfo.Metadata.Add("affected", "99"); + +var baseStatusCode = Grpc.Core.StatusCode.Internal; +var status = new Google.Rpc.Status{ +    Code = (int)baseStatusCode +}; + +status.Details.Add(Google.Protobuf.WellKnownTypes.Any.Pack(errorInfo)); + +var metadata = new Metadata(); +metadata.Add("grpc-status-details-bin", status.ToByteArray()); +throw new RpcException(new Grpc.Core.Status(baseStatusCode, "fake-err-msg"), metadata); +``` + +{{% /codetab %}} + + +{{% codetab %}} + +Just like the [Dapr Java SDK](https://github.com/tmacam/dapr-java-sdk/), the Java Pluggable Components SDK uses [Project Reactor](https://projectreactor.io/), which provides an asynchronous API for Java. + +Errors can be returned directly by: +1. Calling the `.error()` method in the `Mono` or `Flux` that your method returns +1. Providing the appropriate exception as parameter. + +You can also raise an exception, as long as it is captured and fed back to your resulting `Mono` or `Flux`. + +**ETag Mismatch** + +```java +final Status status = Status.newBuilder() + .setCode(io.grpc.Status.Code.FAILED_PRECONDITION.value()) + .setMessage("fake-err-msg-for-etag-mismatch") + .addDetails(Any.pack(BadRequest.FieldViolation.newBuilder() + .setField("etag") + .setDescription("The ETag field provided does not match the one in the store") + .build())) + .build(); +return Mono.error(StatusProto.toStatusException(status)); +``` + +**ETag Invalid** + +```java +final Status status = Status.newBuilder() + .setCode(io.grpc.Status.Code.INVALID_ARGUMENT.value()) + .setMessage("fake-err-msg-for-invalid-etag") + .addDetails(Any.pack(BadRequest.FieldViolation.newBuilder() + .setField("etag") + .setDescription("The ETag field must only contain alphanumeric characters") + .build())) + .build(); +return Mono.error(StatusProto.toStatusException(status)); +``` + +**Bulk Delete Row Mismatch** + +```java +final Status status = Status.newBuilder() + .setCode(io.grpc.Status.Code.INTERNAL.value()) + .setMessage("fake-err-msg-for-bulk-delete-row-mismatch") + .addDetails(Any.pack(ErrorInfo.newBuilder() + .putAllMetadata(Map.ofEntries( + Map.entry("affected", "99"), + Map.entry("expected", "100") + )) + .build())) + .build(); +return Mono.error(StatusProto.toStatusException(status)); +``` + +{{% /codetab %}} + + +{{% codetab %}} + +**ETag Mismatch** + +```go +st := status.New(codes.FailedPrecondition, "fake-err-msg") +desc := "The ETag field provided does not match the one in the store" +v := &errdetails.BadRequest_FieldViolation{ + Field: etagField, + Description: desc, +} +br := &errdetails.BadRequest{} +br.FieldViolations = append(br.FieldViolations, v) +st, err := st.WithDetails(br) +``` + +**ETag Invalid** + +```go +st := status.New(codes.InvalidArgument, "fake-err-msg") +desc := "The ETag field must only contain alphanumeric characters" +v := &errdetails.BadRequest_FieldViolation{ + Field: etagField, + Description: desc, +} +br := &errdetails.BadRequest{} +br.FieldViolations = append(br.FieldViolations, v) +st, err := st.WithDetails(br) +``` + +**Bulk Delete Row Mismatch** + +```go +st := status.New(codes.Internal, "fake-err-msg") +br := &errdetails.ErrorInfo{} +br.Metadata = map[string]string{ + affected: "99", + expected: "100", +} +st, err := st.WithDetails(br) +``` + +{{% /codetab %}} + +{{< /tabs >}} + ## Next steps - Get started with developing .NET pluggable component using this [sample code](https://github.com/dapr/samples/tree/master/pluggable-components-dotnet-template) diff --git a/daprdocs/content/en/operations/components/pluggable-components-registration.md b/daprdocs/content/en/operations/components/pluggable-components-registration.md index 06a4b6a2e..15e7fdbc8 100644 --- a/daprdocs/content/en/operations/components/pluggable-components-registration.md +++ b/daprdocs/content/en/operations/components/pluggable-components-registration.md @@ -132,7 +132,7 @@ Follow the steps provided in the [Deploy Dapr on a Kubernetes cluster]({{< ref k ## Add the pluggable component container in your deployments -When running in Kubernetes mode, pluggable components are deployed as containers in the same pod as your application. +Pluggable components are deployed as containers **in the same pod** as your application. Since pluggable components are backed by [Unix Domain Sockets][uds], make the socket created by your pluggable component accessible by Dapr runtime. Configure the deployment spec to: @@ -140,7 +140,7 @@ Since pluggable components are backed by [Unix Domain Sockets][uds], make the so 2. Hint to Dapr the mounted Unix socket volume location 3. Attach volume to your pluggable component container -Below is an example of a deployment that configures a pluggable component: +In the following example, your configured pluggable component is deployed as a container within the same pod as your application container. ```yaml apiVersion: apps/v1 @@ -167,17 +167,51 @@ spec: - name: dapr-unix-domain-socket emptyDir: {} containers: - ### --------------------- YOUR APPLICATION CONTAINER GOES HERE ----------- - ## - ### --------------------- YOUR APPLICATION CONTAINER GOES HERE ----------- - ### This is the pluggable component container. + containers: + ### --------------------- YOUR APPLICATION CONTAINER GOES HERE ----------- + - name: app + image: YOUR_APP_IMAGE:YOUR_APP_IMAGE_VERSION + ### --------------------- YOUR PLUGGABLE COMPONENT CONTAINER GOES HERE ----------- - name: component + image: YOUR_IMAGE_GOES_HERE:YOUR_IMAGE_VERSION volumeMounts: # required, the sockets volume mount - name: dapr-unix-domain-socket mountPath: /tmp/dapr-components-sockets image: YOUR_IMAGE_GOES_HERE:YOUR_IMAGE_VERSION ``` +Alternatively, you can annotate your pods, telling Dapr which containers within that pod are pluggable components, like in the example below: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: app + labels: + app: app +spec: + replicas: 1 + selector: + matchLabels: + app: app + template: + metadata: + labels: + app: app + annotations: + dapr.io/pluggable-components: "component" ## the name of the pluggable component container separated by `,`, e.g "componentA,componentB". + dapr.io/app-id: "my-app" + dapr.io/enabled: "true" + spec: + containers: + ### --------------------- YOUR APPLICATION CONTAINER GOES HERE ----------- + - name: app + image: YOUR_APP_IMAGE:YOUR_APP_IMAGE_VERSION + ### --------------------- YOUR PLUGGABLE COMPONENT CONTAINER GOES HERE ----------- + - name: component + image: YOUR_IMAGE_GOES_HERE:YOUR_IMAGE_VERSION +``` + Before applying the deployment, let's add one more configuration: the component spec. ## Define a component