mirror of https://github.com/dapr/docs.git
[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:
parent
e8bf566e7c
commit
d8dabf4309
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue