Construyendo aplicaciones Web con Go

Controladores

Los controladores son un tema bastante familiar en otras comunidades de desarrollo web. Como la mayoría de los desarrolladores web está alrededor de una interfaz de poderosos red/http, ni muchas implementaciones del regulador han pegado fuerte. Sin embargo, hay un gran beneficio en el uso de un modelo controlador. Permite limpias y bien definidas abstracciones más allá de lo de la interfaz de controlador de red/http solo puede proporcionar.

Dependencias con Handler

En este ejemplo vamos a experimentar con la construcción de nuestra propia implementación de controlador utilizando algunas de las características estándar en Go. Pero primero, vamos a empezar con los problemas que estamos tratando de resolver.

Digamos que estamos usando el paquete render de la biblioteca estándar del que hablamos en capítulos anteriores:

var render = render.New(render.Options{})

Si queremos que nuestros http.Handlers puedan acceder a nuestra instancia render.Render, tenemos un par de opciones.

  1. Utilizar una variable global: Esto no es demasiado malo para programas pequeños, pero cuando el programa crece esto rápidamente se convierte en una pesadilla de mantenimiento.
  2. Pasar la variable a través de un cierre al http.Handler: Esta es una gran idea, y la deberíamos estar usando la mayor parte del tiempo.

La aplicación termina pareciéndose a esto:

func MiControlador (r *render.Render) http.Handler {
   return http.HandlerFunc(func (rw http.ResponseWriter, r *http.Request) {
     // ahora podemos acceder a 'r'
  })
}

Caso para controladores

Cuando el programa crece en tamaño, comenzarás a notar que muchos de tus http.Handlers compartirán las mismas dependencias y tendrás una gran cantidad de estos cierres http.Handlers con los mismos argumentos. La forma en que me gusta limpiar esto es escribiendo una implementación del controlador base que me ofrezca un par de pequeñas victorias:

  1. Me permite compartir las dependencias entre http.Handlers que tienen objetivos o conceptos similares.
  2. Evita las variables globales y funciones para facilitar las pruebas/simulaciones.
  3. Me proporciona un mecanismo más centralizada e idiomatico de Go -tal como el manejo de errores.

¡Gran parte de los controladores nos proporcionan todas estas cosas sin importar un paquete externo! La mayor parte de esta funcionalidad viene de un uso inteligente del conjunto de características Go, a saber estructuras Go e incrustación. Echemos un vistazo a la implementación.

package main

import "net/http"

// Acción define una firma de función estándar para que la podamos utilizar
// al crear acciones del controlador.  Una acción del controlador básicamente
// es un método asociado a un controlador.
type Acción func(rw http.ResponseWriter, r *http.Request) error

// Este es nuestro controlador base
type ControladorAplic struct {}

// La función Acción ayuda con el manejo de errores en un controlador
func (c *ControladorAplic) Acción(a Acción) http.Handler {
     return http.HandlerFunc(func (rw http.ResponseWriter, r *http.Request) {
         if err := a(rw, r); err != nil {
            http.Error(rw, err.Error(), 500)
        }
    })
}

¡Eso es todo! Esa es toda la aplicación en que debemos tener el poder de los controladores a nuestro alcance. Todo lo que resta por hacer es implementar un controlador de ejemplo:

package main

import (
     "net/http"

    "gopkg.in/unrolled/render.v1"
)

type miControlador struct {
    ControladorAplic
    *render.Render
}

func (c *miControlador) Índice(rw http.ResponseWriter, r *http.Request) error {
    c.JSON(rw, 200, map[string]string {"Hola": "JSON"})
     return nil
}

func main() {
    c := &miControlador {Render: render.New(render.Options{})}
    http.ListenAndServe(":8080", c.Acción(c.Índice))
}

Ejercicios

  1. Extiende miControlador para que tenga múltiples acciones para diferentes rutas de la aplicación.
  2. Juega con más implementaciones de controladores, sé creativo.
  3. Anula el método Acción en miControlador al presentar una página de error HTML.