How to elegantly design an SDK


I believe that many development students must have heard of SDK. The full name of SDK is Software Development Kit, which is software development tool kit. It is a set of tools provided by the manufacturer of a hardware platform, operating system, or programming language to assist software developers in creating applications for a specific platform, system, or programming language. SDK is often used as a collection of development tools to create application software for a specific software package, software framework, hardware platform, operating system, etc.

Let me start by introducing some new ideas. Students who have written Java must have heard of JDK, which is only one word different from SDK. So what are the differences and connections between them? ?

First, the difference between SDK (Software Development Kit) and JDK (Java Development Kit):

  • Different definitions: SDK is a software development kit. It is a broad concept, including various APIs, libraries, documents, tools, etc., used to assist developers in developing specific type of application. JDK is the Java Development Kit, a type of SDK, specifically designed for the development of the Java language.

  • The content covered is different: JDK includes Java Runtime Environment (JRE), Java compiler (javac) and Java basic class library. The SDK may contain libraries, interfaces, documentation, sample code, etc. for specific programming languages or frameworks.

Secondly, the two of them also have the same things:

  • Both are development tools: Whether it is SDK or JDK, they are both development toolkits, which provide developers with a series of tools to help developers develop more efficiently.

  • Both can provide libraries and APIs: Both will provide library files and API interfaces. These library files and API interfaces encapsulate some underlying operations and provide higher-level operation interfaces, allowing developers to implement functions more easily.

In short, SDK is a collective term, while JDK is just an integrated development tool for Java and is a subset of SDK.

Next, we will start to understand the true role of the SDK, how to use it, and how to elegantly design an SDK.

1 The main function of SDK

The role of SDK (Software Development Kit) is mainly reflected in the following aspects:

  1. Provide API interfaces: SDK usually contains a set of API interfaces, which are predefined. Developers can call these interfaces to interact with the underlying system, thus simplifying the development process.
  2. Provide library files: SDK usually contains some library files, which contain a large number of functions and classes. Developers can use these functions and classes directly without writing them from scratch.
  3. Provide documentation and sample code: The SDK will also provide detailed development documentation and sample code to help developers understand and use API interfaces and library files.

In general, the role of the SDK is to help developers develop applications faster and more conveniently. By providing development tools, API interfaces, library files, documents and sample codes, the SDK reduces the difficulty of development and improves development efficiency.

2 SDK usage scenarios

The usage scenarios of SDK are very wide, mainly including the following aspects:

  1. Mobile application development: Whether it is Android or iOS platform, developers can use the corresponding SDK to build various mobile applications. For example, Android developers can use the Android SDK to access various hardware functions of the device, such as cameras, sensors, etc.; iOS developers can use the iOS SDK to take advantage of the features of Apple devices, such as Touch ID, Apple Pay, etc.
  2. Game development: Game developers can use the SDK provided by the game engine to build game applications. For example, developers can use Unity SDK to implement various functions in games, such as graphics rendering, physical simulation, audio processing, etc.
  3. Mini Program Development: Mini Program SDK is a development toolkit for developing and building mini program applications. Developers can use these APIs and components to build mini-program applications, such as adding functions to mini-programs, calling hardware devices, implementing interactions, etc. Using the Mini Program SDK can speed up the development and deployment process of Mini Programs, and improve the stability and performance of Mini Programs.
  4. Website development: In website development, SDK can serve as a bridge between the website and third-party services. For example, a payment SDK allows a website to easily integrate payment functions without having to develop a complex payment system on its own. Similarly, social SDK allows websites to integrate social network functions, such as user login, sharing, etc.
  5. Cloud service: The SDK of cloud service provides developers with a way to interact with cloud services. For example, developers can use AWS SDK (Amazon Web Services Development Kit) to call Amazon’s various cloud services, such as computing, storage, database, analysis, etc. Similarly, the Google Cloud SDK and Azure SDK also provide developers with the ability to interact with Google and Microsoft’s cloud services.
  6. Embedded system development: In the development process of embedded systems, SDK is often used to provide hardware abstraction layers, drivers and development tools. Through the embedded SDK, developers can write, debug and deploy applications more conveniently, reducing development difficulty and complexity. For example, developers of smart home appliances can use SDKs to simplify interaction with device sensors and implement remote control and other functions.
  7. Internet of Things (IoT) development: The development of the Internet of Things has made interconnection between devices necessary, and SDK plays an important role in the development of the Internet of Things. IoT SDK usually includes device connection, data transmission, security management and other functions to help developers quickly build IoT applications. By using the IoT SDK, developers can connect devices to the cloud platform to achieve remote monitoring, data analysis, intelligent control and other functions.

In general, the usage scenarios of the SDK are quite extensive, covering almost all aspects of software development. However, you still have to choose according to actual needs when using it. These are just some typical usage scenarios of the SDK. In fact, as long as a specific function needs to be encapsulated for developers to use, the SDK may be used.

3 Elegantly design an SDK

