構建屬於你自己的 dapr 綁定組件

上一篇文章中,吐槽了拖延症的危害,因此這次我來分享一下我最新推送到 dapr 的最新的一個新的綁定組件,通過這個來看一下如何實現自己的綁定組件。

文中提到的 PR 可以在 dapr/components-contrib#872 查看對應的具體代碼。

什麼是 dapr 的綁定組件?

在 dapr 中,綁定是用於使用外部系統功能(比如事件或者接口)的擴展組件。它的優勢在於:

在官方文檔中,也提到了一個具體的例子:以 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。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://www.4async.com/2021/05/building-your-own-dapr-binding/