Klávesové zkratky na tomto webu - rozšířené Na obsah stránky

Užití repository v řadičích

10.18 - 25. ledna 2009 | ASP.NET 2.0

Dost už bylo infrastruktury, pojďme si ji trochu užít! :) Dneska si ukážeme jak používat naší infrastrukturu, která by nám měla spoustu věcí usnadnit. Proto jí vlastně tady tvořím.

Dependency injection a bindování modelu

Vytvoříme si řadič, který bude pracovat řekněme s uživateli – to je taková věc, která je nutná snad v každé aplikaci. Ruku na srdce, aplikace bez uživatelů je tak trochu k ničemu. Tak pojďme na to!

[HandleErrors]
public class UsersController : System.Web.Mvc.Controller {

  private IUsersRepository _usersRepository;

  public UsersController(IUsersRepository usersRepository) {
    _usersRepository = usersRepository;
  }

  [ActionName("Rest")]
  [AcceptVerbs(HttpVerbs.Post)]
  public ActionResult Create(FormCollection forms) {

    var user = new User();
    if (ModelState.IsValid == false) {
      throw new ValidationException();
    }

    UpdateModel(user, forms.ToValueProvider());
    _usersRepository.Save(user);

    return Json(user);
  }
}

Krátké a úderné. Jde o klasický řadič, který má závislost na repository uživatelů. Tuto závislost si nechává nainjektovat přes konstruktor. O její životní cyklus se nestará, to řeší IoC kontejner. Pak tu máme akci, která zpracovává POST dotazy řekněme na URL /Users, to teď není podstatné. Tato akce by měla vytvořit nového uživatele z dat, která přišla v POSTu (FormCollection).

Pokud nejsou data v pořádku, tak vyhodíme validační výjimku. O to, jak se tato data mapují na model uživatele, se opět řadič nestará, to je věc model binderu. Pokračujeme dále k uložení uživatele, někam. Kam a jakým způsobem je řadiči opět šumák, to je starost repository. Nakonec pošleme zpátky hotového uživatele jako JSON objekt.

A to je pro dnešek vše… :)

Persistence modelu pomocí ActiveRecord

15.16 - 21. ledna 2009 | ASP.NET 2.0

V architektuře naší MVC aplikace jsme se dostali již poměrně daleko. Vybrali jsme si šablonovací systém, máme vyřešenou integraci s IoC kontejnerem, vymazlené routování, připravené model bindery a teď je na čase nějakým způsobem model persistovat. Existuje mnoho způsobů, jak toho dosáhnout.

  1. Persistenci si můžete řešit sami na úrovni ADO.NET. Jenže proč řešit něco, co už někdo stokrát vyřešil?
  2. Použít nějaký ORM nástroj.

ORM nástrojů na výběr máme dnes tolik, že je stěží na prstech spočítáme, jaký si tedy vybrat? Pro mnohé bude první volbou LINQ to SQL. Pro jednoduché mapování (1:1) a tam, kde je možné být závislý pouze na MS SQL, je toto řešení dostačující. Osobně preferuju malinko složitější cestu, ale ve výsledku myslím, že mnohem mocnější. :)

Castle ActiveRecord a Repository pattern

Já ve svých posledních proktech používám Castle ActiveRecord, což je implemenatce návrhového vzoru ActiveRecord nad ORM frameworkem NHibernate. V mnoha ohledech práci s NHibernatem značně zjednodušuje, ať už je to mapování, automatickou správu session a podobně. Způsoby užití jsme už popsal ve spotu ActiveRecord vs. Repository pattern. Další výhodou je, že vše, co funguje v NHibernate (LINQ, Spatial, vlastní mapovací typy), můžete použí i s Castle ActiveRecord.

Datové třídy však nedědím z bázové třídy ActiveRecordBase, ale používám Repository, která se stará o CRUD operace s entitou. Pokud chcete mít mapování a závislost na ActiveRecord snadno odstranitelnou, můžete mapovací atributy vyčlenit třeba do samostatného partial souboru. Ale nejspíš nikdy v životním cyklu vaší aplikace nebudete ORM framework měnit, takže se trápit metadaty o databázovém schema je celkem zbytečné…

Základem pro repository je generické rozhraní IRepository<>, pro snadnou automatickou registraci repositoří v kontejneru ještě nadefinujeme ještě i negenerické rozhraní:

using Castle.ActiveRecord;

public interface IRepository {}

public interface IRepository<Tentity> : IRepository where TEntity : class {
  TEntity FindById(int id);
  void Save(TEntity entity);
  void Delete(TEntity entity);
}

