構建屬於你自己的dapr繫結元件

語言: CN / TW / HK

在上一篇文章中,吐槽了拖延症的危害,因此這次我來分享一下我最新推送到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。