czwartek, 21 maja 2015

PHP: wykorzystanie Zend_Cache dla zapytań SOAP

Aktualnie solidnie przebudowujemy jedną z wielu funkcjonalności aplikacji napisanej w Zend Framework 1. Znaczną część komunikacji przenosimy z zapytań SQL na odpytania API wystawionego jako WebService. Dzięki temu zwiększamy szanse na rozdzielenie dwóch aplikacji, ale też zwiększamy elastyczność naszej aplikacji. W wielu miejscach gdzie odpytywaliśmy bazę, wykorzystywaliśmy Zend_Cache, aby zmniejszyć liczbę zapytań.


Cachowanie wyników zapytania przyda się zawsze tam, gdzie dane nie zmieniają się zbyt szybko. Statyczny wynik przechowywany w plikowym cache będzie również pobierany przez aplikację szybciej niż wykonanie ponownie zapytania na bazie. Jest to też prawdziwe dla wywołań metod w WebService. Problem jaki się pojawia, to jak rozsądnie cachować  te wyniki?

Wróćmy na chwilę do SQL. Prędzej czy później zapytanie SQL to prosty tekst. Z tego tekstu zawsze łatwo można uzyskać skrót, który potem służy do identyfikacji pliku z cache wyniku. Takie rozwiązanie całkiem nieźle się sprawdza. Nie ma znaczenia co wrzucamy do Zend_Cache, bo zawsze podlega serializacji. Problemem jest tylko wyznaczenie unikalnej nazwy dobrze identyfikującej zapytanie i używanie tego w sposób globalny. Dlatego też osobiście używam takiego kodu:

queryCache($query, $bind, $lifetime) {
    $zend_cache = static::initCache($lifetime);
    $name = 'query_' . md5($query . serialize($bind));

    $wynik = $zend_cache->load($name);
    if($wynik === false) {
        if (empty($bind)) {
            $wynik = self::getDb()->query($query)->fetchAll();
        } else {
            $wynik = self::getDb()->query($query, $bind)->fetchAll();
        }
        $zend_cache->save($wynik, $name);
    }
    return $wynik;
}

Jak widać nazwa wyznaczana jest z zapytania i tablicy parametrów. To samo zapytanie, z tymi samymi parametrami daje taką samą nazwę w cache. Jak więc łatwo i uniwersalnie budować statyki w cache dla wywołania metod klasy klienta WS?

Poniżej prezentuję swoje rozwiązanie:
public static function queryCallable($callable, $parameters = array(), $lifetime = 86400, ) {
    $zend_cache = static::initCache($lifetime);
    $name = 'call_' . md5(serialize($callable) . serialize($parameters));
    $wynik = $zend_cache->load($name);
    if($wynik === false) {
        $wynik = call_user_func_array($callable, $parameters);
        $zend_cache->save($wynik, $name);
    }
    return $wynik;
}

W tym przypadku nazwa jest generowana na podstawie callable i tablicy parametrów. Klient WS realizuje wzorzec Singleton, dzięki czemu z łatwością można cachować każde wywołanie tej samej metody dla tych samych parametrów w całej aplikacji i co ważne można to zrobić w sposób stosunkowo przezroczysty. Oczywiście zamiast napisać:

$client = WSClient::getInstance();
$data = $client->callMethod($param);

Trzeba napisać:
$data = CacheFactory::queryCallable(array(WSClient::getInstance(), 'callMethod'), array($param));
Uzyskany wynik będzie identyczny. Jedyny problem jest taki, że budując callable nie możemy oprzeć się na pomocy IDE. Utrudnione jest też wyszukiwanie wszystkich odwołań do metody w callable co nieco utrudnia refaktoryzację.

Brak komentarzy:

Prześlij komentarz