Ceci est la troisième partie sur cinq d'une série de tutoriels sur le test de code gourmand en données avec Go. Dans la deuxième partie, j'ai couvert les tests sur une véritable couche de données en mémoire basée sur le populaire SQLite. Dans ce tutoriel, je vais passer en revue une couche de données complexe locale comprenant une base de données relationnelle et un cache Redis..
Tester avec une couche de données en mémoire est génial. Les tests sont rapides, et vous avez le contrôle total. Mais parfois, vous devez être plus proche de la configuration réelle de votre couche de données de production. Voici quelques raisons possibles:
Je suis sûr qu'il y a d'autres raisons, mais vous pouvez comprendre pourquoi l'utilisation d'une couche de données en mémoire pour les tests peut ne pas suffire dans de nombreux cas..
D'ACCORD. Nous voulons donc tester une couche de données réelle. Mais nous voulons toujours être aussi légers et agiles que possible. Cela signifie une couche de données locale. Voici les avantages:
Dans ce tutoriel, nous allons monter la barre. Nous allons implémenter (très partiellement) une couche de données hybride composée d'une base de données relationnelle MariaDB et d'un serveur Redis. Ensuite, nous utiliserons Docker pour créer une couche de données locale pouvant être utilisée dans nos tests..
Tout d'abord, vous avez besoin de Docker, bien sûr. Consultez la documentation si vous ne connaissez pas Docker. La prochaine étape consiste à obtenir des images pour nos magasins de données: MariaDB et Redis. Sans entrer dans les détails, MariaDB est une excellente base de données relationnelle compatible avec MySQL, et Redis est une excellente base de données clé-valeur en mémoire (et bien plus encore)..
> docker pull mariadb…> docker pull redis…> images de docker REPOSITORY TAG ID IMAGE CREE TAILLE mariadb dernier 51d6a5e69fa7 il y a 2 semaines 402MB dernier est b6dddb991dfa il y a 2 semaines 107MB
Maintenant que Docker est installé et que nous avons les images pour MariaDB et Redis, nous pouvons écrire un fichier docker-compose.yml, que nous utiliserons pour lancer nos magasins de données. Appelons notre base de données "songify".
mariadb-songify: image: mariadb: dernière commande:> --general-log --general-log-file = / var / log / mysql / query.log exposer: - "3306" ports: - environnement "3306: 3306" : MYSQL_DATABASE: "songify" MYSQL_ALLOW_EMPTY_PASSWORD: "true" volumes_de: - mariadb-data mariadb-data: image: mariadb: derniers volumes: - / var / lib / mysql entrée: / bin / bash redis: image: redis expose: - " 6379 "ports: -" 6379: 6379 "
Vous pouvez lancer vos magasins de données avec le docker composer
commande (similaire à vagabond
). Le résultat devrait ressembler à ceci:
il y a un certain nombre de points de départ. hybride * DB chargée à partir du disque: 0,002 seconde redis_1 | * Prêt à accepter les connexions… mariadb-songify_1 | [Note] mysqld: prêt pour les connexions…
À ce stade, vous disposez d'un serveur MariaDB à part entière à l'écoute sur le port 3306 et d'un serveur Redis à l'écoute sur le port 6379 (les deux sont les ports standard)..
Tirons parti de ces magasins de données puissants et mettons à niveau notre couche de données en une couche de données hybride mettant en cache les morceaux par utilisateur dans Redis. Quand GetSongsByUser ()
est appelé, la couche de données va d’abord vérifier si Redis stocke déjà les morceaux pour l’utilisateur. Si c'est le cas, il suffit de renvoyer les morceaux de Redis, mais si ce n'est pas le cas (mémoire cache manquante), les fichiers seront récupérés de MariaDB et remplis le cache Redis, de sorte qu'il est prêt pour la prochaine fois..
Voici la définition de struct et constructeur. La structure conserve un handle de base de données comme avant et aussi un client Redis. Le constructeur se connecte à la base de données relationnelle ainsi qu’à Redis. Il crée le schéma et supprime les redis uniquement si les paramètres correspondants sont vrais, ce qui n’est nécessaire que pour les tests. En production, vous créez le schéma une fois (en ignorant les migrations de schéma).
type HybridDataLayer struct db * sql.DB redis.Client func NewHybridDataLayer (chaîne dbHost, dbPort int, chaîne redisHost, createSchema bool, clearRedis bool) (* HybridDataLayer, erreur) dsn: = fmt.Sprint tcp (% s:% d) / ", dbHost, dbPort) si createSchema err: = createMariaDBSchema (dsn) si err! = nil return nil, err db, err: = sql.Open (" mysql ", dsn + "desongcious? parseTime = true") si err! = nil return nil, err redisClient: = redis.NewClient (& redis.Options Addr: redisHost + ": 6379", Mot de passe: "", DB "0, ) _, err = redisClient.Ping (). Result () si err! = nil return nil, err si clearRedis redisClient.FlushDB () return & HybridDataLayer db, redisClient, nil
MariaDB et SQLite sont un peu différents en ce qui concerne DDL. Les différences sont petites mais importantes. Go n'a pas de boîte à outils cross-DB mature comme le fantastique SQLAlchemy de Python, vous devez donc le gérer vous-même (non, Gorm ne compte pas). Les principales différences sont les suivantes:
INCRÉMENTATION AUTOMATIQUE
.VARCHAR
au lieu de TEXTE
.Voici le code:
func createMariaDBSchema (chaîne dsn) erreur db, err: = sql.Open ("mysql", dsn) si err! = nil return err // Recréer les commandes de base de données: = [] chaîne "DROP DATABASE songify;", "CREATE DATABASE songify;", pour _, s: = plage (commandes) _, err = db.Exec (s) si err! = Nil return err // Créer une base de données de schéma, err = sql.Open ("mysql", dsn + "songify? parseTime = true") si err! = nil return err schéma: = [] chaîne 'CREATE TABLE SI' N'EXISTE PAS la chanson (id INTEGER PRIMARY KEY AUTO_INCREMENT, url VARCHAR (2088) UNIQUE , titre VARCHAR (100), description VARCHAR (500)); ',' CREATE TABLE IF NOT EXISTS utilisateur (id INTEGER PRIMARY KEY AUTO_INCREMENT, nom VARCHAR (100), email VARCHAR (100) UNIQUE, enregistré_en TIMESTAMP, dernier_login_ TIMESTAMP); ', "CREATE INDEX user_email_idx ON utilisateur (email);",' CREATE TABLE SI NON EXISTE label (id INTEGER PRIMARY KEY AUTO_INCREMENT, nom VARCHAR (100) UNIQUE); ', "CREATE INDEX label_name_idx ON label (nom);" 'CREATE TABLE SI PAS 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 IF NE PAS EXISTER utilisateur_song (utilisateur_id INTEGER NON NULL REFERENCES utilisateur (id), song_id INTEGER NON NULL REFERENCES song (id), PRIMARY KEY (user_id, song_id)); ', pour _, s: = plage (schéma) _, err = db.Exec (s) si err! = nil return err retourne nil
Redis est très facile à utiliser à partir de Go. La bibliothèque client "github.com/go-redis/redis" est très intuitive et suit fidèlement les commandes Redis. Par exemple, pour vérifier si une clé existe, utilisez simplement le Sorties ()
méthode du client redis, qui accepte une ou plusieurs clés et renvoie le nombre de clés existantes.
Dans ce cas, je ne vérifie qu'une seule clé:
count, err: = m.redis.Exists (email) .Result () si err! = nil return err
Les tests sont en réalité identiques. L'interface n'a pas changé et le comportement n'a pas changé. Le seul changement est que l'implémentation conserve désormais un cache dans Redis. le GetSongsByEmail ()
méthode appelle maintenant juste refreshUser_Redis ()
.
func (m * HybridDataLayer) GetSongsByUser (utilisateur u) (chansons [] chanson, erreur d'erreur) err = m.refreshUser_Redis (u.Email, & chansons) retour
le refreshUser_Redis ()
La méthode retourne les morceaux utilisateur de Redis s'ils existent et les récupère sinon de MariaDB..
type Songs * [] Song func (m * HybridDataLayer) refreshUser_Redis (chaîne de courrier électronique, out Songs) error count, err: = m.redis.Exists (email) .Result () si err! = nil return err si count == 0 err = m.getSongsByUser_DB (email, out) si err! = Nil return err pour _, chanson: = plage * out s, err: = serializeSong (chanson) si err! = Nil return err _, err = m.redis.SAdd (email, s) .Result () si err! = nil return err return membres, err: = m.redis.SMembers (email) .Result () pour _ , member: = membres de la plage song, err: = deserializeSong ([] byte (membre)) si err! = nil return err * out = append (* out, song) retour out, nil
Il y a un léger problème du point de vue de la méthodologie de test. Lorsque nous testons via l'interface de couche de données abstraite, nous n'avons aucune visibilité sur l'implémentation de la couche de données..
Par exemple, il est possible que la couche de données ignore complètement le cache et récupère toujours les données de la base de données. Les tests passeront, mais nous ne pourrons pas tirer profit de la mémoire cache. Dans la cinquième partie, je parlerai du test de votre cache, ce qui est très important..
Dans ce tutoriel, nous avons abordé les tests sur une couche de données complexe locale composée de plusieurs magasins de données (une base de données relationnelle et un cache Redis). Nous avons également utilisé Docker pour déployer facilement plusieurs magasins de données à des fins de test..
Dans la quatrième partie, nous nous concentrerons sur les tests par rapport aux magasins de données distants, en utilisant des instantanés des données de production pour nos tests et en générant nos propres données de test. Restez à l'écoute!