相信很多开发同学一定都听说过SDK,SDK全称Software Development Kit,即软件开发工具包。它是由硬件平台、操作系统或编程语言的制造商提供的一套工具,协助软件开发人员面向特定的平台、系统或编程语言创建应用。SDK经常被用于为特定的软件包、软件框架、硬件平台、操作系统等创建应用软件的开发工具的集合。
先来个抛砖引玉,写过Java的同学一定听说过JDK,和SDK只有一字之差,那么它们两者之间有什么区别和联系呢?
首先,SDK(Software Development Kit) 和 JDK(Java Development Kit) 之间的区别:
其次,它们两个也有相同的地方:
简而言之,SDK是统称,而JDK只是Java的集成开发工具,是SDK的子集。
接下来,我们就着手了解一下SDK的真正作用,是如何使用的,又如何优雅的设计一个SDK。
SDK(Software Development Kit)的作用主要体现在以下几个方面:
总的来说,SDK的作用就是帮助开发者更快、更方便地开发应用程序。通过提供开发工具、API接口、库文件以及文档和示例代码,SDK降低了开发的难度,提高了开发的效率。
SDK的使用场景非常广泛,主要包括以下几个方面:
总的来说,SDK的使用场景相当广泛,几乎涵盖了软件开发的各个方面。不过具体使用时还是要根据实际需求进行选择。这些只是SDK的一些典型使用场景,实际上,只要是需要对某种特定功能进行封装以便于开发者使用的场景,都可能会使用到SDK。
Go语言SDK的设计流程一般可以分为以下几个步骤:
下面我们就以一个HTTP服务为例设计一个简单的SDK。
const (
HeaderName = "barry yan"
)
var (
data map[string]string
ErrOk = Err{Code: 200, Msg: "ok"}
ErrNotAuth = Err{Code: 401, Msg: "not auth"}
ErrRequestBad = Err{Code: 400, Msg: "request bad"}
)
func init() {
data = make(map[string]string)
}
type T struct {
Key string `json:"key,omitempty"`
Val string `json:"val,omitempty"`
}
type Err struct {
Code int `json:"code,omitempty"`
Msg string `json:"msg,omitempty"`
}
func headerInterceptor(c *gin.Context) {
header := c.Request.Header.Get("name")
if header != HeaderName {
c.JSON(http.StatusUnauthorized, ErrNotAuth)
c.Abort()
return
}
c.Next()
}
func create(c *gin.Context) {
var t T
if err := c.BindJSON(&t); err != nil {
c.JSON(http.StatusBadRequest, ErrRequestBad)
return
}
data[t.Key] = t.Val
c.JSON(http.StatusOK, ErrOk)
return
}
func get(c *gin.Context) {
key := c.Param("key")
val := data[key]
c.JSON(http.StatusOK, val)
return
}
func main() {
r := gin.Default()
r.Use(headerInterceptor)
r.POST("/create", create)
r.GET("/get/:key", get)
_ = r.Run(":9999")
}
在没有SDK的情况下我们尝试写代码调用接口:
func TestCreateAPI(t *testing.T) {
// 创建一个HTTP客户端
client := &http.Client{}
// 创建POST请求的body
reqData := []byte(`{"key":"A","val":"1"}`)
// 创建一个POST请求
req, err := http.NewRequest("POST", "http://localhost:9999/create", bytes.NewBuffer(reqData))
if err != nil {
fmt.Println("创建请求时发生错误:", err)
return
}
// 设置请求头
req.Header.Set("Content-Type", "application/json")
req.Header.Set("name", "barry yan")
// 发送请求并获取响应
resp, err := client.Do(req)
if err != nil {
fmt.Println("发送请求时发生错误:", err)
return
}
defer func() {
_ = resp.Body.Close()
}()
// 读取响应内容
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("读取响应时发生错误:", err)
return
}
// 打印响应内容
fmt.Println(string(body))
}
func TestGetAPI(t *testing.T) {
// 创建一个HTTP客户端
client := &http.Client{}
// 创建一个GET请求
req, err := http.NewRequest("GET", "http://localhost:9999/get/A", nil)
if err != nil {
fmt.Println("创建请求时发生错误:", err)
return
}
req.Header.Set("name", "barry yan")
// 发送请求并获取响应
resp, err := client.Do(req)
if err != nil {
fmt.Println("发送请求时发生错误:", err)
return
}
defer func() {
_ = resp.Body.Close()
}()
// 读取响应的内容
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("读取响应时发生错误:", err)
return
}
// 打印响应内容
fmt.Println(string(body))
}
该方式的缺点显而易见,比如:
(1)请求参数和返回值定义没有固定的规范
(2)重复代码太多
(3)调用链复杂时难以解耦合
基于此,我们设计一个SDK,专门用于调用该系统API的接口
我们先将Go调用HTTP接口的方式做一个封装:
type Option func(*HttpClient)
type HttpClient struct {
Url string
Body []byte
Header map[string]string
Client *http.Client
}
func NewHttpClient(url string, opts ...Option) *HttpClient {
cli := &HttpClient{Url: url, Client: &http.Client{}}
for _, opt := range opts {
opt(cli)
}
return cli
}
func WithBody(body []byte) Option {
return func(client *HttpClient) {
client.Body = body
}
}
func WithHeader(header map[string]string) Option {
return func(client *HttpClient) {
client.Header = header
}
}
func (c *HttpClient) Post() ([]byte, error) {
req, err := http.NewRequest("POST", c.Url, bytes.NewBuffer(c.Body))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
for k, v := range c.Header {
req.Header.Set(k, v)
}
resp, err := c.Client.Do(req)
if err != nil {
return nil, err
}
defer func() {
_ = resp.Body.Close()
}()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
}
func (c *HttpClient) Get() ([]byte, error) {
req, err := http.NewRequest("GET", c.Url, bytes.NewBuffer(c.Body))
if err != nil {
return nil, err
}
for k, v := range c.Header {
req.Header.Set(k, v)
}
resp, err := c.Client.Do(req)
if err != nil {
return nil, err
}
defer func() {
_ = resp.Body.Close()
}()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
}
接下来我们sdk的核心代码就是对我们的业务接口调用方式进行封装:
(1)定义统一的请求体结构和错误码:
请求体:
type CreateRequest struct {
Key string `json:"key,omitempty"`
Val string `json:"val,omitempty"`
}
错误码:
type Err struct {
Code int `json:"code,omitempty"`
Msg string `json:"msg,omitempty"`
}
var (
ErrOk = Err{Code: 200, Msg: "ok"}
ErrNotAuth = Err{Code: 401, Msg: "not auth"}
ErrRequestBad = Err{Code: 400, Msg: "request bad"}
ErrInnerErr = Err{Code: 500, Msg: "inner err"}
)
(2)sdk核心方法
const (
defaultUsername = "barry"
defaultPasswd = "yan"
)
type SDK struct {
Host string
User string
Passwd string
header map[string]string
}
func NewSDK(host, userName, passWd string) (*SDK, error) {
sdk := &SDK{
Host: host,
User: userName,
Passwd: passWd,
}
if sdk.checkAuth() {
sdk.header = map[string]string{"name": "barry yan"}
return sdk, nil
}
return nil, errors.New("auth err")
}
func (s *SDK) checkAuth() bool {
return s.User == defaultUsername && s.Passwd == defaultPasswd
}
func (s *SDK) Create(request CreateRequest) Err {
path := "/create"
bytes, err := json.Marshal(request)
if err != nil {
return ErrInnerErr
}
resp, err := NewHttpClient(fmt.Sprintf("%s%s", s.Host, path),
WithBody(bytes),
WithHeader(map[string]string{"name": "barry yan"})).Post()
if err != nil {
return ErrInnerErr
}
httpResp := &Err{}
if err := json.Unmarshal(resp, httpResp); err != nil {
return ErrInnerErr
}
if httpResp.Code != http.StatusOK {
return *httpResp
}
return ErrOk
}
func (s *SDK) Get(key string) (string, Err) {
path := "/get"
resp, err := NewHttpClient(fmt.Sprintf("%s%s/%s", s.Host, path, key),
WithHeader(map[string]string{"name": "barry yan"})).Get()
if err != nil {
return "", ErrInnerErr
}
return string(resp), ErrOk
}
import (
"fmt"
sdk "go-http-sdk"
"net/http"
"testing"
)
func TestSDKCreate(t *testing.T) {
newSDK, err := sdk.NewSDK("http://localhost:9999", "barry", "yan")
if err != nil && newSDK != nil {
return
}
err1 := newSDK.Create(sdk.CreateRequest{Key: "D", Val: "1"})
if err1.Code != http.StatusOK {
fmt.Println(err1)
}
}
func TestSDKGet(t *testing.T) {
newSDK, err := sdk.NewSDK("http://localhost:9999", "barry", "yan")
if err != nil && newSDK != nil {
return
}
resp, err2 := newSDK.Get("D")
if err2.Code != http.StatusOK {
fmt.Println(err2)
}
fmt.Println(resp)
}
看,是不是感觉代码少了太多
到这里大家可能会产生疑问,为什么NewSDK
的时候除了host还要带上username和passwd这两个参数。
其实主要是因为系统一般会有Auth认证的流程,主要是用于认证调用者是否为该系统的合法用户,API中的header(name=barry yan)也正是为了验证用户,当然实际一定是要比这个复杂的多,SDK也会有对Auth认证方式的封装。
除此之外,由于时间关系,本文中的这个SDK案例设计的确实过于简单,希望大家在真实的生产项目中不要照搬模仿,在这里提供几个比较好的SDK设计:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。