Zona .Net

Refatorando idéias, produzindo conhecimento.

Archive for the ‘Design Pattern’ Category

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.

Written by Hamon Vitorino

2 de setembro de 2009 at 10:37 pm

Qualidade de Código – Parte I

leave a comment »

Quem me conhece sabe que costumo escrever muito, especialmente quando o assunto é amplo e controverso. O convite para o blog surgiu no momento em que me aprofundo nesse intricado tema e pretendo, portanto, resumir nesta série de poucos posts, as experiências que tenho vivenciado nos últimos tempos.

Construir software de qualidade ainda é um desafio para as organizações. Seja pela premência do tempo, pela falta de maturidade ou mesmo pela simples falta de informação, muitas sequer inserem no seu processo de desenvolvimento, disciplinas que envolvam a aferição, testes e revisão do código fonte produzido.

Há um mito no estudo da qualidade que diz:

  Criar programas é uma arte que não pode seguir regras, normas ou padrões.

Removendo os absurdos, quantos de nós já não vivenciaram situações próximas a estas, especialmente às 23:00h do dia anterior ao prazo de entrega daquele projeto inadiável?

Não é meu objetivo aqui debater o extenso tema da qualidade, mas situá-la no âmbito das falhas inerentes ao elemento humano envolvido na elaboração de programas fontes[i] e nas alternativas automatizadas de aferição e testes recorrentes, com vistas a maximizar a produção de aplicações mais estáveis.

Quando se fala em aferir o código produzido, temos essencialmente três vertentes:

  1. Análise de métricas de código fonte (Code Metrics);
  2. Análise de conformidade a padrões de codificação (Static Rule Analysis);
  3. Análise manual do código fonte com base em listas de verificação (Checklists).

Destes métodos o meu preferido é de longe a análise de métricas. Digo isto porque as métricas de código fonte são baseadas em modelos matemáticos consagrados, em grande parte fruto de inferências da inspeção manual de código, amplamente pacificadas em ferramentas de mercado, algumas delas com décadas de existência.

Estes números são capazes de fornecer indícios importantes sobre a qualidade relativa do código fonte e, quando aplicadas durante o ciclo de desenvolvimento de um projeto, são capazes de revelar distorções antes vistas apenas nas etapas de testes integrados. 

Para exemplificar, vamos responder as seguintes perguntas:

  1. Ao analisar o código em uma classe o que lhe faria dizer se este ou aquele método é mais complexo?
  2. Qual critério você utilizaria para dizer que este ou aquele método está mais sujeito a falhar?
  3. Qual dos métodos é mais difícil de manter?

À minha mente vem imediatamente uma resposta para as três perguntas: “Ora, o método que possui mais linhas de código é o código mais complexo, mais propenso a falhas e mais difícil de manter.”

É nesta linha que nascem as métricas de código fonte. No exemplo acima a métrica é denominada SLOC (Source Lines Of Code).

Eu diria que na mesma proporção de complexidade dos sistemas atuais nasceram métricas e modelos matemáticos para atestar a exequibilidade, estabilidade, manutenibilidade e performance do código fonte gerado. Não vamos (e nem conseguiríamos) esgotar aqui todos estes elementos. Prefiro apresentar algumas métricas elementares, práticas e de grande valia.

Complexidade Máxima (CC): Originalmente denominada Cyclomatic Complexity, mede o nível de complexidade de um método/função. Elaborada por Thomas J. McCabe em 1976[ii] e aperfeiçoada por Steve McConnell da Microsoft em seu livro Code Complete de 1993.

A métrica CC mede o nível de complexidade através da contagem dos caminhos de execução distintos de um trecho de código. Cada método/função possui originalmente um CC igual a 1. Este valor é incrementado a cada instrução IF, ELSE, FOR, FOREACH, WHILE, TRY, CATCH encontrada. Estudos demonstram[iii] que trechos de código com CC superior a 25 possuem alto risco de conterem defeitos (>30%). Com CC superior a 60 a probabilidade cresce para 85% e a partir de 74 pontos, o risco vira fato.

Profundidade Máxima (NBD): Número máximo de blocos de código aninhados (Nested Block Depth) em um método ou função. Esta é outro forte indicativo da complexidade de um trecho de código. Estruturas de código IF, ELSE, FOR, FOREACH, WHILE aninhadas em excesso, além de tornarem a execução mais complexa, dificultam a compreensão e a legibilidade. Métodos/funções com NBD acima de 6 denotam necessidade de refatoramento.

Quantidade de Métodos Por Classe (MPC): Oferece um forte indicativo do grau de coesão de uma classe/library. Classes com muitos métodos apresentam uma forte probabilidade de acumularem mais responsabilidades que o necessário, violando o princípio da responsabilidade única (Single Responsiblity Principle).

Média de Instruções por Método (ASM): É uma derivação da métrica SLOC e mede a quantidade de instruções contidas nos métodos das classes, oferecendo claramente uma dimensão do seu tamanho.

Nível de Documentação do Código (PDOC): Mede a proporção de linhas que contém documentação (XML-DOC) em relação ao total de linhas de código num método, classe ou projeto. A proporção ideal situa-se entre 12% e 20%.

Nível de Comentários do Código (PCOM): Mede a proporção de comentários in-line em relação ao total de linhas de código num método, classe ou projeto. Idealmente, uma medida de 20% denota que o código está bem comentado. Códigos com proporção inferior a 5% e superior a 40% estão, respectivamente, pouco e muito comentados.

No próximo post, apresentarei ferramentas para coleta destas métricas e também exemplos de análise de código fonte. Até breve!


[i] Wohlin, C., Shull, F., Aurum, A., Ciolkowski, M., Petersson, M. (2002) “Software inspection benchmarking-a qualitative and quantitative comparative opportunity”. In: Software Metrics, 2002. Proceedings. Eighth IEEE Symposium, p. 118-127

[ii] McCabe (December 1976). “A Complexity Measure”. IEEE Transactions on Software Engineering: p. 308-320. http://classes.cecs.ucf.edu/eel6883/berrios/notes/Paper%204%20(Complexity%20Measure).pdf. 

[iii] Rich Sharpe. “McCabe Cyclomatic Complexity: the proof in the pudding”. Enerjy. http://www.enerjy.com/blog/?p=198.

Written by Gustavo Hurtado

18 de agosto de 2009 at 2:00 am

Publicado em .NET, Design Pattern

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 , ,