Background
In a cache management pattern, usually the object that stored in the cache will exactly same with the object returned from the real data store.
Following diagram shows the working of cache:
As shown in diagram above, the cache repository agent (CRA) will responsible to return object requested by the client.
1. Client request Object.
2. CRA will check to the cache, if the object available, than return it back to client.
3. If not exist in the cache, get the object from repository (REP) (i.e. database repository), return to the client and asynchronously add it to the cache.
Problem
With the following condition, some problems might be appeared when the usage of CRA and Cache is come after the system running well in the model of Client <-> REP. The problems are:
1. Object returned by REP is not the one that expected by CRA to be stored in the Cache.
2. More precisely, if the CRA expects object which contains a raw data structure, but the REP return an object which contains processed data structure.
Solving the problems above is actually the objective of this article. We will assume the object used by REP and CRA is not a simple object such like a Person object which can be retrieved using a key/ID (REP.GetByID(id) or CRA.GetByID(id)), but the object that contains collection of item, that act like a table in database but stored in the Cache (we call it CollectionObjectInfo). Following diagram shows the sample structure of CollectionObjectInfo:
While the object that returned by REP is already in product format, i.e.: an xml string produced from the combining subset data of the DataList:
1: <?xml version="1.0" encoding="utf-8"?>
2: <ObjectInfo>
3: <data>v11, v12, v1n</data>
4: <data>vm1, vm2, vmn</data>
5: </ObjectInfo>
Just realize and smell something bad in my code. Call a method with passing boolean if the logic inside the method is enable and check it inside that method.
Let me show one of a method that uses a boolean parameter.
1: public static T Get(bool isEnable, string key)
2: {
3: if (isEnable)
4: {
5: //Main Logic.
6: }
7:
8: return default(T);
9: }
The method above is a member of some kind of a façade class that try to make our life simpler when need logic steps inside the method.
The caller itself will pass the boolean parameter based on configuration related with their context. So let see two callers use this façade:
-
In first caller class, let say class RepositoryOne<T>:
1: protected ObjectInfo<T> getFromFacade(Guid objectID)
2: {
3: return FacadeClass<ObjectInfo<T>>.Get(IS_ENABLE, objectID.ToString());
4: }
-
In the other class, let say class RepositoryTwo:
1: public override IPrincipal Find(IPrincipal sample)
2: {
3: StoreIdentity sampleIdentity = (StoreIdentity)sample.Identity;
4: IPrincipal principal = (IPrincipal) FacadeClass <StorePrincipal>.Get(IS_ENABLE, sampleIdentity.Name);
5:
6: //Main Logic.
7:
8: return principal;
9: }
Btw, where is IS_ENABLE come from? IS_ENABLE Is a readonly variable in class level which the value set and got from a config value.
Ok, back again, the objective is to make the usage of IS_ENABLE to be as efficient as possible, even though all the codes above will run smoothly without any warning or error message. Fortunately we're the master, who can smell something not good appears in front of our face. Let me make it clear, to make the usage of IS_ENABLE to be as efficient as possible is never pass it to the façade. Let the logic be handled by the caller.
The Caller
Now, let me define the caller. The caller is any class that needs the façade. In the current case all the callers are some kind of Repository that need to retrieve something from the façade. And also, in the current case the all callers are inherited from one class, let say RepositoryBase<T> class.
In this case also, yes, the all callers should have a configuration to be able to use the façade. However, in the other case or other class, yes the other class can use the façade without has to be bothered with any of configuration, just call it and get from it.
Now, it makes sense that what I wrote has something wrong in it. Please see conditions below:
-
One or more classes should have config value to determine whether they'll be able to call the façade or not.
-
Other classes don't have to worry with this determination.
Solution
It has clear now that only the repository things need this: IS_ENABLE.
Let us refactor the façade class to remove all IS_ENABLE parameters usage.
Now, the façade will look like this:
1: public static T Get(string key)
2: {
3: //Main Logic.
4:
5: return default(T);
6: }
The problem was on the caller, more specifically is on the base class, RepositoryBase<T> that should encapsulate the process call the façade so the inherited classes will use this call method efficiently. I add following members inside the base class:
-
A readonly variable of IS_ENABLE which is moved from the child classes.
-
Constructor that got the value for IS_ENABLE from configuration owned by each child class.
-
New get method which uses IS_ENABLE to filter call to the façade, so the filter now happens in the caller.
1: protected readonly bool IS_ENABLE;
2:
3: public BaseAgentRepository(bool isEnable)
4: {
5: IS_ENABLE = isEnable;
6: }
7:
8: protected T get(string key)
9: {
10: if (IS_ENABLE)
11: {
12: return FacadeClass<T>.Get(key);
13: }
14:
15: return default(T);
16: }
The consequence for the child classes is to override the constructor to pass the config value for IS_ENABLE and call the get to façade using the one in base class.
-
In RepositoryOne<T>:
Constructor:
1: public RepositoryOne()
2: : base(OneConfig.Instance.Tuning.Facade.Enable)
3: {
4: }
Change in getFromFacade:
1: protected ObjectInfo<T> getFromFacade(Guid objectID)
2: {
3: return get(objectID.ToString());
4: }
-
In RepositoryTwo<T>:
Constructor:
1: public RepositoryTwo()
2: : base(TwoConfig.Instance.Facade.Enable)
3: {
4: }
Change in Find method:
1: public override IPrincipal Find(IPrincipal sample)
2: {
3: SampleIdentity sampleIdentity = (SampleIdentity)sample.Identity;
4: IPrincipal principal = (IPrincipal)get(sampleIdentity.Name);
5:
6: //Main Logic.
7:
8: return principal;
9: }
Conclusion
None of the code can be perfect at the first time, at least which happens in lot of my code. Event one variable can mess up your application a lot, not for the warning or error, but for the smell.
However, this solution is not the best, in the feature, if the smell appears as bad as the current condition, I'll let the code to adapt.