Moq + AutoMapper e um problema de designer

284 views
Skip to first unread message

Riderman Sousa

unread,
Jun 1, 2013, 11:25:12 PM6/1/13
to dotnetar...@googlegroups.com

Em minha app, todas as classes de domínio seguem a padronização:

  1. Todas implementam a interface IEntity
  2. As propriedades Id são protected*
  3. As propriedades do tipo IList também são protected inicializadas no construtor

Abaixo segue um exemplo clássico de uma entidade de domínio:

public class CheckListItemTemplate : IEntity
{
    public virtual int Id { get; protected set; }
    public virtual string Text { get; set; }
    public virtual CheckListItemTemplate Parent { get; set; }
    public virtual IList<CheckListItemTemplate> Itens { get; protected set; }

    public CheckListItemTemplate()
    {
        Itens = new List<CheckListItemTemplate>();
    }

    public void AddItem(CheckListItemTemplate item)
    {
        item.Parent = this;
        Itens.Add(item);
    }
}

*Isto porque o Id é gerado pelo banco e para não correr o risco de algum desenvolvedor tentar setar esta propriedade definimos os Ids como protected.

Projeto de testes

Temos um repositório fake genérico utilizado nos testes:

public class Repository<T> : IRepository<T>
    where T : class, IEntity
{
    private readonly IDictionary<int, T> _context = new Dictionary<int, T>();

    public void Delete(T obj)
    {
        _context.Remove(obj.Id);
    }

    public void Store(T obj)
    {
        if (obj.Id > 0)
            _context[obj.Id] = obj;
        else
        {
            var generateId = _context.Values.Any() ? _context.Values.Max(p => p.Id) + 1 : 1;
            var stub = Mock.Get<T>(obj);
            stub.Setup(s => s.Id).Returns(generateId);
            _context.Add(generateId, stub.Object);
        }
    }

    // .. 
}

Como podem observar no método Store*, todos os objetos de testes (do tipo IEntity) devem ser um Mock**. Isto porque no projeto real, quando salvamos um objeto o NHibernate se encarrega de atualizar a propriedade Id. Já no projeto de testes temos de fazer isto manualmente, porém não temos como setar a propriedade Id com um novo valor, então a solução encontrada foi mocar o objeto inteiro para que o Get da propriedade Id correspondesse ao novo Id. Exatamente o que faz esta linha stub.Setup(s => s.Id).Returns(generateId).

*Por convenção, objetos com Id <= 0 são novos e com Id > 0 são objetos já existentes no banco de dados.
**Como mock utilizo o Moq.

Id como protected

O maior problema ocorre por causa da propriedade Id e o fato dela estar como protected Quando falamos a nível de designer é uma ótima abordagem porém isto traz enormes transtornos quando vamos testar nossa aplicação.

Por exemplo, em um teste que estou escrevendo preciso que meu repositório Fake tenha alguns dados já populados.

Code

Acompanhe comigo. Tenho as seguintes classes (+ a classe CheckListItemTemplate mostrada acima.)

public class Passo : IEntity
{
    public int Id { get; protected set; }
    public virtual IList<CheckListItemTemplate> CheckListItens { get; protected set; }
}

public class Processo : IEntity
{
    public virtual int Id { get; protected set; }
    public virtual Passo Passo { get; set; }
    public virtual IList<CheckListItem> CheckListItens { get; protected set; }
}

Após salvar o processo, o primeiro Passo (ordenado pelo campo Ordem seguindo do campo CreateAt) é associado ao processo:

model.Passo = PassoRepositorio.All().OrderBy(p => p.Ordem).ThenBy(p => p.CreateAt).First();
model.CheckListItens.Clear();
Parallel.ForEach(Mapper.Map<IList<CheckListItem>>(model.Passo.CheckListItens), (it) => model.AddCheckListItem(it));

Este código é executando sempre que salvamos um novo Processo.
Para qualquer teste que crie um novo Processo, este código será executado!

Teste

Se temos de criar algum teste que crie um novo processo, nosso primeiro objetivo é popular o repositório com alguns dados fictícios*, especificamente Passos e CheckListItemTemplates para que o código acima não falhe**.

*Para popular objetos com dados fictícios utilizo o AutoFixture.
**Irá falhar caso nenhum Passo seja encontrado no repositório .First() ou este passo não tenha nenhum checklist Mapper.Map<IList<CheckListItem>>(model.Passo.CheckListItens).


Então precisaremos de um repositório de passos e cada passo com uma lista de CheckListItens.
Lembre-se de que todo objeto IEntity deve ser um Mock<> para que possamos mocar a propriedade Id

Primeira tentativa

Primeiro configuro meu TestInitialize para popular meu repositório com alguns dados fictícios:

var fix = new Fixture();
var listPassos = fix.Build<Mock<Passo>>()
                            .Do((passo) => {
                                passo.SetupProperty(x => x.Nome, fix.Create<string>());
                                passo.SetupGet(x => x.CheckListItens).Returns(
                                    fix.Build<CheckListItemTemplate>() // Precisa ser necessariamente Mock<>, porém como fazer isto?
                                        .With(p => p.Texto)
                                        .OmitAutoProperties()
                                        .CreateMany(5).ToList()
                                    );
                            })
                            .OmitAutoProperties()
                            .CreateMany(10);

foreach (var item in listPassos)
    passoRepository.Store(item.Object);    

Em seguida posso executar os testes:

[TestMethod]
public void Salvar_novo_processo_modificar_data_atendimento_passo_atual()
{
    // Arrange
    var fix = new Fixture();
    var vm = fix.Create<ProcessoViewModel>();

    //Act
    Controller.salvar(vm); // Problema
    var processo = Repository.Get(p => p.DataEntrada == vm.DataEntrada && p.ProximoAtendimento == vm.ProximoAtendimento);

    //Asserts
    processo.Should().NotBeNull();
    processo.Passo.Should().NotBeNull();
}

Problemas

