Sample/pluggable components (#120)

This commit is contained in:
Marcos Candeia 2022-10-12 02:30:29 -03:00 committed by GitHub
parent adecca8e8f
commit 78eed72a81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 792 additions and 0 deletions

View File

@ -0,0 +1,2 @@
bin
obj

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<Protobuf Include="Protos\dapr\proto\components\v1\bindings.proto" ProtoRoot="Protos" GrpcServices="Client,Server" />
<Protobuf Include="Protos\dapr\proto\components\v1\pubsub.proto" ProtoRoot="Protos" GrpcServices="Client,Server" />
<Protobuf Include="Protos\dapr\proto\components\v1\state.proto" ProtoRoot="Protos" GrpcServices="Client,Server" />
<Protobuf Include="Protos\dapr\proto\components\v1\common.proto" ProtoRoot="Protos" GrpcServices="Client" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.40.0" />
<PackageReference Include="Grpc.AspNetCore.Server.Reflection" Version="2.49.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,56 @@
// ------------------------------------------------------------------------
// Copyright 2022 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
var componentName = "my-component"; // replace by your component name
// default directory for components
var socketDir = "/tmp/dapr-components-sockets";
if (!Directory.Exists(socketDir)) // creating directory if it not exists
{
Directory.CreateDirectory(socketDir);
}
var socket = $"{socketDir}/{componentName}.sock";
if (File.Exists(socket)) // deleting socket in case of it already exists
{
Console.WriteLine("Removing existing socket");
File.Delete(socket);
}
var builder = WebApplication.CreateBuilder(args);
// Additional configuration is required to successfully run gRPC on macOS.
// For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
// Add services to the container.
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenUnixSocket(socket);
});
builder.Services.AddGrpc();
// gRPC refletion is required for service discovery, do not remove it.
builder.Services.AddGrpcReflection();
var app = builder.Build();
// Configure the HTTP request pipeline.
// app.MapGrpcService<StateStoreService>(); // Uncomment to register the StateStoreService
// app.MapGrpcService<PubSubService>(); // Uncomment to register the PubSubService
// app.MapGrpcService<InputBindingService>(); // Uncomment to register the InputBindingService
// app.MapGrpcService<OutputBindingService>(); // Uncomment to register the OutputBindingService
// gRPC refletion is required for service discovery, do not remove it.
app.MapGrpcReflectionService();
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
app.Run();

View File