public abstract class ActiveRecordRepositoryBase<TEntity>
  : IRepository<TEntity> where TEntity : class {
  public virtual TEntity FindById(int id) {
    return ActiveRecordMediator<TEntity>.FindByPrimaryKey(id);
  }
  public virtual void Save(TEntity entity) {
    ActiveRecordMediator<TEntity>.SaveAndFlush(entity);
  }
  public virtual void Delete(TEntity entity) {
    ActiveRecordMediator<TEntity>.DeleteAndFlush(entity);
  }
}

Pojďme si podporu pro ActiveRecord integrovat do naší MVC aplikace.

Integrace přes Binsor

Výhodou projektů z Castle, je, že se mezi sebou dokážou integrovat a využívat navzájem výhod své synergie. Nejprve si do projektu přidáme referenci na knihovnu Castle.ActiveRecord a ještě Castle.Facilities.ActiveRecordIntegration. Následně zaregistrujeme v souboru Windsor.boo facility pro integraci ActiveRecordu do IoC kontejneru:

import System.Reflection

// podpora ActiveRecord v kontejneru
facility ActiveRecordFacility:
  configuration:
    @isWeb = true, isDebug = false
    assemblies = [ Assembly.Load("MyApplication.Entities") ]
    config(keyvalues, item: add):
      show_sql = true
      command_timeout = 500
      dialect = 'NHibernate.Dialect.MsSql2005Dialect'
      connection.provider = 'NHibernate.Connection.DriverConnectionProvider'
      connection.driver_class = 'NHibernate.Driver.SqlClientDriver'
      connection.connection_string_name = 'CONN_DEMO'

Teď ještě potřebujeme ve web.configu přidat ConnectionString pod klíčem CONN_DEMO a volitelně přidat životnost session na jeden request. To uděláte tak, že do sekce system.web\httpModules přidáte následující modul:

<add
  name="PerRequestSessionScope"
  type="Castle.ActiveRecord.Framework.SessionScopeWebModule, Castle.ActiveRecord"/>

To má za výhodu to, že všechny vaše dotazy za jeden request se chovají jako jedna transakce. Takže integrace frameworku, je hotová. Teď ještě přidáme automatickou registraci samotných repositoří.

for repository in AllTypesBased of IRepository("MyApplication.Entities"):
  component repository.Name.ToLower(), repository.GetInterfaces()[0], repository:
    lifestyle PerWebRequest

Tímto se zaregistrují všechny repository, které dědí z ActiveRecordRepositoryBase a jsou pak ve vašich komponentách dostupné jako IRepository<User> (příklad pro entitu User). Pokud budete mít v repository více metod, je dobré vytvořit pro ně kontrakt, který obsahuje kontrakt generické repository. Například takto:

public interface IUsersRepository : IRepository<User> {
  User FindUserByEmail(string email);
}

Registrace takovýchto komponent už je o něco složitější, ale dá se to také zvládnout. Nejprve si zavedeme konvenci, že repository, které takovéto kontrakty implementují, se budou jmenovat až na I na začátku stejně. :) Takže pro kontrakt IUsersRepository budeme mít konkrétní implementaci UsersRepository atd. Pro jejich registraci využijeme metodu System.Type.FindInterfaces, která bere jako první argument delegát na vyhledávací funkci a druhý argument jsou hledací kritéria. Takže si vytvoříme funkci, která splňuje kontrakt delegáta, a následně ji hned využijeme pro registraci:

def contains_name(t as Type, o as Object):
  return t.Name.Contains(o.ToString())

for repository in AllTypesBased of IRepository("MyApplication.Entities"):
  repository_interface = repository.FindInterfaces(contains_name, repository.Name)
  component repository.Name.ToLower(), repository_interface, repository:
    lifestyle PerWebRequest

Cool ne? Pár řádků konfigurace a nejspíš máme vystaráno, tedy když dodržíme stanovenou konvenci, tak už se o registraci komponent datové vrstvy nemusíme starat. Stejně tak už máme zaregistrované model bindery a controllery. Trošku nám to konfigurace povyrostla, ale díky možnostem, které nám přináší Binsor, je to jen zlomek toho, co by bylo potřeba napsat v XML.

Projektík se nám pomalu rozrůstá, sice stále nic nedělá, ale je to dobrý základ pro pořádný web. Pokusím se ho hodit někam do SVN, aby bylo lehčí se v projektu orientovat a byly vidět jednotlivé změny.

Téměř nový rarouš.weblog

10.42 - 23. června 2008 | Moje práce