Conseguimos criar uma lista de 10 passos onde cada passo é na verdade um Mock<Passo>, ótimo! Mas:

  1. Para cada passo existe uma lista de Mock<CheckListItens> com 5 itens onde o Id de cada deve ser 1, 2, 3, 4 e 5 (nesta ordem). Como conseguir isto?
    Como obter esta lista de IList<Mock<>> dentro de um Mock<> com o Id já preenchido?
    Ou seja, a configuração de passo.SetupGet(x => x.CheckListItens).Returns( ???

  2. O responsável por criar objetos no meu controller, basicamente utiliza o AutoMapper para converter meu objeto ViewModel para um objeto Model que possa ser persistido em meu repositório:
    model = Mapper.Map<TModel>(vm);
    O problema é que meu repositório Fake não consegue salvar um objeto IEntity, apenas um Mock<IEntity>. Como configurar o AutoMapper para sempre retornar um Mock<>?



Atenciosamente,

Riderman de Sousa Barbosa

Web Developer | MCPD Certify

Skype.: 4042-6002 | Cel.: (31) 8681-1986

bindsolution.com

Microsoft Parner Network

Juan Lopes

unread,
Jun 1, 2013, 11:40:26 PM6/1/13
to dotnetar...@googlegroups.com
Para mim, o problema de design é ter uma classe de domínio que só pode
estar em um estado válido usando reflection. Digo, você está tendo
problemas para testar somente porque confia no seu ORM para setar o id
para você. E você só consegue fazer isso usando uma mocking library.

Mas como isso não responde a sua pergunta especificamente (e eu nem
sei como respondê-la), vou atentar aqui pra outra coisa.

> Parallel.ForEach(Mapper.Map<IList<CheckListItem>>(model.Passo.CheckListItens), (it) => model.AddCheckListItem(it));

Você tem algum motivo especifico para adicionar elementos numa coleção
usando Parallel.ForEach? Digo, a menos que você esteja usando uma
coleção concorrente, isso tem serias chances de dar problema. E não me
parece trazer qualquer vantagem no fim das contas.

--
https://github.com/juanplopes

Riderman Sousa

unread,
Jun 2, 2013, 8:45:51 AM6/2/13
to dotnetar...@googlegroups.com
Então Juan, para setar o Id o ORM faz este trabalho para mim. Preciso confiar nele, afinal já está devidamente testado.
A property `Id` como protected é uma estratégia de designer, assim nenhum dev correra o risco de tentar manipular o Id uma vez que a responsabilidade de gerar é do banco de dados e setar o é do ORM!

Em todo caso, acredito que seja uma boa estratégia e um bom designer. 
1. Nenhum dev manipula o Id
2. Gerar `Id` ( bem como os campos `CriadoAs`, `AtualizadoAs` ) com responsabilidade unicamente do banco de dados
3. Setar minha classe de domínio (`Id`, `CriadoAs` e `AtualizadoAs`) com responsabilidade unicamente do ORM. Não preciso criar testes para garantir que funcione, confio que já esteja devidamente testado!

O problema é que no projeto de testes, esta responsabilidades se tornam minhas, não tenho nenhum ORM/Base de dados para realizar estas tarefas.

Caso deixe estas responsabilidades a cargo da minha camada de domínio além de gerar um re-trabalho e ter de lidar com outros problemas (como o Id que é int sequencial), terei de garantir que tudo funcione criando testes para estes campos. Coisa que já está devidamente testado no ORM/Base de dados.

De uma forma ou de outra eu saiu de um problema e entro em outro.
Como você gerencia isto? Como é gerado/setado o Id nas classes de domínio da sua aplicação?
Se for um `max(id) + 1` como garante que outra pessoa não está tentando inserir o mesmo id?

Sobre Parallels comcordo!

Riderman Sousa

unread,
Jun 2, 2013, 8:49:46 AM6/2/13
to dotnetar...@googlegroups.com
Putz.. depois de lê q a gente vê.  Corrigindo:    *concordo

Riderman Sousa

unread,
Jun 2, 2013, 12:48:34 PM6/2/13
to dotnetar...@googlegroups.com

Estou quase chegando a conclusão de que não use private/protected setters, simplesmente porque isto irá dificultar os testes.

Na minha opinião, faz todo sentido as propriedades Id, CriadoAs e AtualizadoAs serem protected set porque são readonly.
Se deixo esta responsabilidade para a camada de domínio atualizar estas propriedades, como vou saber quando atualizar a propriedade AtualizadoAs por exemplo? Ela só deve ser preenchida no update!

Em todos os casos fico sem saída:

  1. Responsabilidade da camada de serviço? deve ser public setter.
  2. Responsabilidade do repositório? deve ser public setter.
  3. Responsabilidade do ORM? pode ser protected, porém tenho de lidar com isso no meu projeto de testes.
  4. Responsabilidade do Banco de dados? pode ser protected, porém tenho de lidar com isso no meu projeto de testes.

Se for public setter, isto é uma quebra de designer e todos terão acesso ao setter destas propriedades podendo causar erros no futuro.
Se for protected setter, tenho de lidar com isto no repositório de testes.

Se alguém tiver alguma ideia, acha que o designer está errado ou que estas propriedades não precisam ser protected setter estou aberto a opiniões.


Riderman de Sousa Barbosa

Web Developer | MCPD Certify

Skype.: 4042-6002 | Cel.: (31) 8681-1986

bindsolution.com

Microsoft Parner Network



2013/6/2 Riderman Sousa <rider...@bindsolution.com>
--
Você recebeu esta mensagem porque faz parte do grupo .Net Architects hospedado no Google Groups.
Para postar envie uma mensagem para dotnetar...@googlegroups.com
Para sair do grupo envie uma mensagem para dotnetarchitec...@googlegroups.com
Para mais opções visite o grupo em http://groups.google.com/group/dotnetarchitects?hl=pt-br
---
Você está recebendo esta mensagem porque se inscreveu no grupo ".Net Architects" dos Grupos do Google.
Para cancelar a inscrição neste grupo e parar de receber seus e-mails, envie um e-mail para dotnetarchitec...@googlegroups.com.
Para obter mais opções, acesse https://groups.google.com/groups/opt_out.
 
 

Juan Lopes

unread,
Jun 2, 2013, 12:50:53 PM6/2/13
to dotnetar...@googlegroups.com
Eu discordo disso, Riderman. Realmente o setter tem que ser protected.
Mas se o id é uma propriedade essencial para a existência da entidade,
deveria ser pelo menos injetada no construtor.

Confiar que ela vai ser populada pelo seu ORM é acoplar sua lógica de
domínio a uma tecnologia.

2013/6/2 Riderman Sousa <rider...@bindsolution.com>:
--
https://github.com/juanplopes

Riderman Sousa

unread,
Jun 2, 2013, 3:05:50 PM6/2/13
to dotnetar...@googlegroups.com
Mas Juan, como você lida com isto?

O que você está dizendo é que não posso ter meu Id gerado pelo banco pois isto seria acoplar minha lógica de domínio a uma tecnologia.
Então tenho de gerar o Id em minha camada de domínio?

Deixar a classe de domínio popular o `Id` eu não vejo como, mesmo porque a ação de salvar/atualizar parte do repositório.

Se eu colocar no construtor como você está sugerindo, quando crio um novo objeto, que Id devo setar? É um novo objeto, não posso pegar o `max(id) + 1` e associar ao objeto que nem foi salvo ainda.

Quer dizer, se a propriedade está como private/protected setter das duas uma:
1. A responsabilidade é da própria classe de setar este dado.
2. A responsabilidade é externa e provavelmente um proxy/reflection será usado para setar esta propriedade.


Em todo caso, quando você fala acoplamento, indica que um depende do outro e não é este o caso.
Na camada de domínio não existe nenhuma dependência entre a mesma e o ORM, NHibernate não é nem mesmo referenciado. Simplesmente ela não gerencia o Id. 
Aqui é mais uma questão de requisito (não sei a palavra ao certo) onde para funcionar a responsabilidade de gerenciar o Id é sua.





2013/6/2 Juan Lopes <m...@juanlopes.net>

Fabrício Cabral

unread,
Jun 2, 2013, 4:04:17 PM6/2/13
to .Net Architects
Riderman,

deixa eu dar uns pitacos aqui, quem sabe não ajuda em alguma coisa? :)

Com relação ao Id, você poderia fazer como disse o Juan, colocá-lo no construtor para
ser injetado por ele. E quem retornaria este Id? Pode ser o repositório. Mas lembre-se
que a interface do repositório faz parte do domínio, enquanto a sua implementação faz
parte da infraestrutura. Como suas classes irão depender das interfaces, você fica livre
para injetar as dependências e assim não ficar preso a uma implementação.

Quanto ao "max(id) + 1", normalmente - alguém me corrija se eu estiver errado - este
Id é implementado como uma sequence (sequência) dentro do banco de dados e assim
que alguém "consulta" o valor deste sequence, o mesmo é incrementado, assim diversos
acessos correspondem a Ids diferentes.

No caso do método setter do Id, ele se faz necessário pelo ORM, pois o mesmo modifica
esta propriedade via reflection.


Enfim, espero ter podido ajudado em algo...

[]'s 


2013/6/2 Riderman Sousa <rider...@bindsolution.com>



--
--fx

