-
-
Save dsdenes/bcf5b89ca518217724f39c78b0f2ba64 to your computer and use it in GitHub Desktop.
Hello!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Code review-t kérnénk a meglévő kódbázisra, egyelőre csak backend (~7.000 sor). | |
A mostani architektúra áttekintése, esetleges szűk keresztmetszetek, problémák kiszűrése | |
RabbitMQ-t használó microservice-t hogyan lehet unit tesztelni?Hogyan lehet unit tesztet írni AMQP (amqplib) protokollt használó JS alkalmazáshoz, ami nem integration teszt. (Jelenleg integration tesztek vannak, localhost rabbitmq docker image segítségével). Létezik-e JS-hez amqplib mock library (én nem találtam)? | |
**Mi írtunk egyet: https://github.com/lab-coop/lab-queue/blob/master/implementations/memory/index.js de ez nem tudom, hogy jelenleg production ready-e, illetve exchange kezeles nincs benne.** | |
Jó-e a jelenlegi architektúra elképzelés RabbitMQ-val? (ts_exchange (topic), routing, log_exchange a logoknak, minden uS ide beszél | |
Hogyan lenne célszerű megoldani, hogy egy TS önállóan is tudjon működni, ha leszakad a hálózatról, de úgy, hogy a jelenleg megírt AMQP-s modulokat ne kelljen újraírni (TS helyi Rabbit broker?) | |
AMQP consumer egy callback. Általában, hogyan lehet/lehet-e felhasználni a promise/async-await/observer/rxjs/generator/async-generator/for-await-of, stb nyelvi elemeket, hogy kihozzunk adatot a callback-ből. | |
**Most anelkul, hogy latnam a pontos igenyt, erre nehez valaszolni. Mi valami hasonlo megoldast szoktunk csinalni: a consumer callback egy promissal ter vissza, ezt wrappolom be es ha resolvolodik, akkor ACK-t kuldok az uzenetre, ha rejectelodik akkor NACK** | |
``` | |
const channel = createChannel(); | |
const consumer = createConsumer( | |
myMessageHandler, | |
message => channel.ack(message), | |
message => channel.nack(message), | |
); | |
channel.consume(consumer); | |
function createConsumer(messageHandler, ack, nack) { | |
return async function(message) { | |
try { | |
await messageHandler(message); | |
ack(message); | |
} catch(err) { | |
nack(message); | |
} | |
} | |
} | |
async function myMessageHandler(message) { | |
return true; | |
} | |
``` | |
**Ebben a felallasban a myMessagehandler unit szinten tesztelheto. Itt ket dogot tesztelnek: a createConsumer meghivja-e a parameterben megadott fugvenyeket bizonyos esetekben, es a myMessageHandler-t kulon.** | |
Az a probléma, hogy nem látok ki a callbackből, az aszinkron hívódik meg valamikor, és nekem az kellene, hogy arra vizsgáljak rá pl. egy unit tesztben, hogy a consumer callbackben megtörtént-e egy hívás | |
**Lsd. fentebb, ilyenkor nem a teljes kort tesztelnem, hanem a myMessageHandlert unit szinten. ** | |
Egy konkrét példa: Egy logger osztály amqp queue-n keresztül kap egy üzenetet, ezt kell kiirnia adatbázisba (pl. sequelize segítségével). Ezt szeretném unit tesztelni: az adott queue-hoz tartozó consume() meghívódik, benne egy aszinkron (Promissal visszatérő) függvény kiírja az adatbázisba az adatot. A unit tesztben azt akarom tesztelni, hogy beadok egy üzenetet az AMQP queue-ba, a callback meghívódik, SQL kiírás elkezdődik, majd sikerül, végül az adatbázisra ránézve expect( count, 'WHERE ...').to.equal(1). ....Sinon mock/spy jó/elég ide? | |
Hozhat-e a konyhára, ha sima AMQP csatorna helyett ConfirmChannel-t csinálunk.(Ebben a sendToQueue és publish függvények egy callbacket is várnak, ami akkor hívódik meg, ha megjött a brókertől az ack, hogy bevette a parancsot) Hogyan lehetne ezeket a callbackeket async/await-ra lecserélni (ugyanez a kérdés a consume függvényeknél is) | |
Hogyna lehet unit/integration tesztelni egy AMQP-t használó osztályt anélkül, hogy ki kellene hozni a publikus interfészen az összes belső amqp connection/channel/queue/exchange stb objektumokat. Ezeket szeretném megfigyelni a unit tesztben, de nem akarom kihozni a publikus interfészre. Dependency injection itt hogyan tud/tud-e segíteni? Hogyan kémkedhetek bele az objektum belsejébe? Sinon spy erre jó? | |
**Itt azt az iranyelvet szoktam kovetni, hogy az egyes metodusok a legkevesbe se tamaszkodjanak a kulso statere, hanem pure functionok legyenek. Pl. ebben az esetben a channel a kulso statere tamaszkodik, a closureben elerheto connection valtozora. Ez nehezen tesztelheto, mert a channel kimenetele fugg a connection allapotatol, amit most meg csak egy (connect), de kesobb tobb metodus is allithat:** | |
``` | |
let connection = null; | |
function connect() { | |
connection = true; | |
} | |
function channel(channelName) { | |
if (connection) { | |
connection.createChannel(channelName) | |
} | |
} | |
``` | |
**Helyette:** | |
``` | |
const getConnection = getCachedConnection(AMQPConnect); | |
function AMQPConnect() { | |
return true; | |
} | |
function getCachedConnection(connect) { | |
connection = null; | |
return () => { | |
if (!connection) { | |
connection = connect(); | |
} | |
return connection; | |
} | |
} | |
function channel(channelName) { | |
const connection = getConnection(); | |
connection.createChannel(channelName) | |
} | |
``` | |
**Amit ebbol a kodbol tesztelnek, hogy a getCachedConnection meghivja-e a parameterkent kapott connection fgvenyt, es kesz.** | |
**A mocking veszelyes, nagyon konnyu elveszni benne, ha lehet sosem hasznalom. A spyingot gyakran, hogy ha azt akarom megnezni, hogy egy parameterkent megkapott valtozot meghivott-e a metodus.** | |
Timeout kérdések: Bluebird#timeout jónak tűnik, de a unit tesztelek random elhasalnak Sinon fake timer használatakor. Hogyan kell a timeout dolgokat jól lekezelni? Legyen timeout ha egy kapcsolat nem sikerül. | |
Legyen timeout ha elküldünk egy parancsot (mondjuk rabbitmq/amqp-n) és arra nem jön meg a válasz időben, stb. | |
Csatlakozási hibák .on('error',...) jellegű hibák kezelése hogyan? Feladat: mondjuk egy .on('error'...) event-et | |
becsomagolok RXJS-be. Azt szeretném elérni, hogy ha az első kapcsolódás sikertelen, akkor próbálkozzon újra pl. 1s-enként pl. 5x, utána dobjon hibát a felettes rétegnek/hívónak. Tehát ha csatlakozási vagy bármilyen hiba volt, akkor először próbálja meg megoldani a modul házon belül, próbáljon újracsatlakozni, stb. és ha végképp nem megy, akkor dobja el magát és értesítse a hívót. Ugyanez kell hogy működjön akkor is, ha menet közben történik kapcsolatvesztés.Az a probléma, hogy ha egy Promise-szal jövök vissza egy kapcsolat létrehozásakor és resolve hogyha sikerült a kapcsolat, reject ha nem, de ha sikerült a kapcsolat, megy a kommunikáció és menet közben jön egy disconnect, akkor a Promise már resolvolva van, nem lehet utólag rejectelni. Újra kell inicializálni a kapcsolatot, ez már egy új Promise lehet, de az kellene, hogy erről a hívó egészen addig ne szerezzen tudomást (mármint arról, hogy az alsóbb rétegben disconnect volt) amíg az alsó réteg végképp el nem dobja a kanalat. | |
**Erre talan az a megoldas, hogy a fgvenyen belul, ahol kell a connection, mindig futasidoben kered le a connectiont, ami a keepConnected belso allapotatol fuggoen mindig mas lehet. Most valami ilyen jut eszembe. Nem teszteltem.** | |
``` | |
const connection = keepConnected(); | |
function keepConnected() { | |
let connection = connect(); | |
let connecting = false; | |
return () => connection; | |
async function connect() { | |
connecting = true; | |
return new Promise(async (resolve, reject) => { | |
try { | |
const _connection = await amqplib.connect(); | |
connecting = false; | |
resolve(_connection); | |
_connection.on('error', () => { | |
_connection.close(); | |
if (counter >= 5) { | |
reject(new Error('Failed to reconnect.')); | |
} else { | |
setTimeout(reconnect, parseInt(Math.exp(++counter))); | |
} | |
}); | |
} catch(err) { | |
reject(err); | |
} | |
}); | |
} | |
function reconnect() { | |
if (!connecting) { | |
connection = connect(); | |
} | |
} | |
} | |
async function createChannel(name) { | |
const connection = await getConnection(); | |
return connection.createChannel(name); | |
} | |
``` | |
RXJS jó, hogy observer patternt csinál, de végül mégiscsak egy callback-em van (subscribe első paramétere) amibe minden bele kell zsúfolnom, ami ide tartozik. Nincs valami olyan új metodológia (ES7-ben akár, lásd async generators/for-await-of) amivel úgy tudnám kezelni ezeket a callback-eket, mintha szinkron iteratorok lennének? (Egy egyszer meghívódó callbackből lehet csinálni Promise-t, ez ok. Van esetleg olyan konstrukció is, amivel ugyanilyet lehet csak többször meghívott callbackre? observer pattern, csak nem RXJS mintára, hanem for..of loop-pal) | |
NodeJS event loop még többedik olvasásra sem világos teljesen (setImmediate, setTImeout(...,0), nextTick() mikor miért érdemes használni) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment