Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
323aadb
WIP: server
mchmarny Jul 1, 2020
cc49891
gRPC server implementaiton
mchmarny Jul 1, 2020
5be80e6
http serving
mchmarny Jul 1, 2020
8082893
grpc ports as string vs int
mchmarny Jul 3, 2020
51572c8
bump tag
mchmarny Jul 3, 2020
0a7384b
adds ctx, fixes linting errors
mchmarny Jul 10, 2020
31ce1e1
v0.8.2 bump
mchmarny Jul 10, 2020
501e18a
fixes serving options
mchmarny Jul 10, 2020
243434b
cleans up cloud events in gttp serving
mchmarny Jul 10, 2020
ee5be78
v0.8.4 version bump
mchmarny Jul 10, 2020
cd5d1f3
uses http method constant
mchmarny Jul 10, 2020
3217505
adds grpc serving tests
mchmarny Jul 11, 2020
c6a1b7c
pubsub evnet as bytes
mchmarny Jul 11, 2020
b9b2321
adds tests, seperate payload between http, grpc
mchmarny Jul 11, 2020
a301d1b
refactored names, tests
mchmarny Jul 11, 2020
c8406d0
fixes release names
mchmarny Jul 11, 2020
492399d
fixes versoin number
mchmarny Jul 11, 2020
c151d2a
http invoke test, grpc invoke params
mchmarny Jul 11, 2020
e203837
makefile updates
mchmarny Jul 11, 2020
9d65aa3
readme updates, fixes grpc to http invocations
mchmarny Jul 11, 2020
5356bea
readme updates
mchmarny Jul 11, 2020
7566345
comments, refactored http service
mchmarny Jul 12, 2020
d950f7d
more serving tests
mchmarny Jul 12, 2020
f9a2fb1
more client tests
mchmarny Jul 12, 2020
703e045
test verbocity
mchmarny Jul 12, 2020
8b040c8
fixes grpc error serializtion error
mchmarny Jul 12, 2020
7c5c4ff
adds serving to readme
mchmarny Jul 13, 2020
df470f5
code formatting
mchmarny Jul 13, 2020
8853daa
adds support for TypeUrl in case of a proto conten
mchmarny Jul 15, 2020
e21b097
added binding in http service
mchmarny Jul 18, 2020
a9e9833
cron in grpc example
mchmarny Jul 18, 2020
74a38e5
single interface for grpc and http sdks
mchmarny Jul 19, 2020
ae479cd
normalized serving interfaces across http and grpc
mchmarny Jul 19, 2020
0699bee
unit, rest api tests for grpc, html serving
mchmarny Jul 19, 2020
8339a35
updated tests
mchmarny Jul 19, 2020
1ecf7c3
only 1 client instance using default constructor
mchmarny Jul 23, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
RELEASE_VERSION =v0.8.0
RELEASE_VERSION =v0.8.17
GDOC_PORT =8888
PROTO_ROOT =https://raw.githubusercontent.com/dapr/dapr/master/dapr/proto/

Expand All @@ -9,8 +9,7 @@ mod: ## Updates the go modules
go mod tidy

test: mod ## Tests the entire project
go test -v -count=1 -race ./...
# go test -v -count=1 -run NameOfSingleTest ./...
go test -count=1 -race ./...

cover: mod ## Displays test coverage in the Client package
go test -coverprofile=cover.out ./client && go tool cover -html=cover.out
Expand Down
121 changes: 100 additions & 21 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
# Dapr SDK for Go

