Go에서 REST API 클라이언트 작성
RESTAPI를 게시할 때 API 클라이언트가 유용합니다.Go의 상투적인 디자인과 유형 시스템 때문에 개발자와 사용자 모두가 쉽게 사용할 수 있습니다.그런데 어떤 것이 좋은 API 클라이언트를 정의했습니까?
이 강좌에서 Go에서 좋은 SDK를 작성한 모범 사례를 살펴보겠습니다.
우리는 예시로 Facest.io API 를 사용할 것이다.
코드를 작성하기 전에 API를 검토하여 다음과 같은 주요 사항을 파악해야 합니다.
기초부터 시작합시다.저장소를 만들고 정확한 이름을 선택하여 API 서비스 이름과 일치하는 것이 좋습니다.고 모듈을 초기화합니다.사용자의 특정한 정보를 저장하기 위해 우리의 주 구조를 만듭니다.이 구조는 나중에 API 끝점을 함수로 포함합니다.
이 구조는 유연해야 하지만 제한도 있기 때문에 사용자는 내부 필드를 볼 수 없다.
우리는 필드
BaseURL
와 HTTPClient
를 내보낼 수 있도록 하기 때문에 사용자는 필요할 때 자신의 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를 찾을 수 있습니다.
Reference
이 문제에 관하여(Go에서 REST API 클라이언트 작성), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/plutov/writing-rest-api-client-in-go-3fkg텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)