Premature future-proofing

Sometimes, the application of DI is not wrong, but just unnecessary. A common manifestation of this is premature future-proofing. Premature future-proofing occurs when we add features to software that we don't yet need, based on the assumption that we might need it one day. As you might expect, this results in unnecessary work and complexity.

Let's look at an example by borrowing from our example service. Currently, we have a Get endpoint, as shown in the following code:

// GetHandler is the HTTP handler for the "Get Person" endpoint
type GetHandler struct {
cfg GetConfig
getter GetModel
}

// ServeHTTP implements http.Handler
func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
// extract person id from request
id, err := h.extractID(request)
if err != nil {
// output error
response.WriteHeader(http.StatusBadRequest)
return
}

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

// happy path
err = h.writeJSON(response, person)
if err != nil {
response.WriteHeader(http.StatusInternalServerError)
}
}

// output the supplied person as JSON
func (h *GetHandler) writeJSON(writer io.Writer, person *get.Person) error {
output := &getResponseFormat{
ID: person.ID,
FullName: person.FullName,
Phone: person.Phone,
Currency: person.Currency,
Price: person.Price,
}

return json.NewEncoder(writer).Encode(output)
}

It's a simple REST endpoint that returns JSON. If we decided that, one day, we might want to output in a different format, we could move the encoding to a dependency, as shown in the following example:

// GetHandler is the HTTP handler for the "Get Person" endpoint
type GetHandler struct {
cfg GetConfig
getter GetModel
formatter Formatter
}

// ServeHTTP implements http.Handler
func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
// no changes to this method
}

// output the supplied person
func (h *GetHandler) buildOutput(writer io.Writer, person *Person) error {
output := &getResponseFormat{
ID: person.ID,
FullName: person.FullName,
Phone: person.Phone,
Currency: person.Currency,
Price: person.Price,
}

// build output payload
payload, err := h.formatter.Marshal(output)
if err != nil {
return err
}

// write payload to response and return
_, err = writer.Write(payload)
return err
}

That code looks reasonable. So, where is the problem? Simply put, it's work we didn't need to do.

By extension, it's code that we didn't need to write or maintain. In this simple example, our changes only added a small amount of extra complexity and this is relatively common. This small amount of additional complexity multiplied across the entire system would slow us down.

If this should ever become an actual requirement, then this is absolutely the right way to deliver the feature, but at that point, it's a feature and therefore a burden that we must have.

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

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