Zona .Net

Refatorando idéias, produzindo conhecimento.

Posts Tagged ‘Repository

DDD: Implementando Repository com Specifications

with one comment

Já tinha lido alguns posts e artigos sobre Domain-Driven Design, mas ainda não tinha dado atenção suficiente ao assunto. Recentemente comecei a ler o livro Domain-Driven Design: Tackling Complexity int ther Heart of Software e, depois de alguns capítulos, percebi que um dos conceitos mais importantes que se pode extrair da leitura é como se comunicar com os elementos do domínio de forma clara, declarativa e flexível. A solução apresentada é o pattern Repository, que delega ao cliente a responsabilidade de construir queries e submetê-las ao repositório para satisfação, além de prover separação de dependência de única direção entre o domínio e as camadas de acesso a dados.

No post passado, falei um pouco sobre como algumas implementações de Repository se assemelham a implementações de DAO, por expor um método para cada critério de busca, e como isso diverge do que me parece ser o objetivo do pattern. Então, ao invés disso, a implementação a seguir utiliza o pattern Spefication, para criação de uma linguagem declarativa de queries.

Durante a leitura do livro, tenho buscado implementar alguns conceitos em uma aplicação simples e pretendo compartilhar essa experiência. Então, sempre que achar um tema interessante, vou trazer uma implementação.

O domínio da a aplicação é um player de música, capaz de criar listas de reprodução dinâmicas a partir de uma especificação.

A figura abaixo traz os elementos do domínio mapeados, até agora, para essa aplicação:

Diagrama

Figura 1: Domínio

A interface primária do repositório tem  um método Match, que recebe uma especificação como parâmetro de entrada e retorna uma coleção de objetos do domínio que satisfazem à especificacão.

public interface IRepository<T>
{
    IEnumerable<T> Match(Specification<T> specification);
}

public interface ISongRepository : IRepository<Song>
{
    IEnumerable<Song> getSongsFromAlbum(string album);
    IEnumerable<Song> getSongsFromGenre(string genre);
    ...
}

public class SongRepository : ISongRepository
{
    internal List<Song> songs;

    public SongRepository()
    {
        songs = createRepositoryData();
    }

    private List<Song> createRepositoryData()
    {
        ...
    }

    #region IRepository<Song> Members

    public IEnumerable<Song> getSongsFromAlbum(string album)
    {
        Specification<Song> fromAlbumSpec = new SongFromAlbumSpecification(album);

        return this.Match(fromAlbumSpec);
    }

    public IEnumerable<Song> getSongsFromGenre(string genre)
    {
        Specification<Song> fromGenreSpec = new SongFromGenreSpecification(genre);

        return this.Match(fromGenreSpec);
    }

    public IEnumerable<Song> Match(Specification<Song> specification)
    {
        List<Song> resultSet = new List<Song>();

        foreach (Song song in songs)
        {
            if (specification.IsSatisfiedBy(song))
                resultSet.Add(song);
        }

        return resultSet;
    }

    #endregion
}

Uma das grandes vantagens de se usar Specification é a possiblidade de compor critérios de busca sem a necessidade de alteração da interface do repositório.  Essa composição se dá pela combinação de especificacões através de operações lógicas.

public interface Specification<T>
{
    bool IsSatisfiedBy(T candidate);

    Specification<T> And(Specification<T> other);
    Specification<T> Or(Specification<T> other);
    Specification<T> Not();
}

public abstract class CompositeSpecification<T> : Specification<T>
{
    #region ISpecification<T> Members

    public abstract bool IsSatisfiedBy(T candidate);

    public Specification<T> And(Specification<T> other)
    {
        return new AndSpecification<T>(this, other);
    }

    public Specification<T> Or(Specification<T> other)
    {
        return new OrSpecification<T>(this, other);
    }

    public Specification<T> Not()
    {
        return new NotSpecification<T>(this);
    }

    #endregion
}

A partir dessas interfaces, devemos buscar identificar quais os principais critérios de busca utilizados pela aplicação e implementá-los como pequenas regras, para possibilitar a criação de novas regras via composição. Para ilustrar essa idéia, criei as especificações SongFromAlbumSpecification, SongFromArtistSpecification e SongTopRatedSpecification.

public class SongFromArtistSpecification : CompositeSpecification<Song>
{
    private string artist;

    public SongFromArtistSpecification(string artist)
    {
        this.artist = artist;
    }

    public override bool IsSatisfiedBy(Song candidate)
    {
        return candidate.Artist == artist;
    }
}

public class SongFromAlbumSpecification : CompositeSpecification<Song>
{
    private string album;

    public SongFromAlbumSpecification(string album)
    {
        this.album = album;
    }

    public override bool IsSatisfiedBy(Song candidate)
    {
        return candidate.Album == album;
    }
}

public class SongTopRatedSpecification : CompositeSpecification<Song>
{
    public SongTopRatedSpecification()
    {}

