[Pluggable components] Error handling documentation (#3067)

* initial draft of error handling table

Signed-off-by: Hannah Hunter <hannahhunter@microsoft.com>

* add go examples

Signed-off-by: Hannah Hunter <hannahhunter@microsoft.com>

* remove code brackets

Signed-off-by: Hannah Hunter <hannahhunter@microsoft.com>

* typo

Signed-off-by: Hannah Hunter <hannahhunter@microsoft.com>

* Update daprdocs/content/en/developing-applications/develop-components/pluggable-components/develop-pluggable.md

Co-authored-by: Marcos Candeia <marrcooos@gmail.com>
Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com>

* add .net code examples, annotations example, marcos review

Signed-off-by: Hannah Hunter <hannahhunter@microsoft.com>

* add note about nuget package

Signed-off-by: Hannah Hunter <hannahhunter@microsoft.com>

* updates from tiago and marcos

Signed-off-by: Hannah Hunter <hannahhunter@microsoft.com>

* remove paragraph

Signed-off-by: Hannah Hunter <hannahhunter@microsoft.com>

* make more clear that pluggable components are in same pod

Signed-off-by: Hannah Hunter <hannahhunter@microsoft.com>

* Update daprdocs/content/en/operations/components/pluggable-components-registration.md

Co-authored-by: Marcos Candeia <marrcooos@gmail.com>
Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com>

* editing other example per marcos

Signed-off-by: Hannah Hunter <hannahhunter@microsoft.com>

---------

Signed-off-by: Hannah Hunter <hannahhunter@microsoft.com>
Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com>
Co-authored-by: Marcos Candeia <marrcooos@gmail.com>
This commit is contained in:
Hannah Hunter 2023-02-03 14:58:33 -06:00 committed by GitHub
parent e8bf566e7c
commit d8dabf4309
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 236 additions and 7 deletions

View File

@ -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" >}}
<!-- .NET -->
{{% 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 %}}
<!-- Java -->
{{% 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 %}}
<!-- Go -->
{{% 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)

View File

@ -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