Juan Lopes

unread,
Jun 2, 2013, 5:09:03 PM6/2/13
to dotnetar...@googlegroups.com
Primary keys monótonas (especialmente sequenciais) são ruins por
diversos motivos:

- Elas dificultam a escalabilidade, por introduzirem um único ponto
capaz de definir a identidade das entidades. Gerar ids monótonos em
sistemas distribuídos é um problema considerado difícil.

- Constantemente elas tornam sua aplicação dependente do banco em que
rodam, pois cada banco tem uma forma diferente de gerar esses ids.

- Tornam os testes mais difíceis, pois a lógica de geração dos ids
está no banco, não na sua aplicação.

- Elas não diminuem a segurança de uma aplicação em si, mas tornam
mais fácil explorar outras falhas de segurança (por ser fácil
adivinhar ids).

Minha sugestão, *que ainda não responde a sua pergunta*, é usar um
GUID como chave das suas entidades.


2013/6/2 Fabrício Cabral <fabri...@gmail.com>:

Fabrício Cabral

unread,
Jun 2, 2013, 5:22:32 PM6/2/13
to .Net Architects
Olá Juan,

ótimos argumentos! No entanto, só por curiosidade, gostaria de saber
aqui da lista quem utiliza o GUID como chave das entidades, pois
acredito que a grande maioria pega do BD mesmo.

[]'s


2013/6/2 Juan Lopes <m...@juanlopes.net>



--
--fx

daniel carli

unread,
Jun 2, 2013, 6:04:13 PM6/2/13
to dotnetar...@googlegroups.com
nos projetos que participo costumo utilizar GUID, e mano, num vai por essa da maioria usa entao vou usar
Daniel Carli

Mário Meyrelles

unread,
Jun 2, 2013, 8:09:38 PM6/2/13
to dotnetar...@googlegroups.com
Embora os argumentos do Juan sejam  válidos, nem sempre faz sentido usar o Guid como chave primária.

Nem sempre seu sistema será distribuído. Caso seja, compensa sim usar um Guid. Caso seu sistema tenha chance de usar replicação, também, use o guid. 

Usar o Guid como chave primária, falando em bancos relacionais, diminui a performance dos selects e prejudicam a qualidade e o tamanho dos índices. O otimizador de query teria trabalho adicional para saber onde estão as informações na tabela. Claro que dá para contornar esse problema de diversas formas, mas em geral, o acesso a dados tende a ficar comprometido quando comparado a soluções padrão como ints/bigints. 



2013/6/2 daniel carli <dansa...@gmail.com>

Riderman Sousa

unread,
Jun 2, 2013, 10:57:10 PM6/2/13
to dotnetar...@googlegroups.com

Mário, concordo com você! Este foi um dois motivos que adotamos Ids como int.
Em todo caso, modificar o tipo da PK para Guid irá gerar um trabalho enorme de desenvolvimento.

Juan, vamos supor que eu modifique minha app para utilizar Guid como PK. Isto resolveria o problema do Id, mas como ficaria a propriedade UpdateAt AtualizadoAs?
Ela também é uma propriedade protected setter e deve ser atualizada assim que um registro é atualizado.

Concorda comigo que resolvemos um problema mas a essência continua. Simplesmente mudamos o foco?
Hoje, nas apps que você desenvolve (ou nas empresas que você presta consultoria) como isto é gerenciado? De quem é a responsabilidade de atualizar esta propriedade?

Cara, isto fritou tanto minha mente hoje que cheguei a escrever um post em meu blog: propriedades-private-protected-setter-e-seus-problemas)

Em fim, Id como Guid irá resolver parte do problema.












2013/6/2 Mário Meyrelles <mariome...@gmail.com>

Marcelo Oliveira

unread,
Jun 2, 2013, 11:30:18 PM6/2/13
to dotnetar...@googlegroups.com
Riderman, to tentando entender teu problema e fiquei com algumas duvidas:

Tu não tá mocando o automapper? 
Me corrija se eu estiver errado, mas no teste em questão tu faz o setup no repositório fake, exercita o controller e faz a assertion sobre um objeto retornado pelo repositório fake?
Qual unidade está de fato sendo testada nesse teste?







2013/6/2 Riderman Sousa <rider...@bindsolution.com>

Rafael Ponte

unread,
Jun 3, 2013, 7:36:11 AM6/3/13
to dotnetar...@googlegroups.com
Olá Riderman,

Você vê tanto problema assim ter os métodos setters? Se você precisa definir o valor dessas propriedades em algum ponto do sistema, na minha opinião, faz todo sentido ter os setters ou outro método.




2013/6/3 Marcelo Oliveira <blac...@gmail.com>

Riderman Sousa

unread,
Jun 3, 2013, 7:37:42 AM6/3/13
to dotnetar...@googlegroups.com

Marcelo, não estou mocando o AutoMapper.
Mocar o AutoMapper teria de configurar todos os mapeamentos novamente no meu projeto de testes. Não sei se é isto que está querendo dizer.

Em minha app, tenho alguns Profiles de configuração como ViewModelToDomainProfile : Profile e DomainToViewModelProfile : Profile.
Uma classe static no projeto UI configura o AutoMapper:

    public static void Configure(Func<Type, object> getService)

    {
        Mapper.Initialize(x =>
        {
            x.ConstructServicesUsing(getService);
    x.AddProfile<DomainToViewModelProfile>();

    x.AddProfile<ViewModelToDomainProfile>();
        });

    }

Meus controllers dependem da interface IMappingEngine do AutoMapper, desta forma no meu projeto de testes tudo que tenho de fazer é executar o .Configure(...) para que as configurações (Profiles) sejam aplicadas ao AutoMapper. Ninject se encarrega de todo o resto.

No profile ViewModelToDomainProfile tenho um Map para ProcessoViewModel -> Processo. Este Map (para minha aplicação UI) está correto, porém no projeto de testes teria de retornar um Mock<Processo> para que o repositório Fake não gere uma exception e eu consiga setar o Id.


Sim! Meu controller possui uma dependência ao IRepository<Processo>. Ninject se encarrega de resolver estas dependências.
Repare na linha Controller.salvar(vm); em meu teste, o controller utuliza o AutoMapper para converter ViewModel (objeto vm) para um objeto de domínio, neste caso um Processo. Porém preciso que retorne um Mock<Processo>, é obrigatório para o repositório fake, caso contrário irá gerar uma exception na linha var stub = Mock.Get<T>(obj);


Todo novo processo deve ter o primeiro passo associado e a data de atendimento configurada.




2013/6/3 Marcelo Oliveira <blac...@gmail.com>

Riderman Sousa

unread,
Jun 3, 2013, 7:46:02 AM6/3/13
to dotnetar...@googlegroups.com

Rafael Ponte, ter estas propriedades setters é um erro de designer.

  1. Id é gerenciado pelo banco de dados, não se pode setar o Id na aplicação. Ter esta propriedade como setter dá esta abertura e pode causar erros ou no mínimo confusões no futuro

    .

  2. Na aplicação UI não preciso (nem posso) definir um valor para o Id, porém na aplicação de testes tenho de setar o Id, não tenho nenhum ORM/base dedados para definir este valor.



2013/6/3 Riderman Sousa <rider...@bindsolution.com>

Rafael Ponte

unread,
Jun 3, 2013, 8:20:21 AM6/3/13
to dotnetar...@googlegroups.com
Olá Riderman,

2013/6/3 Riderman Sousa <rider...@bindsolution.com>

Rafael Ponte, ter estas propriedades setters é um erro de designer.

  1. Id é gerenciado pelo banco de dados, não se pode setar o Id na aplicação. Ter esta propriedade como setter dá esta abertura e pode causar erros ou no mínimo confusões no futuro

