Prueba de código intensivo de datos con Go, Parte 3

Visión general

Esta es la tercera parte de cinco en una serie de tutoriales sobre pruebas de código de uso intensivo de datos con Go. En la segunda parte, cubrí las pruebas con una capa de datos en memoria real basada en el popular SQLite. En este tutorial, revisaré las pruebas en una capa de datos complejos locales que incluye una base de datos relacional y un caché Redis.

Pruebas contra una capa de datos local

La prueba contra una capa de datos en memoria es impresionante. Las pruebas son muy rápidas y usted tiene control total. Pero a veces necesita estar más cerca de la configuración real de su capa de datos de producción. Aquí hay algunas razones posibles:

  • Utiliza detalles específicos de su DB relacional que desea probar.
  • Su capa de datos consta de varios almacenes de datos interactivos.
  • El código bajo prueba consiste en varios procesos que acceden a la misma capa de datos..
  • Desea preparar u observar sus datos de prueba utilizando herramientas estándar.
  • No desea implementar una capa de datos en memoria dedicada si su capa de datos está en flujo.
  • Solo quieres saber que estás probando contra tu capa de datos real.
  • Necesitas probar con una gran cantidad de datos que no caben en la memoria.

Estoy seguro de que hay otras razones, pero puede ver por qué solo usar una capa de datos en la memoria para las pruebas puede no ser suficiente en muchos casos.

DE ACUERDO. Así que queremos probar una capa de datos real. Pero todavía queremos ser lo más ligero y ágil posible. Eso significa una capa de datos local. Aquí están los beneficios:

  • No es necesario aprovisionar ni configurar nada en el centro de datos o en la nube..
  • No hay que preocuparse por que nuestras pruebas corrompan los datos de producción por accidente.
  • No es necesario coordinar con otros desarrolladores en un entorno de prueba compartido. 
  • No hay lentitud en las llamadas de red..
  • Control total sobre el contenido de la capa de datos, con la capacidad de comenzar desde cero en cualquier momento.  

En este tutorial subiremos la apuesta. Implementaremos (muy parcialmente) una capa de datos híbrida que consta de una base de datos relacional MariaDB y un servidor Redis. Luego usaremos Docker para crear una capa de datos local que podamos usar en nuestras pruebas.. 

Uso de Docker para evitar dolores de cabeza de instalación

Primero, necesitas a Docker, por supuesto. Revisa la documentación si no estás familiarizado con Docker. El siguiente paso es obtener imágenes para nuestros almacenes de datos: MariaDB y Redis. Sin entrar en demasiados detalles, MariaDB es un gran DB relacional compatible con MySQL, y Redis es un gran almacén de valor-clave en memoria (y mucho más). 

> docker pull mariadb…> docker pull redis…> imágenes docker REPOSITORY TAG ID DE IMAGEN CREADO TAMAÑO mariadb última 51d6a5e69fa7 hace 2 semanas 402MB redis última b6dddb991dfa hace 2 semanas 107MB 

Ahora que tenemos Docker instalado y tenemos las imágenes para MariaDB y Redis, podemos escribir un archivo docker-compose.yml, que usaremos para iniciar nuestros almacenes de datos. Llamemos a nuestro DB "songify".

mariadb-songify: imagen: mariadb: último comando:> --general-log --general-log-file = / var / log / mysql / query.log expose: - "3306" puertos: - "3306: 3306" entorno : MYSQL_DATABASE: "songify" MYSQL_ALLOW_EMPTY_PASSWORD: "true" volume_from: - mariadb-data mariadb-data: image: mariadb: últimos volúmenes: - / var / lib / mysql entrypoint: / bin / bash redis: image: redis expose: - " 6379 "puertos: -" 6379: 6379 " 

Puede iniciar sus almacenes de datos con el docker-componer hasta comando (similar a vagabundo). La salida debería verse así: 