Poslední týden jsem trávil úpravami tohoto blogu. Měl jsem už nějaký nástřel grafiky už několik dní před tím, dokonce rozběhaný na úvodní straně blogu. :) Jenže abych to mohl posunout dál i do článků, musel jsem šáhnout do šablon blogu. Jenže prezentační vrstva, byla napsaná tak nepoužitelně a každá změna vyžadovala dost práce.

Měl jsem před sebou dvě možnosti. Nechat to být a počkat až napíšu nový systém, nebo upravit stávající. Jenže čekat sám na sebe, až něco napíšu, to bych se taky nemusel dočkat… ;) Nakonec jsem napsal zbrusu novou prezentační vrstvu nad starým systémem.

Rozhodl jsem se pokračovat v započatém stylu, který jsem zvolil pro svoji homepage, tedy návrhový vzor MVP pro prezentační část a Repository pro datovou. Využil a trochu rozšířil některé entity modelu o další vlastnosti a přidal některé nové. Pro výpis článků a komentářů na stránce jsem napsal serverové prvky založené na Generickém repeateru. Pak jsem se dostal k formuláři pro přidávání komentářů. Aktuální WebForms implementace se mi moc nelíbila a tak jsem se rozhodl oprášit NForms a povolat je do služby.

Musel jsem odladit několik bugů, protože tohle bylo poprvé, co jsem je použil. :) Ale nakonec se z toho vyklubal celkem použitelný a elegantní kus softwaru. Proč elegantní? Líbí se mi definování validačních pravidel (sice zatím pouze serverových), znovupoužitelnost kódu apod.

NForms v akci

Když jsem vymýšlel, jak vlastně budu nově komentáře zpracovávat, rozhodl jsem se pro handler, který bude obsluhovat požadavky na akce a zavolá správnou třídu, která má požadavek zpracovat – v tomto případě ukládač komentářů. (Tento model jsem zvolil protože, každá třída by měla dělat pouze jednu věc, proto komentáře nezpracovává ta samá, co prezentuje články.) Jenže teď jsem stál před problémem, že budu muset definovat ten samý formulář na dvou místech. Naštěstí u NForms to není třeba.

Podědil jsem novou třídu CommentForm z formuláře NForms. V ní jsem nadefinoval, jaká políčka a s jakými pravidly se mají vytvořit. Kód vypadá nějak tak:

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using Rarous.NForms;
using Rarous.NForms.Validation;

namespace Rarous.Net.Components {
  public class CommentsForm : Form {
    public const string ArticleUrl = "comment_article_url";
    public const string ArticleId = "comment_article_id";
    public const string Validity = "comment_validity";
    public const string Text = "comment_text";
    public const string UserName = "comment_user_name";
    public const string UserEmail = "comment_user_email";
    public const string UserWeb = "comment_user_web";
    public const string UserAnswer = "comment_user_answer";
    public const string Submit = "comment_submit";

    public CommentsForm()
      :this(null) {
    }

    public CommentsForm(NameValueCollection data)
      : this(data, null) {
    }

    public CommentsForm(NameValueCollection data, List<string> messages)
      : base(data, messages) {
      CreateForm();
      BindData();
    }

    private void CreateForm() {
      AddHidden(ArticleUrl);
      AddHidden(ArticleId);
      AddHidden(Validity);

      AddTextArea(Text, "Text komentáře:", 10, 55).
        Required("Text příspěvku je povinný.");
      AddText(UserName, "Jméno:").
        Required("Jméno je povinné.");
      AddText(UserEmail, "E-mail:").
        SetEmptyValue("@");
      AddText(UserWeb, "Web:").
        SetEmptyValue("http://");

      AddText(UserAnswer, "Odpověď je pivo:").
        AddRule("Zadejte pivo.", s => String.Compare("pivo", s, true) == 0);

      AddSubmit(Submit, "Přidat komentář");
    }
  }
}

Tento formulář pak použivám ve View i handleru. V handleru se vytváří s daty poslanými přes POST, která se pak validují a dále zpracovávají. Líbí se mi, že data se předávají jako NameValueCollection, díky tomu se dá bez problému načítat z Request.Forms, Request.QueryString, nebo z kolekce Cookies.Values. Těmi můžu data předvyplňovat, nebo je nechat validovat a tak. V budoucnu by mohlo přibýt i inteligentní vytváření objektů…

Další featury

  1. U komentářů jsem oprášil Gravatary, které jsem tu už kdysi míval. Použil jsem implementaci z dotnetKicks, kde se používá lokální cachování.
  2. Bezpečnostní otázka se vyplňuje JavaScriptem, takže už vás nebude otravovat.
  3. Po letech jsem přidal stránkování v článcích. Jak na hlavní stránce blogu, tak v rubrikách.
  4. Zvýrazňovač syntaxe teď funguje jen napůl, protože už přestal stačit mým potřebám. Čekám na uvolnění zvýrazňavoče z Codeplexu, který by měl být o dost lepší.

