Detta är del tre av fem i en tutorial-serie om testning av dataintensiv kod med Go. I del två täckte jag testning mot ett realt minne i minnet baserat på den populära SQLite. I den här handledningen går jag över testning mot ett lokalt komplext datalager som innehåller en relationell DB och en Redis-cache.
Testning mot ett minnesdataskikt är fantastiskt. Testerna är blixtsnabba, och du har full kontroll. Men ibland måste du närma dig den faktiska konfigurationen av ditt produktionsdata lager. Här är några möjliga skäl:
Jag är säker på att det finns andra orsaker, men du kan se varför att bara använda ett minnesdataskikt för testning kanske inte räcker i många fall.
OK. Så vi vill testa ett faktiskt datalager. Men vi vill fortfarande vara så lätta och smidiga som möjligt. Det betyder ett lokalt datalager. Här är fördelarna:
I denna handledning kommer vi upp ante. Vi implementerar (mycket delvis) ett hybriddatalag som består av en MariaDB relationell DB och en Redis-server. Då använder vi Docker för att ställa upp ett lokalt datalag som vi kan använda i våra test.
Först behöver du Docker, förstås. Kolla in dokumentationen om du inte är bekant med Docker. Nästa steg är att få bilder för våra datalager: MariaDB och Redis. Utan att gå in på för mycket detaljer är MariaDB en bra relationell DB-kompatibel med MySQL, och Redis är en stor viktminskningsbutik (och mycket mer).
> docker pull mariadb ...> docker pull redis ...> docker bilder REPOSITORY TAG IMAGE ID CREATED STORLEK mariadb senaste 51d6a5e69fa7 2 veckor sedan 402MB redis senaste b6dddb991dfa 2 veckor sedan 107MB
Nu när vi har Docker installerat och vi har bilderna för MariaDB och Redis, kan vi skriva en docker-compose.yml-fil, som vi ska använda för att starta våra datalager. Låt oss ringa vår DB "Songify".
mariadb-songify: image: mariadb: senaste kommandot:> --general-loggen --general-log-file = / var / log / mysql / query.log avslöja: - "3306" portar: - "3306: 3306" miljö : MYSQL_DATABASE: "songify" MYSQL_ALLOW_EMPTY_PASSWORD: "true" volymer_from: - mariadb-data mariadb-data: bild: mariadb: senaste volymer: - / var / lib / mysql entrypoint: / bin / bash redis: bild: redis expose: 6379 "portar: -" 6379: 6379 "
Du kan starta dina datalager med docker-komponera upp
kommando (liknar vagrant upp
). Utgången ska se ut så här:
> docker-compose up Starta hybridtest_redis_1 ... Starta hybridtest_mariadb-data_1 ... Starta hybridtest_redis_1 Starta hybridtest_mariadb-data_1 ... done Starta hybridtest_mariadb-songify_1 ... Starta hybridtest_mariadb-songify_1 ... done Fästa i hybridtest_mariadb-data_1, hybridtest_redis_1, hybridtest_mariadb-songify_1 ... redis_1 | * DB laddad från disk: 0,002 sekunder redis_1 | * Klar för att acceptera anslutningar ... mariadb-songify_1 | [Notera] mysqld: redo för anslutningar ...
Vid denna tidpunkt har du en fullvärdig MariaDB-serverns lyssning på port 3306 och en Redis-server lyssnar på port 6379 (båda är standardportarna).
Låt oss utnyttja dessa kraftfulla databutiker och uppgradera vårt datalager till ett hybriddatalager som cachar låtar per användare i Redis. När GetSongsByUser ()
kallas, ska data lagret först kontrollera om Redis redan lagrar låtarna för användaren. Om det gör så returnerar du bara låtarna från Redis, men om det inte gör (cache miss) kommer det att hämta låtarna från MariaDB och fylla i Redis-cachen, så det är klart för nästa gång.
Här är struktur- och konstruktördefinitionen. Struct håller ett DB-handtag som tidigare och även en redis-klient. Konstruktören ansluter sig till relationell DB såväl som till Redis. Det skapar schemat och spola redis bara om motsvarande parametrar är sanna, vilket bara behövs för testning. I produktion skapar du schemat en gång (ignorerar scheman migreringar).
skriv HybridDataLayer struktur db * sql.DB redis * redis.Client func NewHybridDataLayer (dbHost-sträng, dbPort int, redisHost-sträng, skapaSchema bool, clearRedis bool) (* HybridDataLayer, fel) dsn: = fmt.Sprintf tcp (% s:% d) / ", dbHost, dbPort) om createSchema err: = createMariaDBSchema (dsn) om err! = nil return nil, err db, err: = sql.Open (" mysql " dsn + "desongcious? parseTime = true") om err! = nil return nil, err redisClient: = redis.NewClient (& redis.Options Addr: redisHost + ": 6379", Lösenord: "", DB: 0, ) _, err = redisClient.Ping (). Resultat () om err! = nil return nil, err om clearRedis redisClient.FlushDB () returnerar & HybridDataLayer db, redisClient, nil
MariaDB och SQLite är lite annorlunda när DDL går. Skillnaderna är små, men viktiga. Go har inte en mogen cross-DB verktygslåda som Pythons fantastiska SQLAlchemy, så du måste hantera det själv (nej, Gorm räknas inte). De viktigaste skillnaderna är:
AUTO_INCREMENT
.VARCHAR
istället för TEXT
.Här är koden:
func createMariaDBSchema (dsn-string) fel db, err: = sql.Open ("mysql", dsn) om err! = nil return err // Återskapa DB-kommandon: = [] string "DROP DATABASE songify;" "CREATE DATABASE songify;", för _, s: = range (kommandon) _, err = db.Exec (s) om err! = Nil return err // Skapa schema db, err = sql.Open ("mysql", dsn + "songify? parseTime = true") om err! = nil return err schema: = [] sträng 'CREATE TABLE IF NOT EXISTS sång (id INTEGER PRIMARY KEY AUTO_INCREMENT, url VARCHAR (2088) UNIQUE VARCHAR (100), VARCHAR (100), VARCHAR (100), VARCHAR (100), E-post VARCHAR (100) UNIQUE, registered_at TIMESTAMP, last_login TIMESTAMP), "CREATE TABLE IF NOT EXISTS användaren (id INTEGER PRIMARY KEY AUTO_INCREMENT, namn VARCHAR "," SKAPA INDEX user_email_idx ON användare (email); "," Skapa tabell om inte EXISTS-etikett (id INTEGER PRIMARY KEY AUTO_INCREMENT, namn VARCHAR (100) UNIQUE); "," SKAPA INDEX label_name_idx ON label "Skapa tabell om inte existerar label_song (label_id INTEGER NOT NULL REFE RENCES-etikett (id), song_id INTEGER NOT NULL REFERENCES låt (id), PRIMARY KEY (label_id, song_id)); "," Skapa tabell om inte EXISTS user_song (user_id INTEGER NOT NULL REFERENCES användare (id), song_id INTEGER INTE NULL REFERENSER låt (id), PRIMARY KEY (user_id, song_id));,, _, s: = range (schema) _, err = db.Exec (s) om err! = nil return err returnera noll
Redis är väldigt lätt att använda från Go. Klientbiblioteket "github.com/go-redis/redis" är mycket intuitivt och följer följaktligen kommandona Redis. Till exempel, för att testa om en nyckel finns, använder du bara Utgångar ()
metod för redis-klienten, som accepterar en eller flera nycklar och returnerar hur många av dem som finns.
I det här fallet kontrollerar jag endast en nyckel:
räkna, err: = m.redis.Exists (email) .Result () om err! = nil return err
Testerna är faktiskt identiska. Gränssnittet ändrades inte, och beteendet förändrades inte. Den enda förändringen är att implementeringen nu håller ett cache i Redis. De GetSongsByEmail ()
Metod nu bara samtal refreshUser_Redis ()
.
func (m * HybridDataLayer) GetSongsByUser (u användare) (låtar [] Song, err error) err = m.refreshUser_Redis (u.Email, & songs) returnerar
De refreshUser_Redis ()
Metoden returnerar användarsångerna från Redis om de finns och hämtar dem på annat sätt från MariaDB.
typ Songs * [] Song func (m * HybridDataLayer) refreshUser_Redis (email sträng, ut Songs) fel count, err: = m.redis.Exists (email) .Resultat () om err! = nil return err om räkna == 0 err = m.getSongsByUser_DB (email, out) om err! = Nil return err för _, låt: = range * ut s, err: = serializeSong (song) om err! = Nil return error _, err = m.redis.SAdd (email, s) .Resultat () om err! = nil return err returnera medlemmar, err: = m.redis.SMembers (email) .Result () för _ , medlem: = radmedlemmar låt, err: = deserializeSong ([] byte (medlem)) om err! = nil return err * ut = lägg till (* ut, låt) returnera, nil
Det finns ett litet problem här ur testmetodiksynpunkt. När vi testar genom det abstrakta datalagergränssnittet, har vi ingen synbarhet i implementeringen av datalagret.
Det är till exempel möjligt att det finns en stor fel där datalagret helt hoppar över cacheminnet och hämtar alltid data från DB. Testerna passerar, men vi får inte dra nytta av cacheminnet. Jag ska prata i del fem om att testa din cache, vilket är mycket viktigt.
I den här handledningen täckte vi testning mot ett lokalt komplext datalager som består av flera datalager (en relations DB och en Redis-cache). Vi utnyttjade även Docker för att enkelt distribuera flera datalager för testning.
I del fyra kommer vi att fokusera på testning mot fjärrdatabutiker, med hjälp av ögonblicksbilder av produktionsdata för våra test, och även att generera egna testdata. Håll dig igång!