The design process of Go language SDK can generally be divided into the following steps:

  1. Requirements analysis: Before starting to design the SDK, you first need to clarify the needs and goals of the SDK.
  2. Interface design: After clarifying the requirements, start designing the interface of the SDK. Interface design should be concise and clear, provide clear input and output, and follow consistent naming conventions and design principles.
  3. Code implementation: Based on the interface design, start writing the SDK code. When writing code, follow the best practices of the Go language to ensure the readability, maintainability and performance of the code. Also, have appropriate error handling and logging to facilitate debugging and troubleshooting.
  4. Unit testing and integration testing: Write unit tests and integration tests to verify the correctness and stability of the SDK.
  5. Documentation: Write clear and detailed documentation for the SDK. Documentation should include interface description, parameter description, return value description, error handling, sample code, etc.
  6. Version release and iteration: After completing code implementation, testing and document writing, the SDK version can be released. Follow semantic versioning specifications to ensure version compatibility and stability.

Below we will design a simple SDK using an HTTP service as an example.

3.1 First write a Go HTTP service
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) {<!-- -->
v t T
if err := c.BindJSON( & amp;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 Understand how to call service API

Without SDK, we try to write code to call the interface:

func TestCreateAPI(t *testing.T) {<!-- -->
//Create an HTTP client
client := & amp;http.Client{<!-- -->}

//Create the body of the POST request
reqData := []byte(`{"key":"A","val":"1"}`)

// Create a POST request
req, err := http.NewRequest("POST", "http://localhost:9999/create", bytes.NewBuffer(reqData))
if err != nil {<!-- -->
fmt.Println("An error occurred while creating the request:", err)
return
}

//Set request headers
req.Header.Set("Content-Type", "application/json")
req.Header.Set("name", "barry yan")

//Send request and get response
resp, err := client.Do(req)
if err != nil {<!-- -->
fmt.Println("An error occurred while sending the request:", err)
return
}
defer func() {<!-- -->
_ = resp.Body.Close()
}()

//Read response content
body, err := io.ReadAll(resp.Body)
if err != nil {<!-- -->
fmt.Println("An error occurred while reading the response:", err)
return
}

//Print response content
fmt.Println(string(body))
}

func TestGetAPI(t *testing.T) {<!-- -->
//Create an HTTP client
client := & amp;http.Client{<!-- -->}

// Create a GET request
req, err := http.NewRequest("GET", "http://localhost:9999/get/A", nil)
if err != nil {<!-- -->
fmt.Println("An error occurred while creating the request:", err)
return
}
req.Header.Set("name", "barry yan")

//Send request and get response
resp, err := client.Do(req)
if err != nil {<!-- -->
fmt.Println("An error occurred while sending the request:", err)
return
}
defer func() {<!-- -->
_ = resp.Body.Close()
}()

//Read the content of the response
body, err := io.ReadAll(resp.Body)
if err != nil {<!-- -->
fmt.Println("An error occurred while reading the response:", err)
return
}

//Print response content
fmt.Println(string(body))
}

The disadvantages of this method are obvious, such as:

(1) There are no fixed specifications for the definition of request parameters and return values.

(2) Too many repeated codes

(3) It is difficult to decouple when the call chain is complex

Based on this, we design an SDK specifically for calling the interface of the system API.

3.3 Design API SDK

Let’s first encapsulate the way Go calls the HTTP interface:

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 := & amp;HttpClient{<!-- -->Url: url, Client: & amp;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
}

Next, the core code of our sdk is to encapsulate our business interface calling method:

(1) Define a unified request body structure and error code:

Request body:

type CreateRequest struct {<!-- -->
   Key string `json:"key,omitempty"`
   Val string `json:"val,omitempty"`
}

error code:

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 core methods

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 := & amp;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 := & amp;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 usage examples
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 & amp; & amp; 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 & amp; & amp; newSDK != nil {<!-- -->
      return
   }
   resp, err2 := newSDK.Get("D")
   if err2.Code != http.StatusOK {<!-- -->
      fmt.Println(err2)
   }
   fmt.Println(resp)
}

Look, does it feel like there is too much less code?

4 Summary

At this point, you may have questions as to why NewSDK also contains the two parameters username and passwd in addition to host.

In fact, it is mainly because the system generally has an Auth authentication process, which is mainly used to authenticate whether the caller is a legal user of the system. The header (name=barry yan) in the API is also used to authenticate the user. Of course, it must actually be It is much more complicated than this. The SDK will also encapsulate the Auth authentication method.

In addition, due to time constraints, the SDK case design in this article is indeed too simple. I hope you will not copy and imitate it in real production projects. Here are some better SDK designs:

  • aws-sdk-go
  • tencentcloud-sdk-go
  • alibaba-cloud-sdk-go

All the code of this article has been packaged and uploaded to Github. Everyone is welcome to raise issues. How to obtain the code: Follow the official account [Plain programming] and reply [sdk]