4

I have a master base class for (almost) all the POCO classes in my application.

public abstract class AceOfBase
{
    public long Id { get; set; }
    public DateTimeOffset CreatedOn { get; set; }
    public string Key { get; set; }
}

Some of my main classes(Member , Building, Community, etc.) have a fair amount of properties so I plan on taking advantage of the easy management ServiceStack's Strongly-Typed Client provides.

From the research I've done it seems that the client uses a structured methodology for creating keys

Essentially POCOs gets stored into Redis as serialized JSON with both the typeof(Poco).Name and the Id used to form a unique key for that instance. E.g:

urn:Poco:{Id} => '{"Id":1,"Foo":"Bar"}'

Because at times I will be retrieving an object that inherits the base class(but which I do not know the derived class of at compile-time) how can I best go about storing the key so I can get any object easily?

An an ideal world I would be able to configure RedisClient and RedisTypedClient to use the same naming convention for keys but I haven't found anything that lets me do this(yet) and documentation on individual methods is hard to come by for both clients.

Community
  • 1
  • 1
Matt Foxx Duncan
  • 2,074
  • 3
  • 23
  • 38

1 Answers1

1

After digging through the ServiceStack assembly I found that the strongly-typed client's Store() method is actually just SetEntry(key,value) with a utility method for generating the key structure.

The utility method IdUtils.CreateUrn has a few overloads that let you specify Type and Id and then returns a string used as the key.

What I have done, then, is write a CreateKey method in my Repository base class which I then call when any AceOfBase object is created and set it to Key.

//In BaseRepository
public virtual string CreateKey<T>(T entity) where T : AceOfBase
{
 return IdUtils.CreateUrn<AceOfBase>(entity.Id);
}

//In MemberRepository
Member m = new Member(); //inherits from AceOfBase
m.Key = CreateKey(m);

I've implemented two sets of classes in my application as well, these help me take advantage of Redis' crazy fast query time while allowing me to use relationships normally as I would with EF.

  1. DomainModels -- These look like normal classes in that their properties are traditionally structured.

    public class Activity : AceOfBase
    {
        public AceOfBase IndirectObject { get; set; }
        public Participant Predicate { get; set; }
        public Participant Subject { get; set; }
        public Verb Verb { get; set; }
    }
    
  2. DataModels -- These contain the same primitive and enums as their DomainModel counterparts but if a field is a custom type that is stored in Redis it is replaced with a string(with the same property name) representing the key for that object.

    public class Activity : AceOfBase
    {
        public string IndirectObject { get; set; }
        public string Predicate { get; set; }
        public string Subject { get; set; }
        public Verb Verb { get; set; }
    }
    

I have implemented AutoMapper with a set of custom TypeConverter classes that do the heavy lifting of converting DataModel string properties to the respective property with the same name in the DomainModel. AutoMapper then gets called immediately before storing any object and after an object has been pulled from Redis and translates between the two model types.

//In Configure()
Mapper.CreateMap<string,Member>().ConvertUsing<KeyToBaseConverter<Member>>();
Mapper.CreateMap<Member, string>().ConvertUsing<BaseToKeyConverter<Member>>();

public class KeyToBaseConverter<T> : ITypeConverter<string, T> where T : AceOfBase
{
    public RedisRepository Repository { get; set; }
    public T Convert(ResolutionContext context)
    {
        return Repository.GetByKey<T>(context.SourceValue.ToString());
    }
}
public class BaseToKeyConverter<T> : ITypeConverter<T, string> where T : AceOfBase
{
    public string Convert(ResolutionContext context)
    {
        var f = context.SourceValue as AceOfBase;
        return f.Key;
    }
}

The only component left to address is ensuring that any Lists or Sets I create get the same formatting for their key.

Using this schema I will be able to seamlessly use the strongly-typed client alongside the string-client, ensuring that even when pulling objects without the strongly-typed client I can determine and safely cast the JSON blob into the correct Type by examining only the Key property.

Matt Foxx Duncan
  • 2,074
  • 3
  • 23
  • 38