1

I am looking to cache an expensive query using ServiceStack.

My idea is as follows

Step 1: Cache entire database view to Redis which expires after a day

Step 2: When client calls API route "/cached/balances/{Date}", the service only returns a subset of the cached database view.

To implement this, I am using

Request and Response Models

[Route("/balances", "GET", Notes = "A call to Db which extracts the entire Db balance view")]
public class GetBalances : IReturn<GetBalancesResponse>
{

}

[Route("/cached/balances/{Date}", "GET", Notes = "Gets a subsection of cached balanced")]
public class GetCachedBalances : IReturn<GetBalancesResponse>
{
     public int Date { get; set;} 
     public int? EntityId { get; set;} 
}

public class GetBalancesResponse
{
    public List<BalanceView> Result { get; set; }
}

Service Interface

 public object Any(GetBalances request)
 {
    var balances = Db.Select<BalanceView>();
    return new GetBalancesResponse {Result = balances };
 }

 public object Any(GetCachedBalances request)
 {
       GetBalances balancesRequest = request.ConvertTo<GetBalances>();

     // get the cached response
      var allBalances = (CompressedResult)base.Request.ToOptimizedResultUsingCache(
                this.CacheClient, "urn:balances", () => {
                    var service = this.ResolveService<BalanceServices>();
                    return (GetBalancesResponse)service.Any(balanceRequest);
                });

            // filter the results -> this does not work!
            var filterBalances = response.Response.ConvertTo<GetBalancesResponse>().Result.Where(Ar => Ar.Date == request.Date && (Ar.EntityId == request.EntityId || request.EntityId == null)); 

    return new GetBalancesResponse {Result = filteredBalances};
 }

The filtering of balances does not work as I cannot seem to convert the compressed result to the ResponseDto.

Mark
  • 2,175
  • 20
  • 23

1 Answers1

1

I may have an answer to my own question.

The ServiceInterface GetCachedBalances can be filtered as per below

  public object Any(GetCachedBalances request)
  {
     GetBalances balancesRequest = request.ConvertTo<GetBalances>();

     // cache everything
     var filteredCachedResponse = (CompressedResult)base.Request.ToOptimizedResultUsingCache(
            this.CacheClient, "urn:balances", () => {
                var service = this.ResolveService<BalanceServices>();
                return ((GetBalancesResponse)service.Any(balanceRequest)).Result.Where(Ar => Ar.Date == request.Date && (Ar.EntityId == request.EntityId || request.EntityId == null));
            });

     // filter cached response
     var filteredResponse = base.Cache.Get<GetBalanceResponse>("urn:balances").
                    Result.Where(Ar => Ar.Date == request.Date && (Ar.EntityId == request.EntityId || request.EntityId == null));

      return filteredResponse;
   } 
Mark
  • 2,175
  • 20
  • 23
  • Note the CompressedResult encapsulates compressed serialized bytes in its the final form that they're written directly to the ResponseStream in. So you can't deserialize it back into a DTO, you would need to either store/retrieve the DTO in the cache directly (i.e. not using ToOptimizedResult), or do any processing before you cache the result as you're doing here. – mythz Mar 02 '16 at 12:17
  • @mythz - just realised my own answer is not entirely what I am looking for. I was hoping to cache the entire DbView and then only return a subset of the cache. However this does not seem possible as the only way of doing that would be to somehow deserialize it back into a DTO & filter it according to the request parameters. – Mark Mar 02 '16 at 12:42
  • 1
    You can cache and retrieve DTOs by accessing base.Cache directly. – mythz Mar 02 '16 at 13:07
  • I am not sure giving a "Cache" (prefix/sufix) to your DTOs is a good choice. Cache should be inside your service logic, and inside of it, you test if values are cached or not, and update the cache if not present. – labilbe Mar 02 '16 at 13:19
  • @mythz - thanks base.Cache is exactly what I was looking for. Will update my answer for future references. – Mark Mar 02 '16 at 13:34
  • 1
    @labilbe - agreed. I was thinking of making the '/balances/' route not visible such no clients can only access the cached route. alternatively it may be best to rename the route from cached/balances to balances – Mark Mar 02 '16 at 13:34