Home > Software design >  How can I orginize code in the right way in my case? Golang
How can I orginize code in the right way in my case? Golang

Time:07-01

There are almost two identical functions that do approximately the same thing. What would be the right way to organize the code to avoid repetition in this case? The httpGetter() function accesses cloud platform API and gets JSON response, which I then parsed in another function and based on it I form terraform manifest from the template. The getToken() function does almost the same thing, just gets a token, which is then used in the httpGetter() function.

var accessToken = getToken()


func httpGetter(method, url string) (*http.Response, []byte) {
    client := &http.Client{}
    req, err := http.NewRequest(method, url, nil)
    if err != nil {
        fmt.Println(err)
    }
    req.Header.Add("Accept", "application/json;version=35.0")
    req.Header.Add("Authorization", "Bearer " accessToken)
    res, err := client.Do(req)
    if err != nil {
        fmt.Println(err)
    }
    defer res.Body.Close()
    body, err := ioutil.ReadAll(res.Body)
    if err != nil {
        fmt.Println(err)
    }
    return res, body
}

func getToken() string {
    url := "https://cloud-platform-api.com/api/sessions"
    method := "POST"
    client := &http.Client{}
    req, err := http.NewRequest(method, url, nil)
    if err != nil {
        fmt.Println(err)
    }
    req.Header.Add("Accept", "application/* xml;version=35.0")
    req.Header.Add("Authorization", "Basic <auth-hash>")
    res, err := client.Do(req)
    if err != nil {
        fmt.Println(err)
    }
    defer res.Body.Close()
    if err != nil {
        fmt.Println(err)
    }
    accessToken := res.Header.Get("x-vmware-vcloud-access-token")
    return accessToken
}

CodePudding user response:

First thing first if you know the method is a getter then you don't need to pass the method param so the signature would become something like below. Also, you are already returning a *http.Response back to the caller now it will be the callers decision on what to do with the response the caller should decide what to do in case of if the HTTP call fails so return error and let the caller decide.

func HttpGet(url string) (*http.Response, error)

Now you also want POST method with body (in some cases) so have another function

func HttpPost(URL string, body []byte) (*http.Response, error)

Now to manage both signature and have a common code you could have a private method that will be just used in this file or you could also expose that method (it is up to you)

type Headers map[string]string

func http(method, URL string, body []byte, headers Headers) (*http.Response, error) { // we pass headers here so that the caller can pass custom headers for request
    client := &http.Client{}
    req, err := http.NewRequest(method, url, body)
    if err != nil {
        return nil, err
    }

    req.Header.Add("Accept", "application/json;version=35.0") // common static header you can keep as it is

    for key, value := range headers {
       req.Header.Add(key, value)
    }
    
    return client.Do(req)
}

Using this your call from the two methods would look like

func HttpGet(url string, headers Headers) (*http.Response, error) {
  return http(http.MethodGet, URL, nil, headers)
}

func HttpPost(url string, body []byte, headers Headers) (*http.Response, error) {
  return http(http.MethodPost, url, body, headers)
}

Now you can use this to pass the auth token from the caller like:

func getToken() {
  res, err := httpPost("https://cloud-platform-api.com/api/sessions", nil, 
    map[string]string{
      "Authorization": "Basic <auth-hash>",
    }

  if err != nil {
    // do something with error
  }

  if res.StatusCode == http.StatusCreated {
     // do what you want with the success response like unmarshalling to JSON
  }
}

and for cases where you don't need to pass header, you can do

 res, err := httpGet("some-url", nil) // you pass header as nil
  •  Tags:  
  • go
  • Related