Ter um setter não vai impedir Id de ser gerado pelo banco de dados, ou vai?

Confusões? Se você trabalha sozinho é bem improvável de isso acontecer; se trabalha com uma equipe, então existem técnicas para compartilhar o conhecimento do sistema e código entre os membros da equipe.
  1. Na aplicação UI não preciso (nem posso) definir um valor para o Id, porém na aplicação de testes tenho de setar o Id, não tenho nenhum ORM/base dedados para definir este valor.

Se não precisa, simplesmente não o defina. O ORM, para gerenciar o Id, usa de reflections. Nos seus testes, quem tem a responsabilidade de gerenciar o Id é você, logo, ou você cria um setter (mais simples) ou você também se utiliza de reflections.
 
Não é só porque você é obrigado a ter métodos setters que você tem que utiliza-los no código de produção.

Riderman Sousa

unread,
Jun 3, 2013, 8:42:35 AM6/3/13
to dotnetar...@googlegroups.com
Olá Rafael, segue as respostas:


2013/6/3 Rafael Ponte <rpo...@gmail.com>

Olá Riderman,

2013/6/3 Riderman Sousa <rider...@bindsolution.com>

Rafael Ponte, ter estas propriedades setters é um erro de designer.

  1. Id é gerenciado pelo banco de dados, não se pode setar o Id na aplicação. Ter esta propriedade como setter dá esta abertura e pode causar erros ou no mínimo confusões no futuro

Ter um setter não vai impedir Id de ser gerado pelo banco de dados, ou vai?

Não vai impedir! Eu não disse que iria :).

Confusões? Se você trabalha sozinho é bem improvável de isso acontecer; se trabalha com uma equipe, então existem técnicas para compartilhar o conhecimento do sistema e código entre os membros da equipe.

Cara, confiar em técnicas para compartilhar o conhecimento do código mais do que na arquitetura na minha opinião é um erro enorme.
Seu código tem de ser expressivo, não se pode esperar que em uma empresa com 20 devs todos tenham a mesma compreensão do código, mesmo com técnicas para difundir o conhecimento, muito menos em uma empresa com 50, 100 devs. Mas podemos confiar na arquitetura do sistema que irá impossibilitar erros como estes.

  1. Na aplicação UI não preciso (nem posso) definir um valor para o Id, porém na aplicação de testes tenho de setar o Id, não tenho nenhum ORM/base dedados para definir este valor.

Se não precisa, simplesmente não o defina. O ORM, para gerenciar o Id, usa de reflections. Nos seus testes, quem tem a responsabilidade de gerenciar o Id é você, logo, ou você cria um setter (mais simples) ou você também se utiliza de reflections.
 
Não é só porque você é obrigado a ter métodos setters que você tem que utiliza-los no código de produção.


Acho que a resposta acima encaixa aqui também. O fato de existir estes setter é um erro de designer afina a propriedade é readonly!
Sobre reflection concordo com você! Embora acredito que exista outras alternativas como utilizar classes de domínios fake mas não estou certo sobre estas abordagens.

Fernando Mondo

unread,
Jun 3, 2013, 9:04:27 AM6/3/13
to dotnetar...@googlegroups.com
Vou dar meus pitacos...

O que realmente você quer testar? É um teste unitário ou de integração?


Pelo visto você quer testar o método Salvar que usar o auto mapper e o repositório, então você precisar mockar as suas dependências e garantir que elas sejam chamadas.

Eu sempre uso os ids como int e protected, sem problema algum, dificilmente você realmente precisa setar um id nos testes, a não ser que não use moq.

Você não precisa de um repositório fake se você usa o moq, afinal você está testando seu controller e não um repositório.

Quanto ao automapper você não precisa configurá-lo no seu teste, o Controller recebe a interface IMappingEngine no contrutor, então vamos mockar:


//arrange
   var vm = new Vm();
   var entity = new Entity();

   var repository = new Mock<IRepository>();

   repository.setup(r=>r.save(entity));

   var mapper = new Mock<IMappingEngine>();

   mocker.Setup(m=>m.Map<Entity>(vm)).return(entity);

   var controller = new HomeController(repository.Object, mapper.Object );

//action
   controller.Salvar(vm);

//assertions

  repository.verifyAll(); //garante que o repository foi chamado passando a entidade de acordo com o setup
  mapper.verifyAll(); //garante que o map de foi chamado passando a vm  e voltando a  entidade de acordo com o setup 
    

Com isso você tem o controller bem testado sem precisar setar o Id.

Agora você poderia per um método que precisa do id como um que pega a entidade então o id aparentemente é preciso, porém com moq o id não será usado:


//arrange
   var vm = new Vm();
   var entity = new Entity();
   int id = 10;

   var repository = new Mock<IRepository>();

   repository.setup(r=>r.GetByID(Id)).return(entity);   //não importa o Id da entidade isso é só um mock não? 

   var mapper = new Mock<IMappingEngine>();

   mocker.Setup(m=>m.Map<Entity>(entity)).return(vm);

   var controller = new HomeController(repository.Object, mapper.Object );

//action
  var result =  controller.Show(id) as ActionResult;

//assertions

   Assertion.AreEqual(result.Model,vm); //garante que o retorno do controller é a vm conforme esperado; 

  repository.verifyAll(); //garante que o repository foi chamado passando o id e retornando  a entidade de acordo com o setup
  mapper.verifyAll(); //garante que o map de foi chamado passando a entidade e voltando a  vm de acordo com o setup 



"Mas as veses eu uso um filtro estático do linq como Where, First e então preciso do Id", neste caso para setar os Ids nos testes use um Método de Extensão com reflection. Como esse método só existe na dll de testes isso garante que nenhum programador desavisado set o id no código a não ser nos testes.

 public static class SetPrivateProperty
    {
        public static TEntity SetValue<TEntity, TProperty>(this TEntity entity, Expression<Func<TEntity, TProperty>> property, TProperty value) where TEntity : Entity
        {
            var expression = (MemberExpression)property.Body;
            var name = expression.Member.Name;
            entity.GetType().GetProperty(name).SetValue(entity, value);
            return entity;
        }
    }


Como usar?

 var entity = new Entity();
 entity.SetValue(e=>e.Id, 10); //setado o valor por reflection.

   var repository = new Mock<IRepository>();

   repository.setup(r=>r.All()).return(new List(){entity}); //uma lista de entidades que na verdade tem uma só mas com o id "setado"

Espero ter ajudado.

Riderman Sousa