This is the dapr SDK (client) for go (golang). It covers all of the APIs described in Dapr's [protocol buffers](https://raw.githubusercontent.com/dapr/dapr/master/dapr/proto/) with focus on developer productivity.
Client library to accelerate Dapr development in go. This client supports all public [Dapr API](https://github.com/dapr/docs/tree/master/reference/api) and focuses on developer productivity.

[![Test](https://github.com/dapr/go-sdk/workflows/Test/badge.svg)](https://github.com/dapr/go-sdk/actions?query=workflow%3ATest) [![Release](https://github.com/dapr/go-sdk/workflows/Release/badge.svg)](https://github.com/dapr/go-sdk/actions?query=workflow%3ARelease) [![Go Report Card](https://goreportcard.com/badge/github.com/dapr/go-sdk)](https://goreportcard.com/report/github.com/dapr/go-sdk) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/dapr/go-sdk)

## Installation
## Usage

To install Dapr client package, you need to first [install go](https://golang.org/doc/install) and set up your development environment. To import Dapr go client in your code:
> Assuming you already have [installed](https://golang.org/doc/install) go

Dapr go client includes two packages: `client` (for invoking public Dapr API) and `service` (to create services in go that can be invoked by Dapr).

### Client

Import Dapr go `client` package:

```go
import "github.com/dapr/go-sdk/client"
```

## Quick start
#### Quick start

```go
package main
Expand All @@ -27,26 +33,34 @@ func main() {
panic(err)
}
defer client.Close()
//TODO: use the client here
//TODO: use the client here, see below for examples
}
```

Assuming you have Dapr CLI installed locally, you can then launch your app like this:
Assuming you have [Dapr CLI](https://github.com/dapr/docs/blob/master/getting-started/environment-setup.md) installed locally, you can then launch your app locally like this:

```shell
dapr run --app-id my-app --protocol grpc --app-port 50001 go run main.go
dapr run --app-id example-service \
--protocol grpc \
--app-port 50001 \
go run main.go
```

See [example folder](./example) for complete example.
Check the [example folder](./example) for working Dapr go client examples.

To accelerate your Dapr service development even more, consider the GitHub templates with complete gRPC solutions for two common use-cases:

* [gRPC Event Subscriber Template](https://github.com/mchmarny/dapr-grpc-event-subscriber-template) for pub/sub event processing
* [gRPC Serving Service Template ](https://github.com/mchmarny/dapr-grpc-service-template) which creates a target for service to service invocations


## Examples
#### Usage

Few common Dapr client usage examples
The Dapr go client supports following functionality:

### State
##### State

For simple use-cases, Dapr client provides easy to use methods:
For simple use-cases, Dapr client provides easy to use methods for `Save`, `Get`, and `Delete`:

```go
ctx := context.Background()
Expand All @@ -66,7 +80,7 @@ err = client.DeleteState(ctx, store, "k1")
handleErrors(err)
```

The `StateItem` type exposed by Dapr client provides more granular control options:
For more granular control, the Dapr go client exposed `StateItem` type which can be use to gain more control over the state operations:

```go
data := &client.StateItem{
Expand All @@ -90,7 +104,7 @@ data := &client.StateItem{
err = client.SaveStateItem(ctx, store, data)
```

Similar `StateOptions` exist on `GetDate` and `DeleteState` methods. Additionally, Dapr client also provides a method to save multiple state items at once:
Similarly, `StateOptions` exist on the `GetDate` and `DeleteState` methods to support multiple item operations at once:

```go
data := &client.State{
Expand All @@ -109,17 +123,17 @@ data := &client.State{
err = client.SaveState(ctx, data)
```

### PubSub
##### PubSub

To publish data onto a topic the Dapr client provides a simple method:

```go
data := []byte("hello")
data := []byte(`{ "id": "a123", "value": "abcdefg", "valid": true }`)
err = client.PublishEvent(ctx, "topic-name", data)
handleErrors(err)
```

### Service Invocation
##### Service Invocation

To invoke a specific method on another service running with Dapr sidecar, the Dapr client provides two options. To invoke a service without any data:

Expand All @@ -131,12 +145,12 @@ handleErrors(err)
And to invoke a service with data:

```go
data := []byte("hello")
resp, err := client.InvokeServiceWithContent(ctx, "service-name", "method-name", "text/plain", data)
data := []byte(`{ "id": "a123", "value": "abcdefg", "valid": true }`)
resp, err := client.InvokeServiceWithContent(ctx, "service-name", "method-name", "application/json", data)
handleErrors(err)
```

### Bindings
##### Bindings

Similarly to Service, Dapr client provides two methods to invoke an operation on a [Dapr-defined binding](https://github.com/dapr/docs/tree/master/concepts/bindings). Dapr supports input, output, and bidirectional bindings so the first methods supports all of them along with metadata options:

Expand All @@ -158,7 +172,7 @@ err = client.InvokeOutputBinding(ctx, "binding-name", "operation-name", data)
handleErrors(err)
```

### Secrets
##### Secrets

The Dapr client also provides access to the runtime secrets that can be backed by any number of secrete stores (e.g. Kubernetes Secrets, Hashicorp Vault, or Azure KeyVault):

Expand All @@ -170,6 +184,71 @@ secret, err = client.GetSecret(ctx, "store-name", "secret-name", opt)
handleErrors(err)
```

## Service

Dapr go package provides two implementations for `service`: HTTP and gRPC

### HTTP

Import Dapr go `service` package:

```go
daprd "github.com/dapr/go-sdk/service/http"
```

#### Event Handling

To handle events from specific topic in HTTP, first create a Dapr serving server, add topic event handler, and start the service on specific address:

```go
s := daprd.NewService()

err := s.AddTopicEventHandler("messages", "/messages", messageHandler)
if err != nil {
log.Fatalf("error adding topic subscription: %v", err)
}

// start service on address (e.g. ":8080", "0.0.0.0:8080", "10.1.1.1:8080" )
if err = s.Start(":8080"); err != nil && err != http.ErrServerClosed {
log.Fatalf("error listenning: %v", err)
}

func messageHandler(ctx context.Context, e daprd.TopicEvent) error {
log.Printf("event - Topic:%s, ID:%s, Data: %v", e.Topic, e.ID, e.Data)
return nil
}
```

#### Service Invocation Handler

To handle service invocations in HTTP, first create a Dapr serving server, add invocation handler, and start the service on specific address:



```go
s := daprd.NewService()

err = s.AddInvocationHandler("/EchoMethod", echoHandler)
if err != nil {
log.Fatalf("error adding invocation handler: %v", err)
}

// start service on address (e.g. ":8080", "0.0.0.0:8080", "10.1.1.1:8080" )
if err = s.Start(":8080"); err != nil && err != http.ErrServerClosed {
log.Fatalf("error listenning: %v", err)
}

func echoHandler(ctx context.Context, in *daprd.InvocationEvent) (out []byte, err error) {
if in == nil {
err = errors.New("nil invocation parameter")
return
}
log.Printf("echo handler (%s): %+v", in.ContentType, string(in.Data))
out = in.Data
return
}
```

## Contributing to Dapr go client

See the [Contribution Guide](./CONTRIBUTING.md) to get started with building and developing.
38 changes: 27 additions & 11 deletions client/binding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,36 @@ import (
"github.com/stretchr/testify/assert"
)

// go test -timeout 30s ./client -count 1 -run ^TestInvokeBinding$

func TestInvokeBinding(t *testing.T) {
ctx := context.Background()
data := "ping"

mIn := make(map[string]string)
mIn["test"] = "value"
t.Run("output binding", func(t *testing.T) {
err := testClient.InvokeOutputBinding(ctx, "test", "fn", []byte(data))
assert.Nil(t, err)
})

out, mOut, err := testClient.InvokeBinding(ctx, "serving", "EchoMethod", []byte("ping"), mIn)
assert.Nil(t, err)
assert.NotNil(t, mOut)
assert.NotNil(t, out)
}
t.Run("output binding without data", func(t *testing.T) {
err := testClient.InvokeOutputBinding(ctx, "test", "fn", []byte(data))
assert.Nil(t, err)
})

t.Run("binding without data", func(t *testing.T) {
out, mOut, err := testClient.InvokeBinding(ctx, "test", "fn", nil, nil)
assert.Nil(t, err)
assert.NotNil(t, mOut)
assert.NotNil(t, out)
})

t.Run("binding with data and meta", func(t *testing.T) {
mIn := map[string]string{"k1": "v1", "k2": "v2"}
out, mOut, err := testClient.InvokeBinding(ctx, "test", "fn", []byte(data), mIn)
assert.Nil(t, err)
assert.NotNil(t, mOut)
assert.NotNil(t, out)
assert.Equal(t, data, string(out))
})

func TestInvokeOutputBinding(t *testing.T) {
ctx := context.Background()
err := testClient.InvokeOutputBinding(ctx, "serving", "EchoMethod", []byte("ping"))
assert.Nil(t, err)
}
27 changes: 23 additions & 4 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"log"
"net"
"os"
"sync"

"github.com/pkg/errors"
"google.golang.org/grpc"
Expand All @@ -19,8 +20,10 @@ const (
)

var (
logger = log.New(os.Stdout, "", 0)
_ Client = (*GRPCClient)(nil)
logger = log.New(os.Stdout, "", 0)
_ Client = (*GRPCClient)(nil)
defaultClient Client
doOnce sync.Once
)

// Client is the interface for Dapr client implementation.
Expand Down Expand Up @@ -49,7 +52,10 @@ type Client interface {
SaveState(ctx context.Context, s *State) error

// SaveStateData saves the raw data into store using default state options.
SaveStateData(ctx context.Context, store, key, etag string, data []byte) error
SaveStateData(ctx context.Context, store, key string, data []byte) error

// SaveStateDataVersion saves the raw data into store using default state options and etag.
SaveStateDataVersion(ctx context.Context, store, key, etag string, data []byte) error

// SaveStateItem saves the single state item to store.
SaveStateItem(ctx context.Context, store string, item *StateItem) error
Expand All @@ -71,12 +77,25 @@ type Client interface {
}

// NewClient instantiates Dapr client using DAPR_GRPC_PORT environment variable as port.
// Note, this default factory function creates Dapr client only once. All subsequent invocations
// will return the already created instance. To create multiple instances of the Dapr client,
// use one of the parameterized factory functions:
// NewClientWithPort(port string) (client Client, err error)
// NewClientWithAddress(address string) (client Client, err error)
// NewClientWithConnection(conn *grpc.ClientConn) Client
func NewClient() (client Client, err error) {
port := os.Getenv(daprPortEnvVarName)
if port == "" {
port = daprPortDefault
}
return NewClientWithPort(port)
var onceErr error
doOnce.Do(func() {
c, err := NewClientWithPort(port)
onceErr = errors.Wrap(err, "error creating default client")
defaultClient = c
})

return defaultClient, onceErr
}

// NewClientWithPort instantiates Dapr using specific port.
Expand Down
17 changes: 16 additions & 1 deletion client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"google.golang.org/grpc"
"google.golang.org/grpc/test/bufconn"
"google.golang.org/protobuf/types/known/anypb"

commonv1pb "github.com/dapr/go-sdk/dapr/proto/common/v1"
pb "github.com/dapr/go-sdk/dapr/proto/runtime/v1"
Expand Down Expand Up @@ -76,9 +77,17 @@ type testDaprServer struct {
}

func (s *testDaprServer) InvokeService(ctx context.Context, req *pb.InvokeServiceRequest) (*commonv1pb.InvokeResponse, error) {
if req.Message == nil {
return &commonv1pb.InvokeResponse{
ContentType: "text/plain",
Data: &anypb.Any{
Value: []byte("pong"),
},
}, nil
}
return &commonv1pb.InvokeResponse{
ContentType: req.Message.ContentType,
Data: req.GetMessage().Data,
Data: req.Message.Data,
}, nil
}

Expand Down Expand Up @@ -106,6 +115,12 @@ func (s *testDaprServer) PublishEvent(ctx context.Context, req *pb.PublishEventR
}

func (s *testDaprServer) InvokeBinding(ctx context.Context, req *pb.InvokeBindingRequest) (*pb.InvokeBindingResponse, error) {
if req.Data == nil {
return &pb.InvokeBindingResponse{
Data: []byte("test"),
Metadata: map[string]string{"k1": "v1", "k2": "v2"},
}, nil
}
return &pb.InvokeBindingResponse{
Data: req.Data,
Metadata: req.Metadata,
Expand Down
6 changes: 6 additions & 0 deletions client/invoke.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ func (c *GRPCClient) InvokeService(ctx context.Context, serviceID, method string
Id: serviceID,
Message: &v1.InvokeRequest{
Method: method,
HttpExtension: &v1.HTTPExtension{
Verb: v1.HTTPExtension_POST,
},
},
}
return c.invokeServiceWithRequest(ctx, req)
Expand All @@ -64,6 +67,9 @@ func (c *GRPCClient) InvokeServiceWithContent(ctx context.Context, serviceID, me
Method: method,
Data: &anypb.Any{Value: data},
ContentType: contentType,
HttpExtension: &v1.HTTPExtension{
Verb: v1.HTTPExtension_POST,
},
},
}

Expand Down
Loading