Writing cache based repositories for web application prototyping
When I started building in-house demo application I thought about how to solve temporary data layer so I don’t have to use real database and object mappings for it. Playing with new object model and new components I move way faster if I don’t have any additional ballast that I can avoid. So I wrote simple cache based repository mechanism I can use to imitate real repositories that I will write in the future.
NB! Don’t develop this idea too much further because you can use my method only to some certain point. Don’t forget that you have also maintain relations between objects and keep them consistent. Use my method until your object model is small and simple.
If you look for something perfect you don’t find it here. It is just my pretty raw solution that I still test and I am not very sure if I don’t replace it with something better soon.
Structure of my application
Basically my application follows structure show here (example data, just took it from air):
- ppName.Core (all business classes and interfaces are here)
- Contacts
- Person.cs
- Company.cs
- …
- Projects
- Project.cs
- ProjectTask.cs
- …
- Repositories
- IPersonRepository
- ICompanyRepository
- …
- BusinessEntity.cs
- Contacts
- AppName.Data.CacheBasedStorage (this is the topic of this posting)
- Repositories
- BaseRepository.cs
- PersonRepository.cs
- CompanyRepository.cs
- …
- Repositories
- AppName.Data.NHibernate (not there really, but comes if users like my app)
- AppName.Infrastructure (IoC, validation and other “low level” stuff)
- Resolver.cs
- Validator.cs
- …
- AppName.Web (web version of my application)
I think this structure tells almost everything about architecture. Okay, some notes too. AppName.Core has only interfaces for repositories because it doesn’t deal with objects persisting and doesn’t provide any Data Access Layer logic. It is all done in external repository implementations. I use Unity as IoC container and Resolver class in infrastructure library is mediator between client applications and repository implementations. I can always move from Unity to some other IoC container and client applications doesn’t need to be built again.
Why cache based storage?
I considered different ways about how to implement my toy-repositories and cache seemed to me as pretty good option:
- it is accessible to all users of my application,
- it is easy to use,
- it needs no additional coding for saving and loading data,
- it’s not a problem when data expires – I need data for building and demonstration purposes only.
Of course, it is also possible to use some more elegant solutions but … I am just too lazy to run for idealism and beautiful things. When I get something that works right now for me – I am happy. In this point I don’t know if users like my app or not and I don’t want to make any moves I can avoid.
Implementation of repositories
Cache based data storage implementation is in AppName.Data.CacheBasedStorage library. In short we have abstract class BaseRepository that does all the dirty work for us. The real repositories extend it and they also follow repository interfaces from core library. Basically they wrap calls to base library with correct types.
Let’s start with BaseRepository. The implementation shown here is awful in multi-user context, but for one-man testing and developing it is okay.
public abstract class BaseRepository
{
private Cache _cache;
protected BaseRepository()
{
// Here we add some sample data to cache
}
private Cache Cache
{
get
{
if (_cache != null)
return _cache;
_cache = HttpContext.Current.Cache;
return _cache;
}
}
protected T Get<T>(int id)
{
var list = GetList<T>();
if (list == null)
return default(T);
if (list.ContainsKey(id))
return list[id];
return default(T);
}
protected void Save<T>(T instance) where T : BusinessEntity
{
var list = GetList<T>();
var id = instance.Id;
if (id == 0)
{
if (list.Count > 0)
id = (from l in list
orderby l.Key descending
select l.Key).First();
id++;
instance.Id = id;
if (list.ContainsKey(id))
list[id] = instance;
else
list.Add(id, instance);
}
else
list[id] = instance;
}
protected void Delete<T>(T instance) where T : BusinessEntity
{
var list = GetList<T>();
if (list.ContainsKey(instance.Id))
list.Remove(instance.Id);
}
protected IDictionary<int, T> GetList<T>() where T : BusinessEntity
{
var type = typeof(T).ToString();
if (Cache[type] != null)
return (IDictionary<int, T>)Cache[type];
var list = new Dictionary<int, T>();
Cache[type] = list;
return list;
}
}
Now you may have question: what about data querying? My answer is simple: let’s take LINQ and use it. We don’t have tons of data, we don’t have millions of users – we have only our development machine and one or two users.
And here is one example repository. It is very simple and as stated above it just wraps calls to base repository with correct types. Of course, if I need some more logic in repositories then I can add it to appropriate methods. Later, if users like my applications, I can take this logic from my toy-repositories and put it in real repositories.
public class CompanyRepository : BaseRepository, ICompanyRepository
{
public Company GetCompanyById(int id)
{
return Get<Company>(id);
}
public CompanyName GetCompanyNameById(int id)
{
return Get<CompanyName>(id);
}
public IList<Company> ListCompanies()
{
return GetList<Company>().ToList();
}
private void Save(Company company)
{
Save<Company>(company);
}
public void SaveCompanyName(CompanyName name)
{
Save<CompanyName>(name);
}
public void Delete(Company company)
{
Delete<Company>(company);
}
public void DeleteCompanyName(CompanyName name)
{
Delete<CompanyName>(name);
}
}
Well, this is my current experiment with repositories that should save me some time and be flexible enough to get working prototypes done. I also like the idea that I can keep my object model evolving with my prototype so I create additional value when building prototypes – when I have to start real coding I can use classes I wrote during prototyping and hopefully it saves me some time.
Instead of directly putting cache into repository better to use decorator pattern and achieve the same thing. A much better and cleaner approach as per my view.
The cache can be pruned AT ANY TIME. You should never check the cache and then act as if it was populated without taking a copy/reference of the data cached:
In you GetList method, you do:
if (Cache[type] != null)
return (IDictionary)Cache[type];
The Cache entry could be pruned between the two lines (it happens!) and you would return back a null value. You should do this:
protected IDictionary GetList() where T : BusinessEntity
{
var type = typeof(T).ToString();
var current = Cache[type] as IDictionary;
if (current != null)
return current;
var list = new Dictionary();
Cache[type] = list;
return list;
}
Thanks for comments, guys :)
Pradeep, can you explain your idea more closely? Currently I’m targeting NHibernate when it’s time to move to real repositories.
Marc, you have good point. The other option I consider besides cache is using application variables as they come and go with application instance.
Your code store references to instances. So when I retrieve an object from the in-memory repository and change a property value, then that change is immediately available to everyone before I have saved it.
And if I then want to cancel the change, I can’t just discard the object.
Is that your intention? Have you considered using the XmlSerializer and store the serialized version instead?
Yeah, my only goal is to have minimum overhead when prototyping and playing with my object model. Those repositories are toys that just keep objects somewhere and are able to produce reality-like results. They have nothing to do with real repositories and real DAL logic.
Just think about using some O/R mapper by example. When I add new property to class then I have to change database and class mapping too. I have to test if mapper handles new property correctly. In the early stage application just doesn’t need this kind of ballast. Also think about deployment – currently I don’t need database deployment and configuration.
I like when I can make changes to prototype quickly and customer is able to see how things look like after changes.