unread,
Jun 3, 2013, 10:10:55 AM6/3/13
to dotnetar...@googlegroups.com
Olá Fernando.

  1. Estou testando a entidade processo. O primeiro passo deve ser associado à propriedade Passo da entidade Processo.
  2. É um teste unitário. Nos testes de integração não tenho este problema pois o ORM gerencia isto para mim.

  3. Não tenho como mocar o AutoMapper. No momento ele funciona, converte um objeto ViewModel ProcessoViewModel para um objeto de domínio Processo, o problema é que para o repositório fake preciso de um Mock<Processo>.

  4. Sobre o repositório fake vs Mock isto acredito ser outra discussão. Mas recomendo a leitura deste artigo). Em resumo admito que se estivesse usando um repositório Mock facilitaria este teste, mas foi uma estratégia utilizar fake:

    • Não tenho de mocar o repositório para cada teste.

    • Não tenho de configurar o comportamento do meu repositório para cada teste
      De qualquer forma, foi bom você levantar este ponto, se for o caso vou mocar o repositório. (Não gostaria de fazer isto).

  5. Em minha app, tenho vários profiles para o AutoMapper, todas devem ser configuradas.

    Mocar o AutoMapper (como você faz na linha new Mock<IMappingEngine>(); me traz um Mapper.Engine sem nenhum profile configurado. Quando você faz: mocker.Setup(m=>m.Map<Entity>(vm)).return(entity); eu realmente não entendi.
    Isto irá me retornar uma entidade vazia, toda a configuração que deve ser aplicada para esta conversão não será executada! Quer dizer, você abstraiu algo que não deveria ser abstraído. Se por exemplo nesta conversão tivesse alguma AfterMapAction configurada na UI, nos testes ela simplesmente não seria executada.


O que você acha?

Ajudou sim cara, vlw!





2013/6/3 Fernando Mondo <fernand...@gmail.com>

Riderman Sousa

unread,
Jun 3, 2013, 10:30:09 AM6/3/13
to dotnetar...@googlegroups.com

Fabrício Cabral

unread,
Jun 3, 2013, 10:33:26 AM6/3/13
to .Net Architects
Riderman e Mário,

deixem eu propor uma nova forma de gerar Ids, no mesmo espírito do GUID
e vê se ele se encaixa melhor para este propósito. Pensei em gerar um int
que é a data atual, seguido das horas, minutos, segundos e milésimos de
segundos.

Exemplo:

A data/hora 03/06/2013 às 11:25:03 milésimo 123 ficaria:

20130603112503123

Embora haja a possibilidade de conflito de Ids (dois clientes diferentes gerarem
a mesma chave no mesmo milésimo de instante) acredito que esta possibilidade
seja *bem* pequena.

[]'s

Juan Lopes

unread,
Jun 3, 2013, 10:33:59 AM6/3/13
to dotnetar...@googlegroups.com
2013/6/2 Riderman Sousa <rider...@bindsolution.com>:
> Juan, vamos supor que eu modifique minha app para utilizar Guid como PK.
> Isto resolveria o problema do Id, mas como ficaria a propriedade UpdateAt
> AtualizadoAs?
> Ela também é uma propriedade protected setter e deve ser atualizada assim
> que um registro é atualizado.

Sim, e assim como a PK, a responsabilidade de alterá-las é da sua
aplicação, não do banco de dados. O que resolve o problema é tirar
toda lógica do banco de dados, senão fica difícil de testar.

> Concorda comigo que resolvemos um problema mas a essência continua.
> Simplesmente mudamos o foco?
> Hoje, nas apps que você desenvolve (ou nas empresas que você presta
> consultoria) como isto é gerenciado? De quem é a responsabilidade de
> atualizar esta propriedade?

Eu não presto consultoria. Trabalho numa empresa que desenvolve
produtos. O produto especificamente em que trabalho tem uma porção
*muito* pequena de dados relacionais, menos de 1% dos dados são
relacionais aqui. Mas geralmente faço exatamente o que falei, ids são
guids e datas de inserção/modificação são geradas pela própria
aplicação.

No caso destas datas em específico, permito que elas sejam
modificadas, mas não através de setters, e sim através de métodos que
representam eventos sobre uma entidade. Faço isso principalmente para
ter a opção de injetar a data, pois ter uma dependência para um
"Clock" não ficaria bonito nos objetos do domínio. Então toda entidade
implementa uma interface HasAuditInfo (ou algo que o valha, lembre-se
que eu programo em Java no dia-a-dia) que possui um método que informa
ao repositório que aquela entidade precisa ter uma data injetada
sempre que for inserida ou atualizada.

No C# isso pode ficar ainda mais interessante através da implementação
implícita de interfaces. Você pode colocar todos esses métodos de
injeção (Id, data, etc.) em interfaces que representam a necessidade
da sua entidade e implementá-las implicitamente. Qualquer usuário
daquela classe não vai conseguir usar esses métodos a menos que faça o
cast para a devida interface.

--
https://github.com/juanplopes

Marcelo Oliveira

unread,
Jun 3, 2013, 10:53:06 AM6/3/13
to dotnetar...@googlegroups.com
Acho que a discussão tomou um rumo que não representa o problema de fato... na minha opinião o maior problema é a maneira como os testes estão sendo escritos.

Se puderes criar um gist com esses fontes eu me prontifico a escrever os testes da maneira que considero apropriadas...


Marcelo Oliveira



2013/6/3 Juan Lopes <m...@juanlopes.net>

Riderman Sousa

unread,
Jun 3, 2013, 11:21:21 AM6/3/13
to dotnetar...@googlegroups.com

Juan, entendi como você trabalha. No meu caso não utilizo Guid, e para trabalhar com Int tenho de pegar o max(id) + 1.
O problema é que na camada de domínio não tem acesso ao repositório! O que leva a outro problema, pois só posso setar o Id internamente na classe de domínio.
Se você entende que pelo fato do Id ser gerado pelo banco de dados é ter a lógica da aplicação no banco, então não tenho como usar Id como int. Afinal como o domínio irá gerar o id sem acesso ao repositório?

Não entendi: ... pois ter uma dependência para um "Clock" não ficaria bonito nos objetos do domínio.


O que é um Clock?

Em meu projeto, estas datas fazem parte da interface IAuditable que indica um objeto é aditável. Porém não vejo como setar estas datas sem usar mock/reflection. Ter um método setter ou uma propriedade public setter é quase a mesma coisa, no final são métodos.

Não entendi: ... e implementá-las implicitamente. Qualquer usuário daquela classe não vai conseguir usar esses métodos a menos que faça o cast para a devida interface.


Em uma interface todos os methods são public, não tenho que fazer cast para poder usá-los.





2013/6/3 Juan Lopes <m...@juanlopes.net>

Riderman Sousa

unread,
Jun 3, 2013, 11:32:10 AM6/3/13
to dotnetar...@googlegroups.com
Marcelo, então cara.

Fiz uma postagem em meu blog sobre isso, além de um vídeo mostrando o problema*. Veja se é suficiente. se não for e você quiser, crio um projeto de exemplo ou mesmo um gist! Tudo para achar uma solução. :)

Áudio ta muito ruim, eu sei!



2013/6/3 Marcelo Oliveira <blac...@gmail.com>

Riderman Sousa

unread,
Jun 4, 2013, 12:21:34 PM6/4/13
to dotnetar...@googlegroups.com

Se alguém se interessar, fiz um projeto de testes

.

Acredito ser uma discussão bastante saudável para um grupo de arquitetura.

Lembre-se que o objetivo é utilizar Id como int. A responsabilidade e a forma de gerenciar o id é que está sendo discutida




2013/6/3 Riderman Sousa <rider...@bindsolution.com>

Marcus Alexandre Silva

unread,
Jun 4, 2013, 12:41:54 PM6/4/13
to dotnetar...@googlegroups.com
Batendo o olho (muito rapidamente) no seu projeto me parece que você esta mais preocupado em testar se seu repositório (infraestrutura) esta funcionando do que testar se o negócio esta funcionado. Tanto que todas suas duvidas estão apontadas para o resultado do Repositório

Ja falei por aqui antes e muitos discordam: Infraestrutura se testa com testes integrados (principalmente testes de regressão) e não com Testes Unitários....

Marcus Alexandre Silva

unread,
Jun 4, 2013, 12:46:10 PM6/4/13
to dotnetar...@googlegroups.com
Ainda sobre seu repositório, se ele tem a responsabilidade de setar valores de negócio nas entidades (mesmo que IAuditable tenha cara de infraestrutura me parece ser um requisito do seu negócio) me parece que o domínio esta falho. Você quer usar DDD não?

