Üzenetek és tranzakció
A tranzakciókezelés alapvetően nem bonyolult dolog, már ami a használatát illeti. Ha minden művelet rendben végrehajtódik, akkor nincs teendőnk. Az érdekesebb viszont az, amikor valamilyen hiba lép fel. Ugye ilyenkor rollbackelni kell. Önmagában ez sem nagy szám, az adatbázis elintézi, amit el kell (ugye nem myisam-ot használunk!), viszont sokszor ez nem elég: el is kell takarítanunk magunk után. Hogy miket? Például a létrehozott/elmozgatott/törölt(!) fájlokat és minden egyéb olyat, amit a tranzakción belül hajtottunk végre.
Az mf4php használata során szembesültem a fenti problémával. Ugye arra lenne szükség, hogy az üzenet akkor, és csak akkor kerüljön elküldésre, ha a commit sikeres. Ha a tranzakción kívül küldök üzenetet, akkor amennyiben az üzenet küldés sikertelen, a tranzakció nem rollbackelődik. Viszont ha tranzakción belülre teszem, akkor az üzenet elmegy függetlenül attól, hogy végeredményben sikeres-e a tranzakció, vagy sem. Feloldhatatlan problémának látszik, legalábbis nekem annak tűnt.
Fény az alagút végén
Kis utánaolvasás (JMS), után arra jutottam, hogy az üzeneteket tranzakción belül elküldöm, viszont azokat az mf4php szintjén várakoztatom. Sikeres commit után pedig ténylegesen elküldöm őket. Egy dolog húzhatja keresztbe a számításaimat, mégpedig ha ez a tényleges küldés okoz hibát. Erre viszont azt mondom, hogy ez már nem alkalmazás hiba, tehát nem is ott kell kezelni.
A megvalósításhoz egy dologra van szükség: az mf4php implementációnak értesülnie kell a sikeres commitról. Úgyhogy fogtam magam, és bevetettem a jó öreg observer mintát. Készítettem egy ObservableTransactionManager interfészt a trf4php-ben, ami értesíti a tranzakciós lépésekről a regisztrált megfigyelőket. Ezen kívül pedig létrehoztam egy TransactedMessageDispatcher absztrakt osztályt, ami képes a fent említett üzenetgyűjtésre és -elküldésre.
Használat
Nagyon egyszerű, egy dologra kell figyelni. Ugyanazt a tranzakció managert adjuk át az üzenet kezelőnek, mint amit ténylegesen tranzakció kezelésre használunk. Az alábbi példában a TransactedMemoryMessageDispatcher szerepel, ez egy egyszerű, memória alapú (szinkron) megvalósítása a fentieknek, azonban a beanstalk implementáció is támogatja mindezt.
/* @var $transactionManager ObservableTransactionManager */
$dispatcher = new TransactedMemoryMessageDispatcher($transactionManager);
/* @var $listener MessageListener */
$dispatcher->addEventListener($queue, $listener);
$transactionManager->beginTransaction();
try {
// adatbázis műveletek, egyebek...
$dispatcher->send($queue, $message);
$transactionManager->commit();
// a $message a commit után lesz $listener-nek kézbesítve
} catch (Exception $e) {
$transactionManager->rollback();
// a $message nem lesz elküldve a $listenernek, törlésre kerül
}
Szívesen várom az észrevételeket/javaslatokat akár az elméletet, akár a megvalósítást illetően.