> docker-compose up Iniciando hybridtest_redis_1 ... Iniciando hybridtest_mariadb-data_1 ... Iniciando hybridtest_redis_1 Iniciando hybridtest_mariadb-data_1 ... listo Comenzando híbridotest_mariaadillaAparticación_corazonAparte_mariatota_maria-tat_mariad_mariad__comeridad_comerida_corazon-Perigualidad_todas_corazon_todas_comeridad_corazamiento * DB cargada desde el disco: 0,002 segundos redis_1 | * Listo para aceptar conexiones ... mariadb-songify_1 | [Nota] mysqld: listo para conexiones ... 

En este punto, tiene un servidor MariaDB completo que escucha en el puerto 3306 y un servidor Redis que escucha en el puerto 6379 (ambos son los puertos estándar).

La capa de datos híbrida

Aprovechemos estos potentes almacenes de datos y actualicemos nuestra capa de datos a una capa de datos híbrida que almacena canciones por usuario en Redis. Cuando GetSongsByUser ()se llama, la capa de datos primero verificará si Redis ya almacena las canciones para el usuario. Si lo hace, simplemente devuelve las canciones de Redis, pero si no lo hace (falta de caché), recuperará las canciones de MariaDB y llenará el caché de Redis, así que estará listo para la próxima vez.. 

Aquí está la definición de la estructura y el constructor. La estructura mantiene un identificador de base de datos como antes y también un cliente redis. El constructor se conecta a la base de datos relacional, así como a Redis. Crea el esquema y borra los redis solo si los parámetros correspondientes son verdaderos, lo cual es necesario solo para la prueba. En producción, creas el esquema una vez (ignorando las migraciones de esquema).

escriba HybridDataLayer struct db * sql.DB redis * redis.Client func NewHybridDataLayer (dbHost string, dbPort int, redisHost string, createSchema bool, clearRedis bool) (* HybridDataLayer, error) dnn :. tcp (% s:% d) / ", dbHost, dbPort) si createSchema err: = createMariaDBSchema (dsn) if err! = nil return nil, err db, err: = sql.Open (" mysql ", dsn + "desongcious? parseTime = true") if err! = nil return nil, err redisClient: = redis.NewClient (& redis.Options Addr: redisHost + ": 6379", Password: "", DB: 0, ) _, err = redisClient.Ping (). Result () if err! = nil return nil, err si clearRedis redisClient.FlushDB () return & HybridDataLayer db, redisClient, nil

Usando MariaDB

MariaDB y SQLite son un poco diferentes en lo que respecta a DDL. Las diferencias son pequeñas, pero importantes. Go no tiene un conjunto de herramientas maduro de bases de datos cruzadas como el fantástico SQLAlchemy de Python, por lo que debe administrarlo usted mismo (no, Gorm no cuenta). Las principales diferencias son:

  • El controlador SQL es "github.com/go-sql-driver/mysql".
  • La base de datos no vive en la memoria, por lo que se vuelve a crear cada vez (soltar y crear). 
  • El esquema debe ser una porción de sentencias DDL independientes en lugar de una cadena de todas las sentencias.
  • Las claves primarias de incremento automático están marcadas por AUTOINCREMENTO.
  • VARCHAR en lugar de TEXTO.

Aquí está el código:

