1

I have many methods in many repositories that have the same logical flow:

  1. Retrieve value from Cache
  2. Check value
    1. If no value, fetch from Database
    2. Update Cache
  3. Return value

Is there a way to refactor these methods in such a way that I don't have to rewrite the same lines over and over again?

I'm using an MVC pattern with services e.g. ActivityService service has ActivityRepositoryInterface (most likely it will be the concrete cached version you see below) injected. I can paste more code if it helps?

Thanks

namespace DW\DWBundle\Repository\Cached;

use DW\DWBundle\Cache\ActivityCache;
use DW\DWBundle\Entity\User;
use DW\DWBundle\Helper\CacheHelper;
use DW\DWBundle\Repository\ActivityRepository as ActivityRepositoryInterface;
use DW\DWBundle\Repository\Doctrine\ORM\ActivityRepository as ActivityRepositoryDoctrine;

class ActivityRepository extends CustomRepository implements ActivityRepositoryInterface
{
    private $activityRepository;
    private $cacheHelper;

    public function __construct(ActivityRepositoryDoctrine $activityRepository, CacheHelper $cacheHelper)
    {
        parent::__construct($activityRepository);

        $this->activityRepository = $activityRepository;
        $this->cacheHelper = $cacheHelper;
    }

    public function findRecentLikeActivity($limit)
    {
        $key = ActivityCache::KEY_LIKES_WIDGET;
        $name = ActivityCache::CACHE_ACTIVITY;
        $classType = "ArrayCollection<DW\DWBundle\Entity\Activity>";
        $activity = $this->cacheHelper->getFromCache($key, $name, $classType);
        if ($activity == null) {
            $activity = $this->activityRepository->findRecentLikeActivity($limit);
            $this->cacheHelper->saveToCache($key, $name, $activity);
        }
        return $activity;
    }

    public function findRecentCommentActivity($limit)
    {
        $key = ActivityCache::KEY_COMMENTS_WIDGET;
        $name = ActivityCache::CACHE_ACTIVITY;
        $activity = $this->cacheHelper->getFromCache($key, $name, "ArrayCollection<DW\DWBundle\Entity\Activity>");
        if ($activity == null) {
            $activity = $this->activityRepository->findRecentCommentActivity($limit);
            $this->cacheHelper->saveToCache($key, $name, $activity);
        }
        return $activity;
    }

    public function findRecentActivity($limit)
    {
        $key = ActivityCache::KEY_ALL_WIDGET;
        $name = ActivityCache::CACHE_ACTIVITY;
        $activity = $this->cacheHelper->getFromCache($key, $name, "ArrayCollection<DW\DWBundle\Entity\Activity>");
        if ($activity == null) {
            $activity = $this->activityRepository->findRecentActivity($limit);
            $this->cacheHelper->saveToCache($key, $name, $activity);
        }
        return $activity;
    }

    public function findActivityByUser(User $user)
    {
        $key = ActivityCache::KEY_USER_ID."_".$user->getId();
        $name = ActivityCache::CACHE_ACTIVITY;
        $activity = $this->cacheHelper->getFromCache($key, $name, "ArrayCollection<DW\DWBundle\Entity\Activity>");
        if ($activity == null) {
            $activity = $this->activityRepository->findActivityByUser($user);
            $this->cacheHelper->saveToCache($key, $name, $activity);
        }
        return $activity;
    }

    public function findActivityOrderedByCreated()
    {
        $key = ActivityCache::KEY_LIST;
        $name = ActivityCache::CACHE_ACTIVITY;
        $activity = $this->cacheHelper->getFromCache($key, $name, "ArrayCollection<DW\DWBundle\Entity\Activity>");
        if ($activity == null) {
            $activity = $this->activityRepository->findActivityOrderedByCreated();
            $this->cacheHelper->saveToCache($key, $name, $activity);
        }
        return $activity;
    }

    public function findActivityOrderedByCreatedASC()
    {
        $key = ActivityCache::KEY_LIST_ASC;
        $name = ActivityCache::CACHE_ACTIVITY;
        $activity = $this->cacheHelper->getFromCache($key, $name, "ArrayCollection<DW\DWBundle\Entity\Activity>");
        if ($activity == null) {
            $activity = $this->activityRepository->findActivityOrderedByCreatedASC();
            $this->cacheHelper->saveToCache($key, $name, $activity);
        }
        return $activity;
    }
}

....

namespace DW\DWBundle\Helper;

use JMS\Serializer\Serializer;
use Tbbc\CacheBundle\Cache\CacheManagerInterface;
use Tbbc\CacheBundle\Cache\KeyGenerator\KeyGeneratorInterface;

class CacheHelper
{
    private $cacheManager;
    private $keyGenerator;
    private $serializer;

    public function __construct(CacheManagerInterface $cacheManager,
                                KeyGeneratorInterface $keyGenerator,
                                Serializer $serializer)
    {
        $this->cacheManager = $cacheManager;
        $this->keyGenerator = $keyGenerator;
        $this->serializer = $serializer;
    }

    public function getFromCache($key, $name, $classType)
    {
        $cacheKey = $this->keyGenerator->generateKey($key);
        $cache = $this->cacheManager->getCache($name);
        $serialized = $cache->get($cacheKey);
        $deserialized = $this->deserialize($serialized, $classType);
        return $deserialized;
    }

    public function saveToCache($key, $name, $value)
    {
        $cacheKey = $this->keyGenerator->generateKey($key);
        $cache = $this->cacheManager->getCache($name);
        $serialized = $this->serialize($value);
        $cache->set($cacheKey, $serialized);
    }

    public function deleteFromCache($key, $name)
    {
        $cacheKey = $this->keyGenerator->generateKey($key);
        $cache = $this->cacheManager->getCache($name);
        $cache->delete($cacheKey);
    }

    public function serialize($object)
    {
        return $this->serializer->serialize($object, 'json');
    }

    public function deserialize($data, $class)
    {
        return $this->serializer->deserialize($data, $class, 'json');
    }
}

1 Answer 1

3

You could create a third class that manages both the repository and the cache. Then, don't manually try to get the cached value and if not available query the repository like

$activity = $this->cacheHelper->getFromCache($key, $name, "ArrayCollection<DW\DWBundle\Entity\Activity>");
    if ($activity == null) {
        $activity = $this->activityRepository->findRecentCommentActivity($limit);
        $this->cacheHelper->saveToCache($key, $name, $activity);
    }

Rather have your CachedRepository answer it:

$activity = $this->cachedRepository->getFromCacheOrRepository($key, $name, "ArrayCollection<DW\DWBundle\Entity\Activity>");

The cache and repository query logic can now be implemented in the CachedRepository class and therefore be reused.

3
  • That's a good idea actually and it'll allow me to test the CachedRepository independent of the database.
    – Jonathan
    Commented Jan 5, 2015 at 22:39
  • So just have the CachedRepository return from the cache and the DatabaseRepository return from the database and then the third "BothRepository" do the logic above?
    – Jonathan
    Commented Jan 5, 2015 at 22:41
  • Exactly, the "BothRepository" knows both a repository and some cache. It then is responsible for getting the values either from the cache or (if it is not there) from the repository - and can fill the cache. It should also implement the RepositoryInterface because it behaves like a repository, while under the hood using caching. But that implementation detail should be not of concern by the caller.
    – valenterry
    Commented Jan 6, 2015 at 7:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.