Applying constructor injection

Let's apply constructor injection to our ACME registration service. This time we will be refactoring the REST package, starting with the Register endpoint. You may remember that Register is one of three endpoints in our service, the others being Get and List.
The Register endpoint has three responsibilities:

  • Validate the registration is complete and valid
  • Call the currency conversion service to convert the registration price to the currency requested in the registration
  • Save the registration and the converted registration price into the database

The code for our Register endpoint currently looks as shown in the following code:

// RegisterHandler is the HTTP handler for the "Register" endpoint
// In this simplified example we are assuming all possible errors
// are user errors and returning "bad request" HTTP 400.
// There are some programmer errors possible but hopefully these
// will be caught in testing.
type RegisterHandler struct {
}

// ServeHTTP implements http.Handler
func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
// extract payload from request
requestPayload, err := h.extractPayload(request)
if err != nil {
// output error
response.WriteHeader(http.StatusBadRequest)
return
}

// register person
id, err := h.register(requestPayload)
if err != nil {
// not need to log here as we can expect other layers to do so
response.WriteHeader(http.StatusBadRequest)
return
}

// happy path
response.Header().Add("Location", fmt.Sprintf("/person/%d/", id))
response.WriteHeader(http.StatusCreated)
}

// extract payload from request
func (h *RegisterHandler) extractPayload(request *http.Request) (*registerRequest, error) {
requestPayload := &registerRequest{}

decoder := json.NewDecoder(request.Body)
err := decoder.Decode(requestPayload)
if err != nil {
return nil, err
}

return requestPayload, nil
}

// call the logic layer
func (h *RegisterHandler) register(requestPayload *registerRequest) (int, error) {
person := &data.Person{
FullName: requestPayload.FullName,
Phone: requestPayload.Phone,
Currency: requestPayload.Currency,
}

registerer := &register.Registerer{}
return registerer.Do(person)
}

Disappointingly, we currently only have one test on this function, and it breaks way too easily. It requires both the database and our downstream exchange rate service to be accessible and configured.

While we can ensure our that local database is working, and any changes to it do not affect anyone but us, the downstream exchange rate service is on the internet and is rate limited. We have no control over it or when it works.

This means that even though we only have one test, that test has a high potential to be annoying to run and maintain because it can break at any time for reasons outside our control.

Fortunately, we can not only remove these dependencies but also use mocks to create situations that we could not otherwise. For example, with mocks, we can test our error handling code for when the exchange rate service is down or out of quota.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.163.238