Riderman Sousa

unread,
Jun 4, 2013, 2:50:12 PM6/4/13
to dotnetar...@googlegroups.com
Marcus, este projeto foi criado apenas para exemplificar o problema de designer que ocorre com PKs do tipo int. Por isso os testes.

Sobre  "Infraestrutura se testa com testes integrados" faz sentido, nunca tinha pensado nisto. Eu não sei bem o que você está definindo como infraestrutura neste projeto, se for a geração do Id, o problema é que dependendo do teste unitário você precisa disto pronto.
Por exemplo: Em casos que preciso de um repositório já populado para criar um teste. Como vou popular meu repositório fake? 
Claro, usando Mock consigo fazer isso, mas isto não resolve o problema! Seria o mesmo que falar que PKs do tipo int não funcionam com repositórios fake.

De qualquer forma, PKs do tipo int só vão funcionar em duas situações: No projeto UI e no projeto de testes de integração onde o NHibernate gerencia isso pra mim.


2013/6/4 Marcus Alexandre Silva <inf.marcu...@gmail.com>

Marcus Alexandre Silva

unread,
Jun 4, 2013, 3:12:08 PM6/4/13
to dotnetar...@googlegroups.com
"Em casos que preciso de um repositório já populado para criar um teste. Como vou popular meu repositório fake? 
Claro, usando Mock consigo fazer isso, mas isto não resolve o problema! Seria o mesmo que falar que PKs do tipo int não funcionam com repositórios fake."

Vamos la: A primeira premissa que você precisa partir é que a implementação do repositório não faz parte do seu domínio  mas a interface dele sim. Se o seu Id é preenchido dentro do Store, tudo bem é uma opção sua e bastante conveniente dependendo do cenário, mas os testes unitários não podem atrapalhar o design, na verdade isto não deveria afetar o design em nada! 
O ponto chave, neste caso é fazer com que o Mock da INTERFACE do repositório devolva seu id preenchido.

Mais ou menos assim:

IEmployees repository = Mock.CreateMock<IEmployees>(); 
repository.Expect(r => r.GetById(1)).Return(new Employees { Id = 1 });

AnyService anyService = new AnyService(repository);
anyService.DoSomething(1); 
Employee result = anyService.GetResult(); 
Assert.AreEqual(result.Id, 1); 
Assert.AssertWasCalled(repository => repository.Store(result));

Ou seja: 'Mocko' o que é repositório, injeto ele em um serviço que realmente precisa de ser testado, e depois testo se os métodos do repositório foram chamados corretamente e os resultados do processamento do serviço.

Quando escrevo testes unitários sempre foco no negócio. Todas as vezes que tentei testar infraestrutura com a ferramenta de teste unitário acabei fazendo coisa desnecessária e que me custou um tempão. 
Não me leve a mau mas acho que é isto que você esta fazendo agora....

Mais uma coisa: Se o seu Id é protected setter é só usar o helper que alguém antes ai para injetar via Reflection... Funciona que é uma beleza....

Abraço,

Marcus Alexandre

Riderman Sousa

unread,
Jun 4, 2013, 4:41:53 PM6/4/13
to dotnetar...@googlegroups.com
Marcus, estou entendendo seu raciocínio.  Segue minhas dúvidas:

1. "implementação do repositório não faz parte do seu domínio  mas a interface dele sim. "
  - Porque a interface do repositório deve fazer parte do domínio? No meu caso a interface faz parte do repositório e a implementação em outra camada geralmente vinculada ao ORM.
   Mas isso é apenas um detalhe e não afeta bastante o designer. Desde que a implementação esteja em uma camada separada claro.

2. "mas os testes unitários não podem atrapalhar o design, na verdade isto não deveria afetar o design em nada!"
  - Mas não atrapalha, na verdade acontece o contrário. O designer está prejudicando os testes quando delegamos a responsabilidade de gerenciar o Id para a camada de testes.

3. "fazer com que o Mock da INTERFACE do repositório devolva seu id preenchido."
  - Atualmente a aplicação trabalha com repositório fake, foi uma decisão baseada neste artigo. Talvez é um ponto a se discutir, mas como disse antes se tiver de mudar meu repositório para trabalhar com Mock<> isto nos diz alguma coisa. Por exemplo que não podemos trabalhar com PKs do tipo int pois não funcionam com repositórios fake.

4. "Se o seu Id é protected setter é só usar o helper que alguém antes ai para injetar via Reflection"
  -  Então cara, é exatamente isto que está sendo feito na aplicação de testes, repare nas linhas 28 a 38.  E funciona! :) Mas não acho que seja uma boa abordagem! Por isso criei o projeto no Git, para trabalhar em um bom designer utilizando PKs como int e repositório fake. Qualquer pull request será bem vindo :).

5. "Não me leve a mau mas acho que é isto que você esta fazendo agora"
  - Como disse anteriormente, faz sentido. O problema é que quando delegamos a responsabilidade para o nosso projeto de testes gerar o Id, adicionamos ao nosso projeto a responsabilidade de testar isso. O que nos leva a criar testes de infra em nosso teste de unidade.

Pelo que pude entender, você está me dizendo que não preciso que a camada de testes gere o Id, todos pode ficar com o valor default.
Se for este o caso:

  1. Em nenhum teste unitário poderei usar qualquer método em meu repositório que dependa do Id Get(int id).. (Lembre-se que o repositório é fake, e não um Mock<>)
  2. Teremos uma dificuldade enorme quando desejamos testar modificações em um objeto. Exemplo, após update no objeto Company adicionar entrada no histórico. A única forma de identificar um novo objeto de um existente é pelo Id
  3. Qualquer teste que envolva relacionamento (onde não temos uma property navigation) ficará extremamente mais complexo. Exemplo: Company tem vários Employees, porém a única property indicando este relacionamento está em Employees.Company. Um requisito é que exista apenas 1 gerente para cada empresa. Como minha camada de serviço irá filtrar?





Rodrigo Silva de Andrade

unread,
Jun 4, 2013, 4:54:42 PM6/4/13
to dotnetar...@googlegroups.com
Desculpe, sei que não é relacionado com sua discussão no grupo, mas vejo que quando você diz "designer" você provavelmente quer dizer "design", ou estou errado?


2013/6/4 Riderman Sousa <rider...@bindsolution.com>

Riderman Sousa

unread,
Jun 4, 2013, 4:57:35 PM6/4/13
to dotnetar...@googlegroups.com
Sim, erro meu.
foi mal.


2013/6/4 Rodrigo Silva de Andrade <dang...@gmail.com>

Capetonis

unread,
Jun 4, 2013, 5:30:34 PM6/4/13
to dotnetar...@googlegroups.com
Concordo.

Além disso, não vejo ganhos em se testar um método Salvar do Repositório, onde tudo o que é feito nele, é feito com mocks e fakes...

Fabrício Cabral

unread,
Jun 4, 2013, 5:40:38 PM6/4/13
to .Net Architects
Olá Riderman e demais colegas,

vamos ver se dessa vez eu ajudo em alguma coisa... :)

1. A ideia da interface do Repositório pertencer a camada de domínio é uma escolha
conceitual. Os repositórios fazem parte do domínio. Já a implementação destes
repositórios fazem parte da camada de infraestrutura. Na prática, mesmo que você
coloque as interfaces dos repositórios na camada de infraestrutura, vai funcionar,
mas você estaria conceitualmente errado;

2. Como o Juan Lopes já afirmou aqui, não coloque o seu BD para ficar gerando os
seus IDs. Utilize o GUID ou faça um gerador de IDs que retorne uma data/hora de trás
para frente, que será um int e ao mesmo tempo, será único também. Exemplo:
a data 04/06/2013 às 18h30 46s 123 milésimos será 201306041830123 que é um int
único;

