Zona .Net

Refatorando idéias, produzindo conhecimento.

Posts Tagged ‘.NET

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

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

Streaming com WCF

leave a comment »

Em alguns serviços existe a necessidade de tráfego de grande volume de dados onde é inviável esperar o recebimento total da mensagem para então ser processada. Por isso no WCF temos a possibilidade de trafegar dados em forma de stream, ou seja, sem a necessidade de aguardar o carregamento completo do objeto na memória para então enviá-lo. Dessa forma o receptor já começa a processar a mensagem antes do termino do envio, aumentando assim a escalabilidade.

Vale ressaltar que essa forma de transferência não esta disponível para todos os bindings, apenas para os seguintes: BasicHttpBinding, NetTcpBinding e NetNamedPipeBinding.

Para implementar tal funcionalidade, lançamos mão da propriedade TransferMode que recebe uma das opções do enumerador de mesmo nome. São elas:

Buffered: A mensagem é carregada totalmente na memória antes de ser enviada ao destinatário. É a configuração padrão para todos os bindings.

Streamed: A mensagem e enviada como um stream, exceto o header que é carregado completamente, não excedendo o MaxBufferSize.

StreamedRequest: Apenas a requisição será enviada como stream, sendo  a resposta coloca em buffer antes do envio.

StreamedResponse: É o inverso do anterior, apenas a resposta será enviada em forma de stream.

Existem algumas regras para uso de stream com WCF, uma delas é que a operações/métodos que compõe o contrato recebam ou retornem uma instancia da classe stream ou uma implementação da interface IXmlSerializable.

Um detalhe a ser observado é que o Header da mensagem será sempre posto em buffer.

Tenha em mente também que ao utilizar stream para ganhar performance você abre mão de alguns recursos como transações, segurança no nível de mensagem, etc.

Veja o contrato de serviço abaixo:

[ServiceContract]
public interface IArquivos
{
   [OperationContract]
   Stream Download(string nomeArquivo);

   [OperationContract]
   bool Upload(Stream arquivo);
}

As operações Upload e Download recebem e devolvem streams respectivamente. Veremos agora a implementação desse contrato:

public class FileService :   IArquivos
{
   public Stream Download(string   nomeArquivo)
   {
      return new FileStream(@"..\Files\"   + nomeArquivo, FileMode.Open);
   }

   public bool Upload(Stream arquivo)
   {
      string nomeArquivo =  "BinFile.bin";
      using (FileStream fs = new   FileStream(@"..\Files\" + nomeArquivo, FileMode.Create))
      {
         byte[] buffer = new   byte[8192];
         int bytesRead = arquivo.Read(buffer,   0, 8192);

         while (bytesRead > 0)
         {
            fs.Write(buffer, 0,   bytesRead);
            bytesRead =   imagem.Read(buffer, 0, 8192);
         }
      }
      return true;
   }
}

Outro detalhe importante é em relação ao controle de fechamento do stream, assumimos que sempre quem recebe deve fechá-lo, já que não sabemos quando o receptor realmente recebeu e processou toda a mensagem. No exemplo acima estamos utilizando o bloco using, que invocará o método dispose do objeto FileStream ao sair do escopo.

Vejamos agora o Host desse serviço:

Em uma aplicação console application, adicione a referencia do projeto do serviço e colocamos o seguinte código no método Main:

using (ServiceHost host =   new ServiceHost(typeof(FileService), new Uri[] { new   Uri("http://localhost:7744") }))
{
   BasicHttpBinding objHttpBinding =   new BasicHttpBinding();
   objHttpBinding.TransferMode =   TransferMode.Streamed;
   objHttpBinding.MaxBufferSize =   3065536;
   objHttpBinding.MaxReceivedMessageSize   = 3065536;

   host.Description.Behaviors.Add(new   ServiceMetadataBehavior() { HttpGetEnabled = true });
   host.AddServiceEndpoint(typeof(IArquivos), objHttpBinding, "arquivos");
   host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(),   "mex");

   host.Open();
   
   Console.ReadLine();
}

No host do serviço eu alterei as propriedades MaxBufferSize e MaxReceivedMessageSize, onde a primeira afeta apenas o que é trafegado no header, que é a única parte da mensagem que utiliza buffer.

Vimos que a implementação de mensagens via stream é bem simples e se usada com cautela pode aumentar a escalabiliade de um serviço WCF, também vimos como o WCF é flexível e consegue um baixo acoplamento entre a implementação do serviço e da sua execução por parte do host.


MVP Logo Leonardo Bruno Lima
Microsoft MVP | MCT | MCTS | MCPD
Attps Informática [http://www.attps.com.br]
+55 (85) 99449511
GMail: lblima.net@gmail.com
Live Mail: lblima_net@hotmail.com
   
Blogs: http://lblima.blogspot.com | https://zonadotnet.wordpress.com

“I do not agree with what you have to say, but I’ll defend to the death your
right to say it.” Voltaire.

Written by Leonardo Bruno Lima

4 de agosto de 2009 at 3:23 am

Publicado em .NET, Streaming, WCF

Tagged with , ,