    public override bool IsSatisfiedBy(Song candidate)
    {
        return (candidate.Rate == 5 );
    }
}

Podemos combinar essas especificações de várias maneiras diferentes de forma a construir conceitos mais complexos.  Digamos que queiramos construir uma playlist que contenha todas as músicas de um determinado artista, e que sejam as que possuem melhor classificação.

ISongRepository repository = new SongRepository();

Specification<Song> topRated = new SongTopRatedSpecification();
Specification<Song> fromMaiden = new SongFromArtistSpecification("Iron Maiden");

Specification<Song> topRatedFromMaiden = topRated.And(fromMaiden);

IEnumerable<Song> selectedSongs = repository.Match(topRatedFromMaiden));

Ou uma playlist contendo músicas com melhor classificação de mais de um artista.

Specification<Song> fromCake = new SongFromArtistSpecification("Cake");
Specification<Song> topRatedFromCake = topRated.And(fromCake);

Specification<Song> topRatedFromCakeOrMaiden = topRatedFromCake.Or(topRatedFromMaiden);

IEnumerable<Song> selectedSongs = repository.Match(topRatedFromCakeOrMaiden));

Identificar os principais critérios de busca utilizados pela aplicação e mapeá-los de maneira extensível são as principais tarefas na construção de uma boa linguagem de comunicação com o domínio, pois, a partir desses critérios, vários outros conceitos podem e devem emergir.

O principal intuito aqui é apresentar implementações dos conceitos expostos no livro, portanto, sugestões e  críticas são bem vindas.

Brevemente disponibilizarei o fonte da aplicação.

Anúncios

Written by Hamon Vitorino

2 de setembro de 2009 at 10:37 pm

Repository Pattern

leave a comment »

O modelo de desenvolvimento DDD (Domain Driven Design) é hoje um dos mais prestigiados pela comunidade de desenvolvimento de software, sendo considerado, inclusive, uma maneira “mais OO” de se criar software, adicionando comportamento a objetos de domínio, provendo melhor testabilidade, manutenibilidade, etc. Além disso, um dos princípios do DDD é prover interfaces mais expressivas, com operações bem descritas pelos métodos que a executam.

No livro Domain Driven Design(a bíblia para muitos arquitetos e desenvolvedores), do Eric Evans, o pattern Repository integra a camada de domínio e é usado como uma ferramenta para selecionar objetos de domínio de acordo com um determinado critério.

Pela definição do Fowler, no livro PoEAA(Patterns of Enterprise Application Architecture), o Repository tem o seguinte papel:

Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.

Traduzindo, o Repository tem o papel de mediador entre o domínio e as camadas de mapeamento de dados para permitir o acesso a objetos de domínio.

Ele complementa dizendo:

A system with a complex domain model often benefits from a layer, such as the one provided by Data Mapper, that isolates domain objects from details of the database access code. In such systems it can be worthwhile to build another layer of abstraction over the mapping layer where query construction code is concentrated. This becomes more important when there are a large number of domain classes or heavy querying. In these cases particularly, adding this layer helps minimize duplicate query logic.

Ou seja, a idéia é, no domínio,  abstrair o Data Mapper – responsável pela persistência dos dados, através do Repository, provendo uma linguagem declarativa para construção de queries.

Na prática, os frameworks ORM como NHibernate, Linq To Sql e Entity Framework, fazem o papel do Data Mapper e são eles que devem/podem ser abstraídos.

Na minha opinião, a grande sacada do Repository é fornecer uma linguagem declarativa para construção de queries, pois é ela quem dá ao domínio a liberdade de fazer a consulta que necessitar, sem alterar uma única linha de código do seu repositório. E é isso o que a maioria tem “esquecido” de incluir nas suas implementações.

Já vi implementações que, sob minha ótica, não tiram proveito do pattern, fazendo dele apenas um proxy para o seu respectivo DAO(ou outro pattern para acesso a dados), sob o argumento de que o Repository é uma forma mais expressiva por ter métodos do tipo repository.listarClientesVIP() encapsulando uma chamada a um método do DAO, como dao.consultar(StatusCliente.VIP).  Esse tipo de expressividade até um DAO pode ter e não acho que essa seja a idéia.

Se você observou bem, viu que deixei em aberto a possibilidade de abstração do framework ORM. Isso por que, como em todas as escolhas que fazemos, a escolha de um pattern tem seus tradeoffs. E, apesar de trazer muitos ganhos na abstração da infraestrutura de persistência de dados, a implementação da linguagem declarativa de query é muito custosa, pois requer a implementação de várias operações para composição do critério. Particularmente, faria essa implementação se percebesse a possibilidade de, no meio do projeto, precisar trocar o framework de persistência usado na aplicação. Caso contrário, usaria os mecanismos de construção de queries fornecidos pelo ORM, para assim tirar o máximo de proveito do framework.

Written by Hamon Vitorino

11 de agosto de 2009 at 4:52 am

Publicado em .NET, Design Pattern

Tagged with , ,