Featured image of post 构建属于你自己的dapr绑定组件

构建属于你自己的dapr绑定组件

上一篇文章中,吐槽了拖延症的危害,因此这次我来分享一下我最新推送到dapr的最新的一个新的绑定组件,通过这个来看一下如何实现自己的绑定组件。

文中提到的PR可以在 dapr/components-contrib#872 查看对应的具体代码。

什么是 dapr 的绑定组件?

在dapr中,绑定是用于使用外部系统功能(比如事件或者接口)的扩展组件。它的优势在于:

  • 免除连接到消息传递系统(如队列和消息总线)并进行轮询的复杂性;
  • 聚焦于业务逻辑,而不是如何与系统交互的实现细节;
  • 使代码不受 SDK 或库的跟踪;
  • 处理重试和故障恢复;
  • 在运行时在绑定之间切换;
  • 构建具有特定于环境的绑定的可移植应用程序,不需要进行代码更改;

在官方文档中,也提到了一个具体的例子:以twilio发送短信为例,一般开发过程中应用程序需要依赖Twilio SDK才可以实现功能,但是借助绑定组件,你可以将SDK的绑定转移至dapr程序领域内,在本身应用程序中不再绑定对应的SDK,不用担心未来SDK过期或者变更带来的重复工作(仅需要更新dapr即可)。

根据订阅的进出方向,绑定组件也分为输入绑定和输出绑定。这些绑定均是通过yaml文件描述类型和元数据,通过HTTP/gRPC进行调用。

如何实现自己的绑定组件?

官方例子中提供了一个基础的介绍,上一节中我们也提到了在程序中,根据进出方向可以把绑定组件分为输出绑定和输入绑定。你可以通过官方教程中的例子提供查看:

在这个例子用,你可以看到,根据方向吧dapr发布消息到Kafka作为输出组件,把Kafka读取消息到dapr作为输入组件。

绑定的声明yaml文件的规范则如下:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: <NAME>
  namespace: <NAMESPACE>
spec:
  type: bindings.<TYPE>
  version: v1
  metadata:
  - name: <NAME>
    value: <VALUE>

其中metadata.name则是绑定置名称,spec.metadata.namespec.metadata.value则是配置的属性和对应值。这个值我们可以通过实现接口InputBinding或者OutputBinding实现输入绑定和输出绑定.

type InputBinding interface {
	Init(metadata Metadata) error
	Read(handler func(*ReadResponse) ([]byte, error)) error
}

type OutputBinding interface {
	Init(metadata Metadata) error
	Invoke(req *InvokeRequest) (*InvokeResponse, error)
	Operations() []OperationKind
}

接下来需要实现一个生成对象的方法,比如说我们需要实现一个飞书推送Webhook的绑定组件,则可以:

type FeishuWebhook struct {
	logger     logger.Logger // 这个是dapr的日志接口,输出日志可以使用这个
	settings   Settings // 具体配置信息
	httpClient *http.Client  // 请求HTTP
}


func NewFeishuWebhook(l logger.Logger) *FeishuWebhook {
	// See guidance on proper HTTP client settings here:
	// https://medium.com/@nate510/don-t-use-go-s-default-http-client-4804cb19f779
	dialer := &net.Dialer{ //nolint:exhaustivestruct
		Timeout: 5 * time.Second,
	}
	var netTransport = &http.Transport{ //nolint:exhaustivestruct
		DialContext:         dialer.DialContext,
		TLSHandshakeTimeout: 5 * time.Second,
	}
	httpClient := &http.Client{ //nolint:exhaustivestruct
		Timeout:   defaultHTTPClientTimeout,
		Transport: netTransport,
	}

	return &FeishuWebhook{ //nolint:exhaustivestruct
		logger:     l,
		httpClient: httpClient,
	}
}

在绑定组件生命周期中,init会在初始化是进行调用,传入我们之前在yaml文件中定义的配置文件,因此我们可以在这里实现具体的配置获取:

type Settings struct {
	URL    string `mapstructure:"url"` // Webhook地址
	Secret string `mapstructure:"secret"` // 加密消息的密钥
}

func (s *Settings) Decode(in interface{}) error {
	return config.Decode(in, s)
}

func (s *Settings) Validate() error {
	if s.ID == "" {
		return errors.New("webhook error: missing webhook id")
	}
	if s.URL == "" {
		return errors.New("webhook error: missing webhook url")
	}

	return nil
}

// Init performs metadata parsing
func (t *FeishuWebhook) Init(metadata bindings.Metadata) error {
	var err error
	if err = t.settings.Decode(metadata.Properties); err != nil {
		return fmt.Errorf("feishu configuration error: %w", err)
	}
	if err = t.settings.Validate(); err != nil {
		return fmt.Errorf("feishu configuration error: %w", err)
	}

	return nil
}

接下来在具体的Read(handler func(*ReadResponse) ([]byte, error)) errorInvoke(req *InvokeRequest) (*InvokeResponse, error)方法中,我们可以分别实现读取传入消息和发送传出消息的功能。代码根据不同实现而不同,这里就不做区分了。

总结

我这里根据实际的绑定组件例子介绍了给dapr实现绑定组件的功能,是不是手痒希望试试了?快点加入到贡献大军吧XD。

Licensed under CC BY-NC-SA 4.0
Built with Hugo
主题 StackJimmy 设计