3. Para setar o ID ao objeto, você pode passar o ID como um argumento do construtor
do objeto, como em Employee(Long id, ...);

4. Neste seu repositório Fake, você está armazenando os objetos em memória (usando
um Dictonary) certo? Então a coisa seria assim:

4.1. Crie o Dictionary associando um Long ao tipo de objeto (que nem é feito no artigo
que você usou como referência);

4.2. Crie um objeto qualquer (você pode ter um construtor do tipo
Employee (String name, ...) e lá dentro setar o ID, utilizando uma data (que será um int)
conforme explicado no item 2;

4.3. Armazene o objeto no repositório fake, isto é mande associar o ID àquele objeto
(aí na classe deve ter um getId() público, certo?);

4.4. Para recuperar o objeto, basta passar o ID, que deverá buscar o objeto associado
àquele ID lá no Dictionary.

5. No caso de um repositório real em BD, você passa o ID, e o ORM deve reconstruir
o objeto, utilizando o construtor apropriado Employee(Long id, ...). Assim dessa forma,
não iria precisar de um setId(). Parece que o NHibernate não consegue chamar um
construtor apropriado (segundo me informaram, o Hibernate não faz isto).

Bem, espero poder ter ajudado em alguma coisa...

[]'s


2013/6/4 Riderman Sousa <rider...@bindsolution.com>



--
--fx

Riderman Sousa

unread,
Jun 4, 2013, 6:14:46 PM6/4/13
to dotnetar...@googlegroups.com

Então Fabrício vamos la:
Sempre ajuda, cara. Qualquer discussão é saudável! :)

Eu gostaria que a responsabilidade de gerenciar estes campos ReadOnly (na aplicação) fossem da base de dados

, porém
está causando muita dor de cabeça então vamos mudar esta abordagem. Segue as respostas:

  1. Concordo plenamente!
  2. Tudo bem, vou mudar a implementação para Guid, desvincular qualquer lógica relacionada ao banco de dados (inclusive objetos IAuditable).
    Toda a responsabilidade de setar estas datas e o Id será da aplicação.
  3. Perfeito. Mas como pretendo usar Guid, posso inicializa-lo no construtor correto?
  4. Ok
  5. Sem problemas, se o Guid é gerado no construtor nem preciso.

Segundo ponto

Usar Guid como PK resolve o problema da PK, mas e para os objetos IAuditable?

Propriedade CreateAt: Só deve ser atualizada no insert posso coloca-la no construtor. Resolvido!
Propriedade UpdateAt: A responsabilidade agora é da aplicação, o repositório deverá atualizar esta propriedade sempre que um Update for realizado. Como a propriedade é protected, novamente um proxy/reflection terá de ser usado?

Como você lida com a propriedade UpdateAt ?



2013/6/4 Fabrício Cabral <fabri...@gmail.com>

Capetonis

unread,
Jun 4, 2013, 6:32:42 PM6/4/13
to dotnetar...@googlegroups.com
Provavelmente se você tem um campo no objeto "UpdateAt", significa que no mínimo você estará alterando o objeto em algum momento. Isso deve ser feito em um método do próprio objeto (algo como obj.Alterar("blablabla", "mimimi", "tititi"); ). Neste método atualiza também a propriedade interna do objeto "UpdateAt".

Fabrício Cabral

unread,
Jun 4, 2013, 6:44:22 PM6/4/13
to .Net Architects
Olá Riderman!

2013/6/4 Riderman Sousa <rider...@bindsolution.com>

Então Fabrício vamos la:
Sempre ajuda, cara. Qualquer discussão é saudável! :)

Eu concordo plenamente! Acho que qualquer discussão, mesmo que seja aspectos
"aparentemente simples", podem trazer à tona alguma coisa nova! :)

Eu gostaria que a responsabilidade de gerenciar estes campos ReadOnly (na aplicação) fossem da base de dados

, porém
está causando muita dor de cabeça então vamos mudar esta abordagem. Segue as respostas:

  1. Concordo plenamente!
Ok!

 
  1. Tudo bem, vou mudar a implementação para Guid, desvincular qualquer lógica relacionada ao banco de dados (inclusive objetos IAuditable).
    Toda a responsabilidade de setar estas datas e o Id será da aplicação.

Não precisa ser Guid se você não quiser. Use uma data mesmo, que lhe fornece um número
inteiro único. Inclusive, acho que esta abordagem é utilizada pelas empresas de telecomunicações
e bancos. Quando você abre um chamado, eles te passam um número de protocolo que,
se você atentar, é um número inteiro que corresponde a uma data de trás pra frente.

  1. Perfeito. Mas como pretendo usar Guid, posso inicializa-lo no construtor correto?
Correto.
 
  1. Ok
  2. Sem problemas, se o Guid é gerado no construtor nem preciso.
Perfeito.


Segundo ponto

Usar Guid como PK resolve o problema da PK, mas e para os objetos IAuditable?

Propriedade CreateAt: Só deve ser atualizada no insert posso coloca-la no construtor. Resolvido!

Uma dúvida: esta propriedade é pra ser atualizada no momento do insert no BD ou
quando o objeto for criado? Veja que são duas coisas distintas. Se for o primeiro
caso, então *acho* que *talvez* você tenha um objeto de infraestrutura aí acoplado
a um objeto de domínio.

Propriedade UpdateAt: A responsabilidade agora é da aplicação, o repositório deverá atualizar esta propriedade sempre que um Update for realizado. Como a propriedade é protected, novamente um proxy/reflection terá de ser usado?

Como você lida com a propriedade UpdateAt ?

Eu não sei se é o seu caso, mas eu faria o seguinte:

1. Ao invés de criar uma interface IAuditable, eu criaria um objeto Audit e faria
uma composição deste objeto com a entidade que é auditável. Este objeto seria
um value object (do DDD);

2. Ao criar uma entidade auditável, o construtor criaria um objeto Audit em que o
CreateAt seria o momento de criação e o UpdateAt seria um momento inválido, para
indicar que ainda não foi feito uma alteração, apenas uma criação. Depois faria
a composição deste objeto Audit com a entidade;

3. Ao fazer o update no objeto, seria criado um novo objeto Audit, com o CreateAt
idêntico ao antigo, mas com um UpdateAt do momento do update. Este objeto
então seria substituido pelo antigo objeto Audit (Audit é um VO, certo? Então a
ideia é que ele seja imutável).

Espero ter ajudado em algo...

[]'s
 
--
--fx

Gerson Dias

unread,
Jun 4, 2013, 7:24:53 PM6/4/13
to dotnetar...@googlegroups.com
Uma duvida... O id sendo setado em minha camada de dominio (seja la por qual tecnica) esta vinculando o meu id ao meu negocio (estamos falando de ddd). Assim, caso o especialista do negocio nao veja sentido em um numero unico que identifica a entidade, esta decisao eh puramente para "agradar" o banco de dados, ou seja, eh uma decisao tecnica tendo em vista a tecnologia que uso para guardar os dados... Entao, pq estaria errado eu deixar isso acontecer automaticamente em meu bd ou no meu prm? Pq eu preciso me preocupar com isso em meu dominio? 

Ps: nao quero entrar na discussao "ah, mas se vc nao tem id vai fazer buscas como?" Estou apenas argumentando sobre o pq nao deixar o bd ou o orm se virar com esta questao tecnica

Fabrício Cabral

unread,
Jun 4, 2013, 8:24:11 PM6/4/13
to .Net Architects
Olá Gerson!


