Questa è la terza parte su cinque in una serie di tutorial sul test del codice ad alta intensità di dati con Go. Nella seconda parte, ho coperto i test con un vero strato di dati in memoria basato sul popolare SQLite. In questo tutorial, esaminerò un livello dati complesso locale che include un DB relazionale e una cache Redis.
Testare contro un livello dati in memoria è fantastico. I test sono velocissimi e tu hai il pieno controllo. Ma a volte è necessario essere più vicini alla configurazione effettiva del livello dati di produzione. Ecco alcuni possibili motivi:
Sono sicuro che ci sono altri motivi, ma puoi capire perché l'utilizzo di un livello dati in memoria per i test potrebbe non essere sufficiente in molti casi.
OK. Quindi vogliamo testare un livello dati reale. Ma vogliamo ancora essere il più leggeri e agili possibile. Ciò significa un livello dati locale. Ecco i vantaggi:
In questo tutorial alzeremo la posta. Implementeremo (in modo molto parziale) un livello dati ibrido costituito da un DB relazionale MariaDB e un server Redis. Quindi useremo Docker per alzare un livello dati locale che possiamo usare nei nostri test.
Per prima cosa, hai bisogno di Docker, ovviamente. Controlla la documentazione se non hai familiarità con Docker. Il passo successivo è quello di ottenere immagini per i nostri archivi dati: MariaDB e Redis. Senza entrare troppo nel dettaglio, MariaDB è un ottimo DB relazionale compatibile con MySQL e Redis è un ottimo archivio di valori-chiave in memoria (e molto altro).
> docker pull mariadb ...> docker pull redis ...> immagini docker TAG REPOSITORY ID IMMAGINE CREATO TAGLIA mariadb latest 51d6a5e69fa7 2 settimane fa 402MB redis latest b6dddb991dfa 2 settimane fa 107MB
Ora che abbiamo installato Docker e abbiamo le immagini per MariaDB e Redis, possiamo scrivere un file docker-compose.yml, che useremo per lanciare i nostri archivi di dati. Chiamiamo il nostro DB "songify".
mariadb-songify: immagine: mariadb: ultimo comando:> --general-log --general-log-file = / var / log / mysql / query.log espone: - Porte "3306": - "3306: 3306" ambiente : MYSQL_DATABASE: "songify" MYSQL_ALLOW_EMPTY_PASSWORD: "true" volumes_from: - mariadb-data mariadb-data: image: mariadb: ultimi volumi: - / var / lib / mysql entrypoint: / bin / bash redis: image: redis espone: - " 6379 "porte: -" 6379: 6379 "
Puoi avviare i tuoi archivi di dati con il docker-comporre up
comando (simile a vagabondo
). L'output dovrebbe assomigliare a questo:
> docker-compose up Starting hybridtest_redis_1 ... Starting hybridtest_mariadb-data_1 ... Starting hybridtest_redis_1 Starting hybridtest_mariadb-data_1 ... done Starting hybridtest_mariadb-songify_1 ... Starting hybridtest_mariadb-songify_1 ... done Collegamento a hybridtest_mariadb-data_1, hybridtest_redis_1, hybridtest_mariadb-songify_1 ... redis_1 | * DB caricato dal disco: 0.002 secondi redis_1 | * Pronto ad accettare connessioni ... mariadb-songify_1 | [Nota] mysqld: pronto per le connessioni ...
A questo punto, si ha un server MariaDB a pieno titolo in ascolto sulla porta 3306 e un server Redis in ascolto sulla porta 6379 (entrambe sono le porte standard).
Approfitta di questi potenti archivi di dati e aggiorna il nostro livello dati a un livello dati ibrido che memorizza nella cache i brani per utente in Redis. quando GetSongsByUser ()
viene chiamato, il livello dati controlla prima se Redis memorizza già i brani per l'utente. Se invece restituisce i brani da Redis, se non lo fa (cache miss), recupererà i brani da MariaDB e popolerà la cache Redis, quindi sarà pronto per la prossima volta.
Ecco la definizione di struct e constructor. La struttura mantiene un handle DB come prima e anche un client redis. Il costruttore si connette al DB relazionale e ai Redis. Crea lo schema e svuota i redis solo se i parametri corrispondenti sono veri, che è necessario solo per il test. In produzione, si crea lo schema una sola volta (ignorando le migrazioni dello schema).
digita HybridDataLayer struct db * sql.DB redis * redis.Client func NewHybridDataLayer (dbHost stringa, dbPort int, redisHost stringa, createSchema bool, clearRedis bool) (* HybridDataLayer, errore) dsn: = fmt.Sprintf ("root @ tcp (% s:% d) / ", dbHost, dbPort) se createSchema err: = createMariaDBSchema (dsn) se err! = nil return nil, err db, err: = sql.Open (" mysql ", dsn + "desongcious? parseTime = true") se err! = nil return nil, err redisClient: = redis.NewClient (& redis.Options Addr: redisHost + ": 6379", Password: "", DB: 0, ) _, err = redisClient.Ping (). Result () se err! = nil return nil, err se clearRedis redisClient.FlushDB () return & HybridDataLayer db, redisClient, nil
MariaDB e SQLite sono un po 'diversi per quanto riguarda il DDL. Le differenze sono piccole, ma importanti. Go non ha un toolkit cross-DB maturo come il fantastico SQLAlchemy di Python, quindi devi gestirlo da solo (no, Gorm non conta). Le principali differenze sono:
INCREMENTO AUTOMATICO
.VARCHAR
invece di TESTO
.Ecco il codice:
func createMariaDBSchema (dsn string) error db, err: = sql.Open ("mysql", dsn) se err! = nil return err // Ricrea comandi DB: = [] string "DROP DATABASE songify;", "CREATE DATABASE songify;", per _, s: = range (comandi) _, err = db.Exec (s) se err! = Nil return err // Crea schema db, err = sql.Open ("mysql", dsn + "songify? parseTime = true") se err! = nil return err schema: = [] string 'CREATE TABLE IF NOT EXISTS song (id INTEGER PRIMARY KEY AUTO_INCREMENT, url VARCHAR (2088) UNIQUE , titolo VARCHAR (100), descrizione VARCHAR (500)); ',' CREATE TABLE SE NON ESISTE utente (id INTEGER PRIMARY KEY AUTO_INCREMENT, nome VARCHAR (100), email VARCHAR (100) UNIQUE, registered_at TIMESTAMP, last_login TIMESTAMP); ', "CREATE INDEX user_email_idx ON utente (email);",' CREATE TABLE IF NOT EXISTS label (id INTEGER PRIMARY KEY AUTO_INCREMENT, nome VARCHAR (100) UNIQUE); ', "CREATE INDEX label_name_idx ON label (name);", 'CREATE TABLE SE NON ESISTE label_song (label_id INTEGER NOT NULL REFE Etichetta RENCES (id), song_id INTEGER NOT NULL REFERENCES song (id), PRIMARY KEY (label_id, song_id)); ',' CREATE TABLE SE NON ESISTE user_song (user_id INTEGER NOT NULL REFERENCES utente (id), song_id INTEGER NOT NULL REFERENCES song (id), PRIMARY KEY (user_id, song_id)); ', per _, s: = range (schema) _, err = db.Exec (s) se err! = nil return err return nil
Redis è molto facile da usare da Go. La libreria client "github.com/go-redis/redis" è molto intuitiva e segue fedelmente i comandi di Redis. Ad esempio, per verificare se esiste una chiave, basta usare il Uscite ()
metodo del client redis, che accetta una o più chiavi e restituisce quante di esse esistono.
In questo caso, controllo solo una chiave:
count, err: = m.redis.Exists (email) .Result () se err! = nil return err
I test sono in realtà identici. L'interfaccia non è cambiata e il comportamento non è cambiato. L'unico cambiamento è che l'implementazione ora mantiene una cache in Redis. Il GetSongsByEmail ()
il metodo ora chiama refreshUser_Redis ()
.
func (m * HybridDataLayer) GetSongsByUser (u User) (songs [] Song, err error) err = m.refreshUser_Redis (e.mail, e canzoni) return
Il refreshUser_Redis ()
metodo restituisce le canzoni dell'utente da Redis se esistono e in caso contrario le preleva da MariaDB.
digita Songs * [] Song func (m * HybridDataLayer) refreshUser_Redis (stringa email, out Songs) error count, err: = m.redis.Exists (email) .Result () se err! = nil return err se conta == 0 err = m.getSongsByUser_DB (email, out) se err! = Nil return err per _, song: = range * out s, err: = serializeSong (song) se err! = Nil return err _, err = m.redis.SAdd (email, s) .Result () se err! = nil return err return membri, err: = m.redis.SMembers (email) .Result () per _ , membro: = range members song, err: = deserializeSong ([] byte (membro)) se err! = nil return err * out = append (* out, song) return out, nil
C'è un piccolo problema qui da un punto di vista della metodologia di test. Quando testiamo l'interfaccia del livello dati astratti, non abbiamo visibilità nell'implementazione del livello dati.
Ad esempio, è possibile che ci sia un grande difetto in cui il livello dati ignora completamente la cache e recupera sempre i dati dal DB. I test passeranno, ma non trarremo beneficio dalla cache. Nella parte 5 parlerò della verifica della cache, che è molto importante.
In questo tutorial, abbiamo coperto i test con un livello dati complesso locale costituito da più archivi di dati (un DB relazionale e una cache Redis). Abbiamo anche utilizzato Docker per distribuire facilmente più archivi di dati per i test.
Nella parte quattro, ci concentreremo sui test sugli archivi di dati remoti, utilizzando le istantanee dei dati di produzione per i nostri test e generando anche i nostri dati di test. Rimanete sintonizzati!