Jsou to takové drobnosti, ale mám z nich radost, protože jsem je dlouho dobu odbýval.

Závěrem

Ještě mi zbývá pár věcí dopsat, aktualizovat odkazy v článcích na nové URL, ale už jsou to spíš drobnosti (doufám). Ještě bych chtěl závěrem poděkovat Davidovi a Honzovi, protože u nich jsem se dost inspiroval. Taky děkuju Exíkovi za hodnotné připomínky, Jerrymu za hosting a naší zahradě za příjemné pracovní prostředí atd.

Active Record vs. Repository pattern

12.38 - 21. května 2008 | ASP.NET 2.0

Dnes se podíváme na dva návrhové vzory z oblasti přístupu k datům, které používají zcela odlišnou filosofii, ale nakonec si ukážeme, že jejich kombinací, můžeme získat celkem zajímavé výsledky.

Active Record

Návrhový vzor Active Record staví na předpokladu, že základem aplikace je databázový model a od něj se vše odvíjí. Každý řádek tabulky je reprezentován konkrétní instancí objektu. Většinou je takový objekt odvozen od bázové třídy, která implementuje potřebné rozhranní pro persistenci objektu – typicky CRUD metody.

Diagram vzoru ActiveRecord

Tento vzor se hojně využívá v různých ORM frameworcích. Je součástí dnes velmi módních frameworků Ruby on Rails, django nebo Castle, které na něm stavějí svůj datový model a díky velice snadnému mapování dokážete napsat (vygenerovat) kostru databázové aplikace během několika málo minut.

Pro velice jednoduché aplikace může být tento vzor, díky své jednoduchosti, vhodný. Jenže porušuje několik pravidel dobrého objektového návrhu (persistence ignorance, single responsibility, pevně daná bázová třída, atd.).

Repository

Návrhový vzor Repository je základním kamenem doménou řízeného návrhu. Model aplikace nemá ponětí o tom, jakým způsobem je persistován. O to se stará právě Repository. Navíc díky tomu, že se o persistenci stará cizí objekt, stačí nám znát pouze jeho rozhranní a v případě potřeby ho snadno nahradit jiným.

Diagram vzoru Repository

Kombinace

Teď zabrousím do konkrétnějších implementací a to do ActiveRecord z projektu Castle. Tako konkrétní implementace je postavená na běhovém prostření dotnet a jako svůj základ používá ORM nástroj NHibernate.

Nejpřímější cesta jak s ním pracovat, je podědit třídu z bázové třídy ActiveRecordBase a označit ji a její vlastnosti patřičnými atributy:

[ActiveRecord]
public class User : ActiveRecordBase<User> {
  [PrimaryKey] public int Id { get; set; }
  [Property] public string Name { get; set; }
  [Property] public string Email { get; set; }
  [Property] public string Login { get; set; }
  [Property] public string Password { get; set; }
}

A můžeme pracovat. Autoři si však byli vědomi toho, že vzor Active Record nemusí vyhovovat všem a přidali statickou třídu ActiveRecordMediator, který dokáže posloužit jako základ pro Repository. Třída pak nemusí dědit z ActiveRecordBase:

[ActiveRecord]
public class User {
  [PrimaryKey] public int Id { get; set; }
  [Property] public string Name { get; set; }
  [Property] public string Email { get; set; }
  [Property] public string Login { get; set; }
  [Property] public string Password { get; set; }
}

public class UsersRepository : IRepository<User> {
  public User Load(int id) {
    return ActiveRecordMediator<User>.FindByPrimaryKey(id);
  }
  public void Save(User obj) {
    ActiveRecordMediator<User>.Save(obj);
  }
  public void Delete(User  obj) {
    ActiveRecordMediator<User>.Delete(obj);
  }
}

To už je o něco lepší, ale stále máme v aplikačním modelu silnou vazbu na datový model. Můžeme se posunout o level dál a zkombinovat oba vzory tak, abychom z nich vytěžili co nejvíce. Základní myšlenkou je to, že v aplikaci budeme mít jak datový model, tak doménový. Datový model se bude pomocí Active Record starat o persistenci a Repository poslouží k transformaci doménového modelu na ten datový.

Něco podobného můžete najít v seriálu, který píše Rob Conery, kde používá zjednodušenou Repository, IQueryable jako filtry a LINQ2SQL jako datový model. Velice zajímavé, doporučuji ke shlédnutí.

Tagy: , ,