Go에서 REST API 클라이언트 작성

22095 단어 gorestapi

RESTAPI를 게시할 때 API 클라이언트가 유용합니다.Go의 상투적인 디자인과 유형 시스템 때문에 개발자와 사용자 모두가 쉽게 사용할 수 있습니다.그런데 어떤 것이 좋은 API 클라이언트를 정의했습니까?
이 강좌에서 Go에서 좋은 SDK를 작성한 모범 사례를 살펴보겠습니다.
우리는 예시로 Facest.io API 를 사용할 것이다.
코드를 작성하기 전에 API를 검토하여 다음과 같은 주요 사항을 파악해야 합니다.
  • API의 기본 URL은 무엇입니까? 나중에 변경할 수 있습니까?
  • 버전 제어를 지원합니까?
  • 가능한 오류는 무엇입니까?
  • 고객은 어떻게 인증을 해야 합니까?
  • 이 모든 것을 이해하는 것은 당신이 정확한 구조를 세우는 데 도움이 될 것이다.
    기초부터 시작합시다.저장소를 만들고 정확한 이름을 선택하여 API 서비스 이름과 일치하는 것이 좋습니다.고 모듈을 초기화합니다.사용자의 특정한 정보를 저장하기 위해 우리의 주 구조를 만듭니다.이 구조는 나중에 API 끝점을 함수로 포함합니다.
    이 구조는 유연해야 하지만 제한도 있기 때문에 사용자는 내부 필드를 볼 수 없다.
    우리는 필드 BaseURLHTTPClient 를 내보낼 수 있도록 하기 때문에 사용자는 필요할 때 자신의 HTTP 클라이언트를 사용할 수 있다.
    package facest
    
    import (
        "net/http"
        "time"
    )
    
    const (
        BaseURLV1 = "https://api.facest.io/v1"
    )
    
    type Client struct {
        BaseURL    string
        apiKey     string
        HTTPClient *http.Client
    }
    
    func NewClient(apiKey string) *Client {
        return &Client{
            BaseURL: BaseURLV1,
            apiKey:  apiKey,
            HTTPClient: &http.Client{
                Timeout: time.Minute,
            },
        }
    }
    
    이제 "Get Faces"노드를 계속 실현합시다. 결과 목록을 되돌려주고 페이지를 지원합니다. 이것은 함수가 페이지 옵션을 입력으로 지원해야 한다는 것을 의미합니다.
    API에서 알 수 있듯이 성공 응답과 오류 응답은 항상 같은 구조를 따르기 때문에 데이터 형식과 구분하여 정의할 수 있고 사용자와 무관하기 때문에 내보내지 마십시오.
    type errorResponse struct {
        Code    int    `json:"code"`
        Message string `json:"message"`
    }
    
    type successResponse struct {
        Code int         `json:"code"`
        Data interface{} `json:"data"`
    }
    
    모든 끝점을 같은 위치에 쓰지 않도록 하십시오.파일로 이동하지만 그룹화하고 별도의 파일을 사용합니다.예를 들어, 리소스 유형별로 그룹화할 수 있으며 /v1/faces 로 시작하는 모든 컨텐트가 faces.go 파일에 들어갑니다.
    저는 보통 정의 형식부터 시작해서 수동으로 완성할 수도 있고 JSON-to-Go tool 을 사용하여 JSON을 go로 변환할 수도 있습니다.
    package facest
    
    import "time"
    
    type FacesList struct {
        Count      int    `json:"count"`
        PagesCount int    `json:"pages_count"`
        Faces      []Face `json:"faces"`
    }
    
    type Face struct {
        FaceToken  string      `json:"face_token"`
        FaceID     string      `json:"face_id"`
        FaceImages []FaceImage `json:"face_images"`
        CreatedAt  time.Time   `json:"created_at"`
    }
    
    type FaceImage struct {
        ImageToken string    `json:"image_token"`
        ImageURL   string    `json:"image_url"`
        CreatedAt  time.Time `json:"created_at"`
    }
    
    GetFaces 함수는 페이지를 지원해야 합니다. 우리는func 파라미터를 추가해서 실현할 수 있지만, 이 파라미터들은 선택할 수 있습니다. 나중에 변경될 수 있습니다.따라서 특수한 구조로 그룹화하는 것은 의미가 있다.
    type FacesListOptions struct {
        Limit int `json:"limit"`
        Page  int `json:"page"`
    }
    
    우리 함수가 지원해야 할 또 다른 매개 변수는 상하문입니다. 사용자가 API 호출을 제어할 수 있도록 합니다.사용자는 우리의func에 전달할 상하문을 만들 수 있습니다.간단한 예: 5초 이상 걸리면 API 호출을 취소합니다.
    이제 기능 프레임워크는 다음과 같습니다.
    func (c *Client) GetFaces(ctx context.Context, options *FacesListOptions) (*FacesList, error) {
        return nil, nil
    }
    
    API 호출이 필요한 시점입니다.
    func (c *Client) GetFaces(ctx context.Context, options *FacesListOptions) (*FacesList, error) {
        limit := 100
        page := 1
        if options != nil {
            limit = options.Limit
            page = options.Page
        }
    
        req, err := http.NewRequest("GET", fmt.Sprintf("%s/faces?limit=%d&page=%d", c.BaseURL, limit, page), nil)
        if err != nil {
            return nil, err
        }
    
        req = req.WithContext(ctx)
    
        res := FacesList{}
        if err := c.sendRequest(req, &res); err != nil {
            return nil, err
        }
    
        return &res, nil
    }
    
    func (c *Client) sendRequest(req *http.Request, v interface{}) error {
        req.Header.Set("Content-Type", "application/json; charset=utf-8")
        req.Header.Set("Accept", "application/json; charset=utf-8")
        req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey))
    
        res, err := c.HTTPClient.Do(req)
        if err != nil {
            return err
        }
    
        defer res.Body.Close()
    
        if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
            var errRes errorResponse
            if err = json.NewDecoder(res.Body).Decode(&errRes); err == nil {
                return errors.New(errRes.Message)
            }
    
            return fmt.Errorf("unknown error, status code: %d", res.StatusCode)
        }
    
        fullResponse := successResponse{
            Data: v,
        }
        if err = json.NewDecoder(res.Body).Decode(&fullResponse); err != nil {
            return err
        }
    
        return nil
    }
    
    모든 API 끝점의 동작 방식이 같기 때문에 코드 중복을 피하기 위해 보조 함수sendRequest를 만들었습니다.이것은 공공 헤더 (내용 유형, 인증 헤더) 를 설정하고 요청을 보내며 오류를 검사하고 응답을 해석합니다.
    상태 코드 <200과 >=400을 오류로 간주하고 응답을 errorResponse 로 분석합니다.그러나 API 설계에 따라 API가 다른 방식으로 오류를 처리할 수 있습니다.

    테스트


    이제 이 예에서는 단일 API 노드를 포함하는 SDK가 충분하지만 사용자에게 보내기에는 충분합니까?그럴지도 모르지만, 더 많은 것에 관심을 가져봅시다.
    여기에는 거의 테스트가 필요하다. 두 가지 유형이 있는데 그것이 바로 단원 테스트와 집적 테스트이다.두 번째는 real API를 호출합니다.간단한 테스트를 작성합시다.
    // +build integration
    
    package facest
    
    import (
        "os"
        "testing"
    
        "github.com/stretchr/testify/assert"
    )
    
    func TestGetFaces(t *testing.T) {
        c := NewClient(os.Getenv("FACEST_INTEGRATION_API_KEY"))
    
        ctx := context.Background()
        res, err := c.GetFaces(nil)
    
        assert.Nil(t, err, "expecting nil error")
        assert.NotNil(t, res, "expecting non-nil result")
    
        assert.Equal(t, 1, res.Count, "expecting 1 face found")
        assert.Equal(t, 1, res.PagesCount, "expecting 1 PAGE found")
    
        assert.Equal(t, "integration_face_id", res.Faces[0].FaceID, "expecting correct face_id")
        assert.NotEmpty(t, res.Faces[0].FaceToken, "expecting non-empty face_token")
        assert.Greater(t, len(res.Faces[0].FaceImages), 0, "expecting non-empty face_images")
    }
    
    이 테스트는 env를 사용합니다.API 키의 변수를 설정합니다.이렇게 함으로써 우리는 그들이 공개하지 않을 것을 확보한다.잠시 후, 우리는 이 env를 전파하기 위해 구축 시스템을 설정할 수 있습니다.var 기밀 사용.
    또한 이러한 테스트는 단원 테스트와 분리됩니다.
    go test -v -tags=integration
    

    문서


    당신의 SDK는 명확한 유형과 추상성을 가지고 있기 때문에 너무 많은 정보를 노출할 필요가 없습니다.일반적으로 링크 godoc 를 주요 문서로 제공하면 충분합니다.

    호환성 및 버전 제어


    새 semver를 저장소에 게시하여 SDK 버전을 업데이트합니다.그러나 새로운 부차적인/패치 버전을 파괴하지 않았는지 확인해야 한다.일반적으로 SDK 라이브러리는 API 업데이트를 따라야 하기 때문에 API가 v2를 발표하면 SDK v2 버전도 있어야 합니다.

    결론


    그렇습니다.
    그런데 질문이 하나 있습니다. 지금까지 가장 잘 본 API Go 클라이언트는 무엇입니까?댓글로 공유해 주세요.
    전체 소스 코드here를 찾을 수 있습니다.

    좋은 웹페이지 즐겨찾기