1
\$\begingroup\$

I am repeating this code at lot of places:

public class ProductTypeService : IProductTypeService
{
    public async Task<IList<ProductType>> GetAllAsync(int sectionId = -1)
    {
        string key = "ProductTypeService_" + "GetAllAsync" + sectionId;
        var list = _memoryCacheService.Get<IList<ProductType>>(key);
        if (list == null)
        {
            list = await _repository.GetAllAsync(sectionId);
            _memoryCacheService.Save(key, list);
        }
        return list;
    }

public class VendorService : IVendorService
{
    public async Task<IList<Vendor>> GetAllAsync()
    {
        string key = "VendorService_" + "GetAllAsync";
        var list = _memoryCacheService.Get<IList<Vendor>>(key);
        if (list == null)
        {
            list = await _repository.GetAllAsync();
            _memoryCacheService.Save(key, list);
        }
        return list;
    }

The key is the combination of class + method + parameter. The only change is _repository.XX(XX, XX, ...........). How can I make it DRY?

\$\endgroup\$
  • 2
    \$\begingroup\$ Give us more context: add 2-3 more examples where this is repeated, and also the code that calls these methods. \$\endgroup\$ – janos Oct 23 '14 at 6:56
  • \$\begingroup\$ @janos, updated question and added another example. \$\endgroup\$ – user960567 Oct 23 '14 at 7:11
2
\$\begingroup\$

First we don't review:

  • ProductType
  • Vendor
  • IProductTypeService (just a little)
  • IVendorService
  • IRepository (just a little)
  • what is _memorxCahceService? I will use ObjectCache.

What we need to discuss?

A generic service implementation which will contain the common methods and implementation.

BaseService

We will need a class which can be the bridge to the repository and the cache so define it aas BaseService. T means we will use it in different repository+cache combose int a generic way. No black magic here, move on.

First we need to extract the common things from the two service implementation in you question:

  • get a cache key
  • try load from cache the data
public abstract class BaseService<T>
{
    private ObjectCache ObjectCache { get; set; }
    private IRepository<T> Repository { get; set; }

    protected BaseService(IRepository<T> repository)
        : this(MemoryCache.Default, repository)
    {
    }

    protected BaseService(ObjectCache objectCache, IRepository<T> repository)
    {
        ObjectCache = objectCache;
        Repository = repository;
    }

    protected virtual string GetCacheKey(string suffix, int sectionId = -1)
    {
        return GetType().Name + "_" + suffix + sectionId;
    }

    protected virtual async Task<ICollection<T>> GetAllAsyncValueFactory()
    {
        return await Repository.GetAllAsync();
    }

    public virtual async Task<ICollection<T>> GetAllAsync(int sectionId = -1)
    {
        var key = GetCacheKey("GetAllAsync", sectionId);

        var list = ObjectCache.Get(key) as ICollection<T>;

        if (list == null)
        {
            return ObjectCache.AddOrGetExisting(key, await GetAllAsyncValueFactory(), new CacheItemPolicy
            {
                //TODO: define cache policy
            }) as ICollection<T>;
        }

        return list;
    }
    }

What do we here is that we defining in the constructor what we will need: a repository and an ObjectCache but we could you a default MemoryCache instance if we don't get anything other.

The main thing is i have extracted all the logic you are using commonly in your code but with a generic implementation (hint: don't use IList is a dirty interface). Every method is virtual so they can be easily override in derived classes you you can define different cache keys or you can tell how you would want to get the data from the current repository.

Interface implementation

Because the generic base class we only need to define the constructors in our interface implementation classes as you can se:

public class ProductTypeService : BaseService<ProductType>, IProductTypeService
{
    public ProductTypeService(ObjectCache objectCache, IRepository<ProductType> repository)
        : base(objectCache, repository)
    {
    }

    public ProductTypeService(IRepository<ProductType> repository)
        : base(repository)
    {
    }
}

The constructor implementation will be different based on your unknown needs.

\$\endgroup\$
  • \$\begingroup\$ GetAllAsync is not the only method. The repository method name can anything and parameters can be arbitrary. \$\endgroup\$ – user960567 Oct 23 '14 at 12:10
  • \$\begingroup\$ Then implement them in the BaseService<T> class. \$\endgroup\$ – Peter Kiss Oct 23 '14 at 15:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.