在上一篇文章中,吐槽了拖延症的危害,因此这次我来分享一下我最新推送到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.name
和spec.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)) error
和Invoke(req *InvokeRequest) (*InvokeResponse, error)
方法中,我们可以分别实现读取传入消息和发送传出消息的功能。代码根据不同实现而不同,这里就不做区分了。
总结
我这里根据实际的绑定组件例子介绍了给dapr实现绑定组件的功能,是不是手痒希望试试了?快点加入到贡献大军吧XD。