@ -0,0 +1,13 @@
{
"profiles": {
"DaprMemStoreComponent": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5259;https://localhost:7089",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,122 @@
/*
Copyright 2022 The Dapr Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
syntax = "proto3";
package dapr.proto.components.v1;
import "dapr/proto/components/v1/common.proto";
option go_package = "github.com/dapr/dapr/pkg/proto/components/v1;components";
// Interface for input bindings
service InputBinding {
// Initializes the inputbinding component component with the given metadata.
rpc Init(InputBindingInitRequest) returns (InputBindingInitResponse) {}
// Establishes a stream with the server, which sends messages down to the
// client. The client streams acknowledgements back to the server. The server
// will close the stream and return the status on any error. In case of closed
// connection, the client should re-establish the stream.
rpc Read(stream ReadRequest) returns (stream ReadResponse) {}
// Ping the InputBinding. Used for liveness porpuses.
rpc Ping(PingRequest) returns (PingResponse) {}
}
service OutputBinding {
// Initializes the outputbinding component component with the given metadata.
rpc Init(OutputBindingInitRequest) returns (OutputBindingInitResponse) {}
// Invoke remote systems with optional payloads.
rpc Invoke(InvokeRequest) returns (InvokeResponse) {}
// ListOperations list system supported operations.
rpc ListOperations(ListOperationsRequest) returns (ListOperationsResponse) {}
// Ping the OutputBinding. Used for liveness porpuses.
rpc Ping(PingRequest) returns (PingResponse) {}
}
// reserved for future-proof extensibility
message ListOperationsRequest {}
message ListOperationsResponse {
// the list of all supported component operations.
repeated string operations = 1;
}
// InputBindingInitRequest is the request for initializing the input binding
// component.
message InputBindingInitRequest {
// The metadata request.
MetadataRequest metadata = 1;
}
// reserved for future-proof extensibility
message InputBindingInitResponse {}
// OutputBindingInitRequest is the request for initializing the output binding
// component.
message OutputBindingInitRequest {
// The metadata request.
MetadataRequest metadata = 1;
}
// reserved for future-proof extensibility
message OutputBindingInitResponse {}
// Used for describing errors when ack'ing messages.
message AckResponseError {
string message = 1;
}
message ReadRequest {
// The handle response.
bytes response_data = 1;
// The unique message ID.
string message_id = 2;
// Optional, should not be fulfilled when the message was successfully
// handled.
AckResponseError response_error = 3;
}
message ReadResponse {
// The Read binding Data.
bytes data = 1;
// The message metadata
map<string, string> metadata = 2;
// The message content type.
string content_type = 3;
// The {transient} message ID used for ACK-ing it later.
string message_id = 4;
}
// Used for invoking systems with optional payload.
message InvokeRequest {
// The invoke payload.
bytes data = 1;
// The invoke metadata.
map<string, string> metadata = 2;
// The system supported operation.
string operation = 3;
}
// Response from the invoked system.
message InvokeResponse {
// The response payload.
bytes data = 1;
// The response metadata.
map<string, string> metadata = 2;
// The response content-type.
string content_type = 3;
}

View File

@ -0,0 +1,39 @@
/*
Copyright 2022 The Dapr Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
syntax = "proto3";
package dapr.proto.components.v1;
option csharp_namespace = "Dapr.Client.Autogen.Grpc.v1";
option java_outer_classname = "ComponentProtos";
option java_package = "io.dapr.v1";
option go_package = "github.com/dapr/dapr/pkg/proto/components/v1;components";
// Base metadata request for all components
message MetadataRequest {
map<string, string> properties = 1;
}
// reserved for future-proof extensibility
message FeaturesRequest {}
message FeaturesResponse {
repeated string features = 1;
}
// reserved for future-proof extensibility
message PingRequest {}
// reserved for future-proof extensibility
message PingResponse {}

View File

@ -0,0 +1,106 @@
/*
Copyright 2022 The Dapr Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
syntax = "proto3";
package dapr.proto.components.v1;
import "dapr/proto/components/v1/common.proto";
option go_package = "github.com/dapr/dapr/pkg/proto/components/v1;components";
// PubSub service provides a gRPC interface for pubsub components.
service PubSub {
// Initializes the pubsub component with the given metadata.
rpc Init(PubSubInitRequest) returns (PubSubInitResponse) {}
// Returns a list of implemented pubsub features.
rpc Features(FeaturesRequest) returns (FeaturesResponse) {}
// Publish publishes a new message for the given topic.
rpc Publish(PublishRequest) returns (PublishResponse) {}
// Establishes a stream with the server (PubSub component), which sends
// messages down to the client (daprd). The client streams acknowledgements
// back to the server. The server will close the stream and return the status
// on any error. In case of closed connection, the client should re-establish
// the stream. The first message MUST contain a `topic` attribute on it that
// should be used for the entire streaming pull.
rpc PullMessages(stream PullMessagesRequest)
returns (stream PullMessagesResponse) {}
// Ping the pubsub. Used for liveness porpuses.
rpc Ping(PingRequest) returns (PingResponse) {}
}
// Used for describing errors when ack'ing messages.
message AckMessageError {
string message = 1;
}
// Used for acknowledge a message.
message PullMessagesRequest {
// Required. The subscribed topic for which to initialize the new stream. This
// must be provided in the first request on the stream, and must not be set in
// subsequent requests from client to server.
Topic topic = 1;
// The unique message ID.
string ack_message_id = 2;
// Optional, should not be fulfilled when the message was successfully
// handled.
AckMessageError ack_error = 3;
}
// PubSubInitRequest is the request for initializing the pubsub component.
message PubSubInitRequest {
// The metadata request.
MetadataRequest metadata = 1;
}
// reserved for future-proof extensibility
message PubSubInitResponse {}
message PublishRequest {
bytes data = 1;
// The pubsub name.
string pubsub_name = 2;
// The publishing topic.
string topic = 3;
// Message metadata.
map<string, string> metadata = 4;
// The data content type.
string content_type = 5;
}
// reserved for future-proof extensibility
message PublishResponse {}
message Topic {
// The topic name desired to be subscribed
string name = 1;
// Metadata related subscribe request.
map<string, string> metadata = 2;
}
message PullMessagesResponse {
// The message content.
bytes data = 1;
// The topic where the message come from.
string topic_name = 2;
// The message related metadata.
map<string, string> metadata = 3;
// The message content type.
string content_type = 4;
// The message {transient} ID. Its used for ack'ing it later.
string id = 5;
}

View File

@ -0,0 +1,275 @@
/*
Copyright 2022 The Dapr Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
syntax = "proto3";
package dapr.proto.components.v1;
import "google/protobuf/any.proto";
import "dapr/proto/components/v1/common.proto";
option go_package = "github.com/dapr/dapr/pkg/proto/components/v1;components";
// QueriableStateStore service provides a gRPC interface for querier state store
// components. It was designed to embed query features to the StateStore Service
// as a complementary service.
service QueriableStateStore {
// Query performs a query request on the statestore.
rpc Query(QueryRequest) returns (QueryResponse) {}
}
message Sorting {
// The key that should be used for sorting.
string key = 1;
enum Order {
ASC = 0;
DESC = 1;
}
// The order that should be used.
Order order = 2;
}
message Pagination {
// Maximum of results that should be returned.
int64 limit = 1;
// The pagination token.
string token = 2;
}
message Query {
// Filters that should be applied.
map<string, google.protobuf.Any> filter = 1;
// The sort order.
repeated Sorting sort = 2;
// The query pagination params.
Pagination pagination = 3;
}
// QueryRequest is for querying state store.
message QueryRequest {
// The query to be performed.
Query query = 1;
// Request associated metadata.
map<string, string> metadata = 2;
}
// QueryItem is an object representing a single entry in query results.
message QueryItem {
// The returned item Key.
string key = 1;
// The returned item Data.
bytes data = 2;
// The returned item ETag
Etag etag = 3;
// The returned error string.
string error = 4;
// The returned contenttype
string content_type = 5;
}
// QueryResponse is the query response.
message QueryResponse {
// The query response items.
repeated QueryItem items = 1;
// The response token.
string token = 2;
// Response associated metadata.
map<string, string> metadata = 3;
}
// TransactionalStateStore service provides a gRPC interface for transactional
// state store components. It was designed to embed transactional features to
// the StateStore Service as a complementary service.
service TransactionalStateStore {
// Transact executes multiples operation in a transactional environment.
rpc Transact(TransactionalStateRequest) returns (TransactionalStateResponse) {
}
}
// TransactionalStateOperation describes operation type, key, and value for
// transactional operation.
message TransactionalStateOperation {
// request is either delete or set.
oneof request {
DeleteRequest delete = 1;
SetRequest set = 2;
}
}
// TransactionalStateRequest describes a transactional operation against a state
// store that comprises multiple types of operations The Request field is either
// a DeleteRequest or SetRequest.
message TransactionalStateRequest {
// Operations that should be performed.
repeated TransactionalStateOperation operations = 1;
// Request associated metadata.
map<string, string> metadata = 2;
}
// reserved for future-proof extensibility
message TransactionalStateResponse {}
// StateStore service provides a gRPC interface for state store components.
service StateStore {
// Initializes the state store component with the given metadata.
rpc Init(InitRequest) returns (InitResponse) {}
// Returns a list of implemented state store features.
rpc Features(FeaturesRequest) returns (FeaturesResponse) {}
// Deletes the specified key from the state store.
rpc Delete(DeleteRequest) returns (DeleteResponse) {}
// Get data from the given key.
rpc Get(GetRequest) returns (GetResponse) {}
// Sets the value of the specified key.
rpc Set(SetRequest) returns (SetResponse) {}
// Ping the state store. Used for liveness porpuses.
rpc Ping(PingRequest) returns (PingResponse) {}
// Deletes many keys at once.
rpc BulkDelete(BulkDeleteRequest) returns (BulkDeleteResponse) {}
// Retrieves many keys at once.
rpc BulkGet(BulkGetRequest) returns (BulkGetResponse) {}
// Set the value of many keys at once.
rpc BulkSet(BulkSetRequest) returns (BulkSetResponse) {}
}
// Etag represents a state item version
message Etag {
// value sets the etag value
string value = 1;
}
// StateOptions configures concurrency and consistency for state operations
message StateOptions {
// Enum describing the supported concurrency for state.
enum StateConcurrency {
CONCURRENCY_UNSPECIFIED = 0;
CONCURRENCY_FIRST_WRITE = 1;
CONCURRENCY_LAST_WRITE = 2;
}
// Enum describing the supported consistency for state.
enum StateConsistency {
CONSISTENCY_UNSPECIFIED = 0;
CONSISTENCY_EVENTUAL = 1;
CONSISTENCY_STRONG = 2;
}
StateConcurrency concurrency = 1;
StateConsistency consistency = 2;
}
// InitRequest is the request for initializing the component.
message InitRequest {
MetadataRequest metadata = 1;
}
// reserved for future-proof extensibility
message InitResponse {}
message GetRequest {
// The key that should be retrieved.
string key = 1;
// Request associated metadata.
map<string, string> metadata = 2;
// The get consistency level.
StateOptions.StateConsistency consistency = 3;
}
message GetResponse {
// The data of the GetRequest response.
bytes data = 1;
// The etag of the associated key.
Etag etag = 2;
// Metadata related to the response.
map<string, string> metadata = 3;
// The response data contenttype
string content_type = 4;
}
message DeleteRequest {
// The key that should be deleted.
string key = 1;
// The etag is used as a If-Match header, to allow certain levels of
// consistency.
Etag etag = 2;
// The request metadata.
map<string, string> metadata = 3;
StateOptions options = 4;
}
// reserved for future-proof extensibility
message DeleteResponse {}
message SetRequest {
// The key that should be set.
string key = 1;
// Value is the desired content of the given key.
bytes value = 2;
// The etag is used as a If-Match header, to allow certain levels of
// consistency.
Etag etag = 3;
// The request metadata.
map<string, string> metadata = 4;
// The Set request options.
StateOptions options = 5;
// The data contenttype
string content_type = 6;
}
// reserved for future-proof extensibility
message SetResponse {}
message BulkDeleteRequest {
repeated DeleteRequest items = 1;
}
// reserved for future-proof extensibility
message BulkDeleteResponse {}
message BulkGetRequest {
repeated GetRequest items = 1;
}
message BulkStateItem {
// The key of the fetched item.
string key = 1;
// The associated data of the fetched item.
bytes data = 2;
// The item ETag
Etag etag = 3;
// A fetch error if there's some.
string error = 4;
// The State Item metadata.
map<string, string> metadata = 5;
// The data contenttype
string content_type = 6;
}
message BulkGetResponse {
repeated BulkStateItem items = 1;
bool got = 2;
}
message BulkSetRequest {
repeated SetRequest items = 1;
}
// reserved for future-proof extensibility
message BulkSetResponse {}

View File

@ -0,0 +1,74 @@
# Dapr Pluggable Components Dotnet Template
## Sample info
| Attribute | Details |
| -------------------- | ------- |
| Dapr runtime version | 1.9.0 |
| Language | .NET |
| Environment | Local |
## Overview
This is a template project that enables you to build a pluggable statestore component in .NET.
## Run the sample
### Prerequisites
- [.NET Core 6+](https://dotnet.microsoft.com/download)
- [grpc_cli tool](https://github.com/grpc/grpc/blob/master/doc/command_line_tool.md) for making gRPC calls. There are [npm installer](https://www.npmjs.com/package/grpc-cli) and [brew formulae](https://formulae.brew.sh/formula/grpc) available to install.
- Operating system that supports Unix Domain Sockets. UNIX or UNIX-like system (Mac, Linux, or [WSL](https://learn.microsoft.com/windows/wsl/install) for Windows users)
### Step 1 - Clone the sample repository
1. Clone the sample repo, then navigate to the pluggable-components-dotnet-template sample:
```bash
git clone https://github.com/dapr/samples.git
cd samples/pluggable-components-dotnet-sample
```
2. Examine the `./Services/Services.cs` file. You'll see four commented classes. They are `StateStoreService`, `PubSubService`, `InputBindingService` and `OutputBindingService`, their protos are defined inside `./Protos` folder. Uncomment any number of them as these serve as a unimplemented proto service that you start from.
Uncommenting StateStoreService as an example:
```csharp
// Uncomment the lines below to implement the StateStore methods defined in the following protofiles
// ./Protos/dapr/proto/components/v1/state.proto#L123
public class StateStoreService : StateStore.StateStoreBase
{
private readonly ILogger<StateStoreService> _logger;
public StateStoreService(ILogger<StateStoreService> logger)
{
_logger = logger;
}
}
```
### Step 2 - Register your unimplemented service
Once you decide which of proto services you are going to implement, go to the `./Program.cs` file and examine the lines 46-50. You'll see commented lines, uncomment based on the services that you chose to implement.
For registering StateStoreService:
```csharp
// Configure the HTTP request pipeline.
app.MapGrpcService<StateStoreService>(); // Uncomment to register the StateStoreService
// app.MapGrpcService<PubSubService>(); // Uncomment to register the PubSubService
// app.MapGrpcService<InputBindingService>(); // Uncomment to register the InputBindingService
// app.MapGrpcService<OutputBindingService>(); // Uncomment to register the OutputBindingService
```
### Step 3 - Making gRPC requests
1. Run the sample code by running `dotnet run`
2. Based on the previous step you can make calls to any of those services using the `grpc_cli` tool. The example below show how to execute a `Set` on `StateStore` services, but you can apply the same for the others following their proto definitions.
```shell
grpc_cli call unix:///tmp/dapr-components-sockets/memstore.sock dapr.proto.components.v1.StateStore/Set "key:'my_key', value:'my_value'"
```
From now on, you should be able to implement the unimplemented methods from your desired service. Refer to the [official Microsoft documentation for development using Protocol Buffers](https://learn.microsoft.com/aspnet/core/grpc/basics?view=aspnetcore-6.0#c-tooling-support-for-proto-files) for further information.

View File

@ -0,0 +1,62 @@
// ------------------------------------------------------------------------
// Copyright 2022 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
// Uncomment to import Dapr proto components namespace.
// using Dapr.Proto.Components.V1;
namespace DaprComponents.Services;
// Uncomment the lines below to implement the StateStore methods defined in the following protofiles
// ./Protos/dapr/proto/components/v1/state.proto#L123
// public class StateStoreService : StateStore.StateStoreBase
// {
// private readonly ILogger<StateStoreService> _logger;
// public StateStoreService(ILogger<StateStoreService> logger)
// {
// _logger = logger;
// }
// }
// Uncomment the lines below to implement the PubSub methods defined in the following protofiles
// ./Protos/dapr/proto/components/v1/pubsub.proto#L23
// public class PubSubService : PubSub.PubSubBase
// {
// private readonly ILogger<PubSubService> _logger;
// public PubSubService(ILogger<PubSubService> logger)
// {
// _logger = logger;
// }
// }
// Uncomment the lines below to implement the InputBindings methods defined in the following protofiles
// ./Protos/dapr/proto/components/v1/bindings.proto#L23
// public class InputBindingService : InputBinding.InputBindingBase
// {
// private readonly ILogger<InputBindingService> _logger;
// public InputBindingService(ILogger<InputBindingService> logger)
// {
// _logger = logger;
// }
// }
// Uncomment the lines below to implement the OutputBindings methods defined in the following protofiles
// ./Protos/dapr/proto/components/v1/bindings.proto#L37
// public class OutputBindingService : OutputBinding.OutputBindingBase
// {
// private readonly ILogger<OutputBindingService> _logger;
// public OutputBindingService(ILogger<OutputBindingService> logger)
// {
// _logger = logger;
// }
// }

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,14 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http2"
}
}
}