func createMariaDBSchema (dsn string) error db, err: = sql.Open ("mysql", dsn) if err! = nil return err // Volver a crear los comandos DB: = [] string "DROP DATABASE songify;", "CREATE DATABASE songify;", para _, s: = rango (comandos) _, err = db.Exec (s) if err! = Nil return err // Crear db del esquema, err = sql.Open ("mysql", dsn + "songify? parseTime = true") if err! = nil return err schema: = [] string 'CREATE TABLE IF NOT EXISTS song (id INTEGER PRIMARY KEY AUTO_INCREMENT, url VARCHAR (2088) UNIQUE , título VARCHAR (100), descripción VARCHAR (500)); ',' CREAR TABLA SI NO EXISTE usuario (id INTEGER PRIMARY KEY AUTO_INCREMENT, nombre VARCHAR (100), correo electrónico VARCHAR (100) ÚNICO, registrado en TIMESTAMP, last_login TIMESTAMP); ', "CREAR ÍNDICE user_email_idx ON usuario (correo electrónico);",' CREAR TABLA SI NO EXISTE la etiqueta (ID INTEGER PRIMARY KEY AUTO_INCREMENT, nombre VARCHAR (100) UNIQUE); ', "CREATE INDEX label_name_idx ON label (nombre);", 'CREAR TABLA SI NO EXISTE label_song (label_id INTEGER NOT NULL REFE RENCES label (id), song_id INTEGER NOT NULL REFERENCES song (id), PRIMARY KEY (label_id, song_id)); ',' CREATE TABLE SI NO EXISTE user_song (user_id INTEGER NOT NULL REFERENCES usuario (id), song_id INTEGER NOT NULL REFERENCES song (id), PRIMARY KEY (user_id, song_id)); ', para _, s: = range (schema) _, err = db.Exec (s) if err! = nil return err return nil 

Usando redis

Redis es muy fácil de usar desde Go. La biblioteca del cliente "github.com/go-redis/redis" es muy intuitiva y sigue fielmente los comandos de Redis. Por ejemplo, para probar si existe una clave, simplemente use la Salidas () Método del cliente redis, que acepta una o más claves y devuelve cuántas de ellas existen.. 

En este caso, compruebo una sola clave:

 count, err: = m.redis.Exists (correo electrónico) .Resultar () si err! = nil return err

Prueba de acceso a múltiples almacenes de datos

Las pruebas son en realidad idénticas. La interfaz no cambió, y el comportamiento no cambió. El único cambio es que la implementación ahora mantiene un caché en Redis. los GetSongsByEmail () método ahora solo llama refreshUser_Redis ().

func (m * HybridDataLayer) GetSongsByUser (u User) (canciones [] Song, error err) err = m.refreshUser_Redis (u.Email, & songs) return 

los refreshUser_Redis () El método devuelve las canciones de usuario de Redis si existen y, de lo contrario, las obtiene de MariaDB..

tipo Canciones * [] Función de canción (m * HybridDataLayer) refreshUser_Redis (cadena de correo electrónico, canciones de salida) error cuenta, err: = m.redis.Exists (correo electrónico) .Resultar () si err err = nil return err si cuenta == 0 err = m.getSongsByUser_DB (correo electrónico, fuera) si err! = Nil return err para _, song: = range * out s, err: = serializeSong (song) si err! = Nil return err  _, err = m.redis.SAdd (email, s) .Result () si err! = nil return err return miembros, err: = m.redis.SMembers (email) .Result () para _ , miembro: = rango de miembros canción, err: = deserializeSong ([] byte (miembro)) si err! = nil return err * out = append (* out, song) return out, nil 

Hay un pequeño problema aquí desde el punto de vista de la metodología de prueba. Cuando probamos a través de la interfaz de la capa de datos abstracta, no tenemos visibilidad de la implementación de la capa de datos.

Por ejemplo, es posible que exista una gran falla en la capa de datos que omite completamente el caché y siempre obtiene los datos de la base de datos. Las pruebas pasarán, pero no podemos beneficiarnos del caché. Hablaré en la parte cinco acerca de probar su caché, lo cual es muy importante..  

Conclusión

En este tutorial, cubrimos las pruebas en una capa de datos complejos locales que consta de múltiples almacenes de datos (una base de datos relacional y un caché de Redis). También utilizamos Docker para implementar fácilmente múltiples almacenes de datos para pruebas.

En la parte cuatro, nos centraremos en las pruebas contra los almacenes de datos remotos, utilizando instantáneas de los datos de producción para nuestras pruebas y también generando nuestros propios datos de prueba. Manténganse al tanto!