如何优雅的设计一个SDK

简介: 如何优雅的设计一个SDK

相信很多开发同学一定都听说过SDK,SDK全称Software Development Kit,即软件开发工具包。它是由硬件平台、操作系统或编程语言的制造商提供的一套工具,协助软件开发人员面向特定的平台、系统或编程语言创建应用。SDK经常被用于为特定的软件包、软件框架、硬件平台、操作系统等创建应用软件的开发工具的集合。

先来个抛砖引玉,写过Java的同学一定听说过JDK,和SDK只有一字之差,那么它们两者之间有什么区别和联系呢?

首先,SDK(Software Development Kit)JDK(Java Development Kit) 之间的区别:

  • 定义不同:SDK是软件开发工具包,它是一个广泛的概念,包括各种API、库、文档、工具等,用于辅助开发者开发特定类型的应用程序。而JDK是Java开发工具包,是SDK的一种,专门针对Java语言的开发。
  • 涵盖的内容不同:JDK包含Java的运行环境(Java Runtime Environment,JRE),Java编译器(javac)和Java基础的类库。而SDK可能包含特定编程语言或框架的库、接口、文档、示例代码等。

其次,它们两个也有相同的地方:

  • 都是一种开发工具:无论是SDK还是JDK,都是开发工具包,为开发者提供了一系列的工具,帮助开发者更有效率地进行开发。
  • 都可以提供库和API:两者都会提供库文件和API接口。这些库文件和API接口封装了一些底层操作,提供了更高级别的操作接口,让开发者能更简单地实现功能。

简而言之,SDK是统称,而JDK只是Java的集成开发工具,是SDK的子集。

接下来,我们就着手了解一下SDK的真正作用,是如何使用的,又如何优雅的设计一个SDK。

1 SDK的主要作用

SDK(Software Development Kit)的作用主要体现在以下几个方面:

  1. 提供API接口:SDK通常包含一套API接口,这些接口是预先定义好的,开发者可以通过调用这些接口,实现与底层系统的交互,从而简化开发过程。
  2. 提供库文件:SDK中通常包含一些库文件,这些库文件包含了大量的函数和类,开发者可以直接使用这些函数和类,而无需从头开始编写。
  3. 提供文档和示例代码:SDK还会提供详细的开发文档和示例代码,帮助开发者理解和使用API接口和库文件。

总的来说,SDK的作用就是帮助开发者更快、更方便地开发应用程序。通过提供开发工具、API接口、库文件以及文档和示例代码,SDK降低了开发的难度,提高了开发的效率。

2 SDK的使用场景

SDK的使用场景非常广泛,主要包括以下几个方面:

  1. 移动应用开发:无论是Android还是iOS平台,开发者可以使用相应的SDK来构建各类移动应用。例如,Android开发者可以使用Android SDK来访问设备的各种硬件功能,如摄像头、传感器等;iOS开发者则可以使用iOS SDK来利用苹果设备的特色功能,如Touch ID、Apple Pay等。
  2. 游戏开发:游戏开发者可以使用游戏引擎提供的SDK来构建游戏应用。例如,开发者可以利用Unity SDK来实现游戏中的各种功能,如图形渲染、物理模拟、音频处理等。
  3. 小程序开发:小程序SDK是一种开发工具包,用于开发和构建小程序应用程序。开发者可以使用这些API和组件来构建小程序应用程序,例如在小程序中添加功能、调用硬件设备、实现交互等。使用小程序SDK可以加速小程序开发和部署的过程,并提高小程序的稳定性和性能。
  4. 网站开发:在网站开发中,SDK可以作为网站与第三方服务集成的桥梁。例如,支付SDK可以让网站轻松集成支付功能,而无需自行开发复杂的支付系统。类似地,社交SDK可以让网站集成社交网络的功能,如用户登录、分享等。
  5. 云端服务:云端服务的SDK为开发者提供了与云服务交互的方式。例如,开发者可以使用AWS SDK(亚马逊网络服务开发工具包)来调用亚马逊的各种云服务,如计算、存储、数据库、分析等。同样,Google Cloud SDK和Azure SDK也为开发者提供了与Google和Microsoft的云服务进行交互的能力。
  6. 嵌入式系统开发:在嵌入式系统开发过程中,SDK常常用于提供硬件抽象层、驱动程序以及开发工具等。通过嵌入式SDK,开发者可以更方便地编写、调试和部署应用程序,降低开发难度和复杂性。例如,智能家电的开发者可以使用SDK来简化与设备传感器的交互、实现远程控制等功能。
  7. 物联网(IoT)开发:物联网的发展使得设备之间的互联互通成为必要,而SDK在物联网开发中扮演着重要角色。IoT SDK通常包括设备连接、数据传输、安全管理等功能,帮助开发者快速构建IoT应用程序。通过使用IoT SDK,开发者可以将设备连接到云平台,实现远程监控、数据分析和智能控制等功能。

总的来说,SDK的使用场景相当广泛,几乎涵盖了软件开发的各个方面。不过具体使用时还是要根据实际需求进行选择。这些只是SDK的一些典型使用场景,实际上,只要是需要对某种特定功能进行封装以便于开发者使用的场景,都可能会使用到SDK。

3 优雅的设计一个SDK

