Na obsah stránky

Single Responsibility Principle in the wild

Aleš Roubíček | | # permalink

Tak jsem se tak koukal na poslední CloudCover, kde představovali novou službu Windows Azure AppFabric Caching. Služba je to jistě zajímavá a je jednou z možností jak zajistit session state affinity v Azure. Ale proč o tom píšu.

Součástí epizody byla i živá ukázka, jak přidat cachování do existující aplikace.

Teď to možná některé NHibernate znalé veterány zaskočí, ale v Microsoftím světě ORM si o něčem jako 2nd level cache můžete nechat zdát. Vůbec tam spoustu krásných věcí nemají, ale o tom třeba až jindy.

No prostě tam měli repository (používat repository, tam kde je LINQ, je už samo o sobě obskurní, ale budiž), která vypadala nějak tak:

public class ProductsRepository : IProductsRepository {

  public List<string> GetProducts() {

    List<string> products = null;

    NorthwindEntities context = new NorthwindEntities();
    var query = from product in context.Products
                select product.ProductName;

    products = query.ToList();

    return products;
  }
}

Mno, měli tedy respository produktů, která vracela názvy všech produktů přímo z databáze. No a pak se jali přidat cachování takového listu:

public class ProductsRepository : IProductsRepository {

  DataCacheFactory dcf;

  public ProductsRepository() {
    dcf = new DataCacheFactory();
  }

  public List<string> GetProducts() {

    List<string> products = null;

    DataCache dc = dcf.GetDefaultCache();

    products = dc.Get("products");

    if (products != null) {
      return products;
    }

    NorthwindEntities context = new NorthwindEntities();
    var query = from product in context.Products
                select product.ProductName;

    products = query.ToList();

    dc.Put("products", products);

    return products;
  }
}

Úžasné! Jenže tím jsme porušili princip jediné zodpovědnosti. Teď se nám repository účastní hned na dvou záležitostech – na své původní (načíst data z databáze) a nově na cachování načtených dat. Takže to smrdí. It smels like refactoring time!

Jak na to?

Cachování je věc hodná vlastního aspektu. Když se podíváme na výsledný kód, zjistíme, že jsme neudělali nic jiného než, že jsme odekorovali načítání dat logikou pro cachování. Tudíž zvolíme vzor dekorátor! Repository vrátíme do původního stavu, protože ten svůj účel splňuje a přidáme další třídu s jedinou zodpovědností:

public class CachedProductsRepository : IProductsRepository {

  const string ProductsKey = "products";

  readonly DataCacheFactory cacheFactory;
  readonly IProductsRepository repository;

  public CachedProductsRepository(DataCacheFactory cacheFactory, IProductsRepository repository) {
    this.cacheFactory = cacheFactory;
    this.repository = repository;
  }

  public List<string> GetProducts() {

    var cache = cacheFactory.GetDefaultCache();
    var products = cache.Get(ProductsKey);

    if (products == null) {
      products = repository.GetProducts();
      dc.Put(ProductsKey, products);
    }

    return products;
  }
}

No a máme to! Dokonce si teď můžeme vybrat, kdy chceme používat cache nebo sahat do databáze přímo. A to vše díky díky dodržování Open/Closed principle a jednoduché kompozici objektů, které dělají jen a pouze to, co mají. :)

Našli jste v článku chybu? Máte námět na reportáž? Založte mi ticket.