Returning correct object type with ToOptimizedResultUsingCache()

It’s been a while since I used Cache in SS, and back then we didn’t have the magic of ToOptimizedResultUsingCache(), and for such, this is my first steps using it…

I have a simple response:

public Shop Get(GetShop request)
{
    return _shopService.GetShop(request.ShopId, request.PortalId);
}

and, by implementing the magic, I will get:

public object Get(GetShop request)
{
    var cacheKey = $$"api:shops:GetShop-shop-{request.ShopId}-portal-{request.PortalId}";
    return base.Request.ToOptimizedResultUsingCache(
        base.Cache,
        cacheKey,
        () => _shopService.GetShop(request.ShopId, request.PortalId));
}

I can see that ToOptimizedResultUsingCache accepts a type, like ToOptimizedResultUsingCache<Shop> but I can’t manage to use it correctly so I can return a Shop object instead of object in the call…

Maybe I’m being picky, but is there a way I can still use public Shop Get(GetShop request) instead have to express object?

I understand that the object is actually a CompressedResult type, but… using ToOptimizedResultUsingCache<Shop> it should cast to a Shop object right?

P.S.

I’ve tried direct cast as

public Shop Get(GetShop request)
{
    var cacheKey = $$"api:shops:GetShop-shop-{(request.ShopId.HasValue ? request.ShopId : 0)}-portal-{(request.PortalId.HasValue ? request.PortalId : 0)}";
    return (Shop)base.Request.ToOptimizedResultUsingCache<Shop>(
        base.Cache,
        cacheKey,
        () => _shopService.GetShop(request.ShopId, request.PortalId));
}

but all I got was:

Unable to cast object of type ‘ServiceStack.CompressedResult’ to type ‘…Shop’.

ToOptimizedResult will return a cached bytes result (i.e. CompressedResult) when it’s retrieved from the Cache so it can’t have a Shop type (the compressed bytes are emitted directly to the response, it’s never converted back to Shop Type) but the new [CacheResponse] attribute lets you decorate your Services with a simple attribute, e.g:

[CacheResponse(Duration=60)]
public Shop Get(GetShop request)
{
    return _shopService.GetShop(request.ShopId, request.PortalId);
} 

But in order to be generically useful it typically caches based on Time however it does allow for advanced customization where you can do things like change the cache key used, etc.

I was used to the

var product = cache.Get<Product>(cacheKey, () => _products.GetProductById(request.ProductId) );

kind’a flow … so I will never be able to cache and use the type object … ohh well, we can always go full request cache using that new decorator :smile:

will do for now, but I will try to explore more later on, Thank you @mythz!

1 Like

The main difference I can see currently is

public object Get(GetContact request)
{
    var cacheKey = $$"api:contacts:contact-{request.ContactId}";
    return base.Request.ToOptimizedResultUsingCache(
        base.Cache, cacheKey, TimeSpan.FromMinutes(30), () => _contactService.GetContact(request.ContactId));
}

returns a cached response in under 7ms but using:

[CacheResponse(Duration = 60 * 30)]
public object Get(GetContact request)
{
    var cacheInfo = (CacheInfo)base.Request.GetItem(Keywords.CacheInfo);
    cacheInfo.KeyBase = $$"api:contacts:contact-{request.ContactId}";

    return _contactService.GetContact(request.ContactId);
}

the cached response takes around 60ms in contrast …

in a high-traffic API, this is actually something to consider…

(just to compare, a non-cached response takes around 320ms)

If you’re changing the Key, you’ll also want to do another cache check with the new key to short-circuit the response, e.g:

[CacheResponse(Duration = 60 * 30)]
public object Get(GetContact request)
{
    var cacheInfo = (CacheInfo)base.Request.GetItem(Keywords.CacheInfo);
    cacheInfo.KeyBase = "api:contacts:contact-" + request.ContactId;
    if (Request.HandleValidCache(cacheInfo))
        return null;

    return _contactService.GetContact(request.ContactId);
}

Otherwise before changing the key, make sure that the default Cache Key which uses the /path/info isn’t suitable first, e.g:

[CacheResponse(Duration = 60 * 30)]
public object Get(GetContact request) => contacts.GetContact(request.ContactId);

Also the [CacheResponse] attribute can also benefit from enabling HTTP Client Caching which can eliminate server requests entirely, which you can opt-in to by specifying a MaxAge, e.g:

[CacheResponse(Duration = 60 * 30, MaxAge = 30 * 30)]
public object Get(GetContact request) => contacts.GetContact(request.ContactId);

Because the speed increase is so much higher by using ToOptimizedResultUsingCache() I changed all the code to use that instead of the Decorator CacheResponse.

60ms vs 7ms is a lot, and now the API response is just blazing fast!

:smile:

They’re not comparable if you change the cache key without short-circuiting the response. If you want to compare them don’t change the key.