Go语言SDK的设计流程一般可以分为以下几个步骤:

  1. 需求分析:在开始设计SDK之前,首先需要明确SDK的需求和目标。
  2. 接口设计:在明确需求后,开始设计SDK的接口。接口设计应该简洁明了,提供清晰的输入和输出,并遵循一致的命名规范和设计原则。
  3. 代码实现:根据接口设计,开始编写SDK的代码。在编写代码时,要遵循Go语言的最佳实践,确保代码的可读性、可维护性和性能。同时,要进行适当的错误处理和日志记录,以便于调试和故障排除。
  4. 单元测试与集成测试:编写单元测试和集成测试来验证SDK的正确性和稳定性。
  5. 文档编写:为SDK编写清晰、详尽的文档。文档应该包括接口的描述、参数说明、返回值说明、错误处理以及示例代码等。
  6. 版本发布与迭代:完成代码实现、测试和文档编写后,可以进行SDK的版本发布。遵循语义版本控制规范,确保版本的兼容性和稳定性。

下面我们就以一个HTTP服务为例设计一个简单的SDK。

3.1 先编写一个Go HTTP服务
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")
}
3.2 了解服务API的调用方式

在没有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的接口

3.3 设计API的SDK

我们先将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
}
3.4 SDK使用样例
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)
}

看,是不是感觉代码少了太多

4 小总结

到这里大家可能会产生疑问,为什么NewSDK的时候除了host还要带上username和passwd这两个参数。

其实主要是因为系统一般会有Auth认证的流程,主要是用于认证调用者是否为该系统的合法用户,API中的header(name=barry yan)也正是为了验证用户,当然实际一定是要比这个复杂的多,SDK也会有对Auth认证方式的封装。

除此之外,由于时间关系,本文中的这个SDK案例设计的确实过于简单,希望大家在真实的生产项目中不要照搬模仿,在这里提供几个比较好的SDK设计:

本文的全部代码也已经打包上传到Github,欢迎大家提出issue。获取代码方式:关注公众号【扯编程的淡】回复【sdk

相关文章
|
Java 开发工具 Maven
springboot项目打包为sdk供其他项目引用
springboot项目打包为sdk供其他项目引用
2350 1
|
5月前
|
人工智能 前端开发 IDE
仅凭几张图片,我们是如何让 AI 自动生成 70% 可用前端代码的?
本文系统总结了在仅有 UI 图片、无设计稿和交互说明的情况下,如何通过 AI 技术实现高质量前端代码自动生成。
仅凭几张图片,我们是如何让 AI 自动生成 70% 可用前端代码的?
|
编译器 API 语音技术
SDK介绍
【10月更文挑战第21天】
|
12月前
|
人工智能 自然语言处理 Cloud Native
【攻略】Bolt.diy 云端部署与应用实战:快速生成你的创意助手
随着AI应用从实验室走向大众,构建低门槛、高效率的AI助手平台成为开发者关注焦点。阿里云推出的Bolt.diy解决方案,开源灵活且部署快捷,支持函数计算FC与百炼大模型服务集成,大幅降低全栈AI应用开发难度。本文分享了实际部署Bolt.diy的全过程,并通过创建个人AI项目助理演示其强大功能。无论是生成项目计划、技术文档,还是搭建工具页面,Bolt.diy都能助力开发者快速实现创意,提升效率。文章还探讨了使用中的小问题及优化建议,适合对AI开发感兴趣的读者体验尝试。
335 10
|
JavaScript IDE 开发工具
找不到模块“./App.vue”或其相应的类型声明。ts(2307)
这篇文章介绍了在Vue 3 + TypeScript + Vite开发环境中解决找不到`.vue`文件模块或其类型声明错误的两种方法:使用VSCode的TypeScript Vue Plugin (Volar)插件或手动在`env.d.ts`文件中声明`*.vue`模块类型。
3182 1
找不到模块“./App.vue”或其相应的类型声明。ts(2307)
|
Java Maven 数据安全/隐私保护
如何实现Java打包程序的加密代码混淆,避免被反编译?
【10月更文挑战第15天】如何实现Java打包程序的加密代码混淆,避免被反编译?
3638 2
|
前端开发 JavaScript
前端基础(一)_前端页面构成
本文介绍了前端页面的基本构成,包括HTML(负责页面的结构和语义)、CSS(负责页面的样式和表现)和JavaScript(负责页面的行为和动态效果)。文章通过示例代码展示了如何使用这三种技术来创建一个简单的网页,并解释了HTML文档的结构和语法。
533 0
|
机器学习/深度学习 自然语言处理 API
大模型应用框架-LangChain(一)
LangChain由 Harrison Chase 创建于2022年10月,它是围绕LLMs(大语言模型)建立的一个框架,LLMs使用机器学习算法和海量数据来分析和理解自然语言,GPT3.5、GPT4是LLMs最先进的代表,国内百度的文心一言、阿里的通义千问也属于LLMs。LangChain自身并不开发LLMs,它的核心理念是为各种LLMs实现通用的接口,把LLMs相关的组件“链接”在一起,简化LLMs应用的开发难度,方便开发者快速地开发复杂的LLMs应用。 LangChain目前有两个语言的实现:python、nodejs。
|
机器学习/深度学习 数据采集 缓存
Elasticsearch与机器学习集成的最佳实践
【8月更文第28天】Elasticsearch 提供了强大的搜索和分析能力,而机器学习则能够通过识别模式和预测趋势来增强这些能力。将两者结合可以实现更智能的搜索体验、异常检测等功能。
442 0
|
搜索推荐 算法 大数据
基于内容的推荐系统算法详解
【7月更文挑战第14天】基于内容的推荐系统算法作为推荐系统发展的初期阶段的重要技术之一,具有其独特的优势和广泛的应用场景。然而,随着大数据和人工智能技术的发展,传统的基于内容的推荐系统已经难以满足日益复杂和多样化的推荐需求。因此,未来的推荐系统研究将更加注重多种推荐算法的融合与创新,以提供更加精准、个性化的推荐服务。
2279 2