2013/6/4 Gerson Dias <gerson....@gmail.com>

Uma duvida... O id sendo setado em minha camada de dominio (seja la por qual tecnica) esta vinculando o meu id ao meu negocio (estamos falando de ddd). Assim, caso o especialista do negocio nao veja sentido em um numero unico que identifica a entidade, esta decisao eh puramente para "agradar" o banco de dados, ou seja, eh uma decisao tecnica tendo em vista a tecnologia que uso para guardar os dados... Entao, pq estaria errado eu deixar isso acontecer automaticamente em meu bd ou no meu prm? Pq eu preciso me preocupar com isso em meu dominio? 

Quando se cria um objeto, a referência a este objeto criado é o que o identifica de
maneira única no sistema. No entanto, ao longo do ciclo de vida deste objeto, ele
irá ser persistido (salvo em um BD, arquivo ou etc) e esta identificação única será
perdida. Desta forma, é preciso ter um Id para que possa se reconhecer dois objetos
distintos, mesmos que os mesmos tenham alguns atributos (ou todos) semelhantes.

No entanto, até onde eu saiba, no livro do Evans, ele não afirma onde deve ser gerado
este ID. É uma questão em aberto. Poder ser gerado pela camada de domínio ou pela de
infraestrutura. A questão recai então, quais são as implicações técnicas na forma de
se gerar este ID. Se utilizar um BD para isto, irá cair nos problemas que o Juan Lopes
citou, que eu tomo a liberdade de reproduzir (na íntegra) novamente:

<e-mail do Juan>

Primary keys monótonas (especialmente sequenciais) são ruins por
diversos motivos:

- Elas dificultam a escalabilidade, por introduzirem um único ponto
capaz de definir a identidade das entidades. Gerar ids monótonos em
sistemas distribuídos é um problema considerado difícil.

- Constantemente elas tornam sua aplicação dependente do banco em que
rodam, pois cada banco tem uma forma diferente de gerar esses ids.

- Tornam os testes mais difíceis, pois a lógica de geração dos ids
está no banco, não na sua aplicação.

- Elas não diminuem a segurança de uma aplicação em si, mas tornam
mais fácil explorar outras falhas de segurança (por ser fácil
adivinhar ids).

....

</e-mail do Juan>
 

Ps: nao quero entrar na discussao "ah, mas se vc nao tem id vai fazer buscas como?" Estou apenas argumentando sobre o pq nao deixar o bd ou o orm se virar com esta questao tecnica

Não se precisa fazer buscas necessariamente pelo ID. Pode-se usar um
outro atributo. A questão é como manter a unidade de dois objetos diferentes,
mas que possuam alguns atributos iguais. Exemplo: duas pessoas com
o mesmo nome. Como o sistema vai diferenciar um do outro?

[]'s

--
--fx

Gerson Dias

unread,
Jun 4, 2013, 10:22:01 PM6/4/13
to dotnetar...@googlegroups.com
Fabricio,

Entendo o ponto de haver uma maneira de identificar a entidade unicamente, porém, o negócio pode ter a solução, como simplesmente usar o CPF, algo que traria todos os benefícios de não termos chaves primárias monótonas e nem mesmo a discussão sobre onde gerar a chave, já que esta resposta seria bem mais óbvia.

Mas, de qualquer forma, pq não deixar que o NHibernate crie para mim uma chave GUID automaticamente se esta questão não envolver o negócio? Talvez seja uma solução válida, apesar de criar a dependencia para o NHibernate... e, por outro lado, quis levantar a questão de que nem sempre precisamos de uma propriedade chamada ID para identificar unicamente minha entidade, apenas quis abrir as mentes para novas ideias! :)


Fabrício Cabral

unread,
Jun 5, 2013, 12:19:59 AM6/5/13
to .Net Architects
Olá Gerson!

2013/6/4 Gerson Dias <gerson....@gmail.com>
Fabricio,

Entendo o ponto de haver uma maneira de identificar a entidade unicamente, porém, o negócio pode ter a solução, como simplesmente usar o CPF, algo que traria todos os benefícios de não termos chaves primárias monótonas e nem mesmo a discussão sobre onde gerar a chave, já que esta resposta seria bem mais óbvia.

Sinceramente, isto dá pano pra manga pra uma longa - e caloroso - debate. Já
vi muita gente dizendo pra não se utilizar chaves compostas (CPF como PK).
Outras já defendem a sua utilização. De onde tirei isso?


Outros também afirmam que utilizar CPF não é uma boa escolha para um ID
pois duas pessoas - marido e mulher - podem compartilhar o mesmo CPF (não
sei se isso é pratica antiga). *Ainda* não confirmei esta informação, mas ela veio
de uma fonte de confiança:


Enfim, se mesmo assim o domínio já te der um ID, você pode sim usá-lo ou não
como identificador único das suas entidades. É uma questão de escolha.

 
Mas, de qualquer forma, pq não deixar que o NHibernate crie para mim uma chave GUID automaticamente se esta questão não envolver o negócio? Talvez seja uma solução válida, apesar de criar a dependencia para o NHibernate... e, por outro lado, quis levantar a questão de que nem sempre precisamos de uma propriedade chamada ID para identificar unicamente minha entidade, apenas quis abrir as mentes para novas ideias! :)

A questão de uma propriedade ID para identificar as entidades é uma questão
de escolha. Se o domínio já lhe der um ID, ótimo, você pode usá-la (ou não).

No entanto, com relação a geração do GUID pelo NHibernate, imagine que você
vá trocar de ORM, ou que você não vá mais fazer a persistência dos seus dados
em um SGBD. E aí? Quem é que vai gerar as GUIDs para você? :)

[]'s

--
--fx

Fernando Mondo

unread,
Jun 5, 2013, 7:40:23 AM6/5/13
to dotnetar...@googlegroups.com
Referente ao link do meiobit que foi passado. Cpf é unico para cada pessoa física, podendo ser usado, no máximo que poderia acontecer é evitar que alguém tente se passar por outra ( ou uma mulher tente usar o cpf o marido), e isso é bom, não?

Já o email, o erro foi o banco que não definiu como unique no campo email, não? Já que não faz sentido ter duas pessoas com o mesmo email (neste caso) e o sistema poderia dar um alerta evitando que o usuário cadastra-se errado (que foi o que aconteceu).

No final das contas é tão usual utilizar o Id gerado pelo banco que eu não vejo porque fazer diferente. Porém, em um projeto que estou trabalhando, há poucos casos que eu preciso do Id antes do saveChanges (estou usando Entity Framework) então tive que colocar um Guid, que é gerado no construtor da classe. 

Cada caso é um caso...


--

Riderman Sousa

unread,
Jun 5, 2013, 8:05:17 AM6/5/13
to dotnetar...@googlegroups.com
Fabrício, vlw pela resposta cara.

Eu não respondi ainda porque não entendi a parte do `UpdateAt`.  
Tô estudando um pouco sobre alguns aspectos do DDD para não passar vergonha aqui, por exemplo o que é Value Object . :)


2013/6/4 Fabrício Cabral <fabri...@gmail.com>

--

Fabrício Cabral

unread,
Jun 5, 2013, 1:27:38 PM6/5/13
to .Net Architects

Riderman Sousa

unread,
Jun 5, 2013, 2:12:25 PM6/5/13
to dotnetar...@googlegroups.com
O do Eric Evans já estava na minha lista de livros para ler.
Tenho só que terminar uns dois que estou lendo sobre javascript.

vlw


2013/6/5 Fabrício Cabral <fabri...@gmail.com>
Reply all
Reply to author
Forward
0 new messages