DDD e Value Objects

Olá, ainda sobre DDD…
Segundo o exemplo abaixo, onde tem-se um objeto “Perfil”, e “Avaliacao” representa um voto de um usuário acerca de um perfil, pode-se entender “Avaliacao” como sendo um VO? E este seria um membro do aggregate “Perfil” ?


public class Perfil extends EntidadeBase implements java.io.Serializable {

	private Categoria categoria;
	private Imagem imagem;
	private String nome;
    private Set<Avaliacao> avaliacoesDoPerfil = new HashSet<Avaliacao>(0);

	public Perfil() {
	}

	@Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "id_perfis", unique = true, nullable = false)
	public Integer obterId() {
		return (Integer) this.id;
	}

	public void setarId(Integer id) {
		this.id = id;
	}

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "id_categoria", nullable = false)
	public Categoria obterCategoria() {
		return this.categoria;
	}

	public void setCategorias(Categoria categoria) {
		this.categoria = categoria;
	}

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "id_imagens", nullable = false)
	public Imagem obterImagem() {
		return this.imagem;
	}
	
	public void setarImagem(Imagem imagem) {
		this.imagem = imagem;
	}

	@Column(name = "nome", length = 150)
	public String obterNome() {
		return this.nome;
	}

	public void setarNome(String nome) {
		this.nome = nome;
	}

	@OneToMany(fetch = FetchType.LAZY, mappedBy = "perfis")
	public Set<Avaliacao> obterAvaliacoesDoPerfil() {
		return this.avaliacoesDoPerfil;
	}

	public void setarAvaliacoesDoPerfil(Set<Avaliacao> avaliacoesDoPerfil) {
		this.avaliacoesDoPerfil = avaliacoesDoPerfil;
	}
    //Operações e detalhes ocultados...

}

public class Avaliacao extends EntidadeBase implements java.io.Serializable {

	private Perfil perfil;
	private Date dataAvaliacao;
	private String host;
	private float nota;

	public Avaliacao() {
	}

	@Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "id_avaliacao", unique = true, nullable = false)
	public Integer obterId() {
		return (Integer) this.id;
	}

	public void setarId(Integer id) {
		this.id = id;
	}

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "id_perfis", nullable = false)
	public Perfil obterPerfilAvaliado() {
		return this.perfil;
	}

	public void setarPerfilAvaliado(Perfil perfil) {
		this.perfil = perfil;
	}

	@Temporal(TemporalType.TIMESTAMP)
	@Column(name = "data_avaliacao", nullable = false, length = 0)
	public Date obterDataAvaliacao() {
		return this.dataAvaliacao;
	}

	public void setarDataAvaliacao(Date dataAvaliacao) {
		this.dataAvaliacao = dataAvaliacao;
	}

	@Column(name = "host", nullable = false, length = 100)
	public String obterHost() {
		return this.host;
	}

	public void setarHost(String host) {
		this.host = host;
	}

	@Column(name = "nota", nullable = false, precision = 12, scale = 0)
	public float obterNota() {
		return this.nota;
	}

	public void setarNota(float nota) {
		this.nota = nota;
	}
    //Operações e detalhes ocultados...
}

Entendo que Avaliacao seria sim um VO, uma vez que um voto não se altera, apenas exclui ou insere…
O que vcs acham?

Obrigado pela atenção!

Olá

O atributo id da classe Avaliação indica que ela não é um VO, uma vez que ela tem identidade. Isso significa que se você gravar duas avaliações com a mesma data, o mesmo host e a mesma nota, elas terão identidades diferentes, pois os valores do campo id para as duas instâncias serão diferentes. Isso que você falou de adicionar e excluir mas não alterar se refere à imutabilidade de um VO, que não é requerida apesar de recomendada. Além do mais, como sua classe pode ser imutável com setters para todos os atributos?

Ainda, isso não é um aggregate porque instâncias da classe agregada - Avaliação - podem ser criadas ou removidas por quaisquer outras classes do sistema que não o root do aggregate.

EDIT - como questionamento final, qual o ganho que você obtém ao fazer com que todas as suas classes de domínio herdem de EntidadeBase?

Olá Tarso, obrigado!

Bem, em relação a EntidadeBase, todas as entidades herdam dela, pois, pretendo manter as operações em comum entre esses objetos nessa classe, e também para facilitar a implementação de um Repository genérico.
Quanto aos VOs, acho que ainda não consegui captar…
Por exemplo, no meu tópico anterior vc citou:

Ainda não entendi como um VO pode ter uma tabela mas sem um identificador…
Você teria um exemplo aí disso?

Desculpe a ignorância, mas estou ainda tentando engatinhar com isso… :roll:

Obrigado pela paciência…

Se houver uma tabela correspondente ao VO, haverá sim um identificador, mas ele não se propaga para seu modelo de domínio.

Vejamos um exemplo: temos as classes Pessoa e UF abaixo.

[code]// Isto é uma entidade. Não há duas pessoas com o mesmo cpf.
class Pessoa {
private String cpf, nome;
private UF estado;

// Duas instâncias com o mesmo cpf retornam o mesmo hash code.
public int hashCode() {
    int result = 17;
    result = 31 * result + cpf.hashCode();

    return result;
}

}

// Isto é um VO. Duas instâncias correspondentes à unidade federal “RN” não são distintas.
class UF {
private String sigla;

// Duas instâncias com a mesma sigla retornam o mesmo hashCode().
public int hashCode() {
    int result = 17;
    result = 31 * result + sigla.hashCode();

    return result;
}

}[/code]
A classe Pessoa é uma entidade. Duas pessoas podem ter o mesmo nome e serem do mesmo estado, mas certamente possuirão cpfs diferentes.

A classe UF é um VO. Duas instâncias que contenham a sigla “RN” são iguais. Aqui você pode ou não tomar a decisão de manter seus estados em uma tabela; caso isso ocorra, simplesmente crie uma tabela cuja chave primária é a sigla do estado e pronto, pois como você falou não é correto criar uma tabela sem chave primária - apesar de ser perfeitamente possível.

@Entity class UF { @Id private String sigla; }
Mas perceba que, em seu modelo de domínio, a classe UF continua sem ter identidade. Ela não deixou de ser um VO só porque sua tabela correspondente possui chave primária. Resumindo: os conceitos de identidade no modelo de domínio e no banco de dados são diferentes.

Abraços

Totalmente esclarecedor meu amigo! Show de bola!

Muito obrigado pela ajuda!

Tome cuidado ao começar a trabalhar com VOs, tenha o conceito deles super bem definidos. Se for ver, 99% do que vc acha na web sobre eles trata de modelos anêmicos, completamente o contrário do que o DDD propõem.

Vlw pela dica Bruno!

Surgiu outra dúvida (por favor, tenham paciência comigo… hehe):

No exemplo do UF, quando eu fosse criar uma Pessoa, eu então teria que ter uma instância de UF.
Caso o UF tivesse esta estrutura:

 @Entity  
 class UF {  
     @Id
     private int id;  
     private String sigla;
     private String nome;
 }

ou esta:

    @Entity  
    class UF {  
        @Id  
        private String sigla;  
    }  

, ao instanciar um novo objeto UF para ser vinculado a Pessoa, setando sua chave (id ou sigla), isso já não bastaria para saber “quem é o Estado”, caracterizando assim um Entity?

vlw!

Não entendi…

Você quer armazenar na classe Pessoa apenas o id do Estado? É isso?

Se não me engano @Entity denota uma entidade, para Value Objects deve ser usado @Embedded/Embeddable.

Não não…
A questão é a seguinte, eu entendi bem nos exemplos mais triviais, como Endereço, que não se trata de uma tabela no banco (Desculpe, ainda não consegui fazer o tal do “Database não existe…”). O problema é com os VOs que são uma tabela e possuem chave…

No exemplo citado, UF, a sigla sendo sua @Id (também ainda me sinto preso ao ORM…), já não caracteriza sua identidade? Pois, não existem dois Estados com a mesma sigla…

outra dúvida é, como obter uma lista de todos os VOs Estado, uma vez que VO não possui repository?

Desculpe se não estou conseguindo ser muito claro…

Obrigado pela ajuda, Tarso!

Entidades e Value Objects são conceitos independentes de Repositórios.

Ser uma entidade significa que você é único, não importa que outros objetos tenham os mesmos nomes, os mesmos valores, mesmas datas, mesmos tudo. Cada um é o seu. Claro que para representar dentro de um computador precisamos de algum tipo de identificador, seja ele uma sequence do banco, seja ele o endereço de memória em que o objeto se encontra.

Value Objects são definidos apenas por seus atributos, se dois VOs tem a mesma descrição, eles são o mesmo VO. Para representar, por exemplo, uma UF na base de dados, a própria sigla dela é a chave identificadora dela, assim como qualquer outro atributo pode ser.

Ter IDs ou não, não o fazem ser Entidades ou VOs. Os IDs só servem para a gente guardar esses objetos em algum lugar e ter como buscá-los depois.

Como o Bruno falou, os IDs só existem por causa do banco de dados, que não podemos evitar. Se não fosse isso você nem precisaria configurar um ID.

Suponha que você tem duas instâncias de Pessoa que têm o mesmo valor para o atributo nome: “Roberto Carlos”. Você pode dizer que essas instâncias representam a mesma pessoa? Claro que não. Pode haver milhares de pessoas com esse mesmo nome por aqui. Nesse caso, o que é que diferencia uma pessoa da outra? A classe deverá ter algum atributo que permita distinguir uma pessoa que possui os mesmos atributos da outra; aqui no Brasil o CPF é adequado para isso.

Agora se você pegar duas instâncias da classe Estado que possuem o valor “SP” para o atributo sigla, você pode dizer que são dois estados diferentes? Não importa qual instância você pegue, ela sempre representará o estado de São Paulo.

Outro exemplo. Você vai pagar uma conta com uma nota de 20 reais. Pra você importa qual é a nota? Uma vez que seja de 20 reais, você pode pagar a conta com qualquer uma, ela pode perfeitamente ser substituída pela outra. Instâncias de VOs são totalmente substituíveis. Voltando ao exemplo dos estados, se uma pessoa nasceu em São Paulo, qualquer instância da classe Estado que possua como valor da sigla “SP” pode ser usada. Entidades não são substituíveis, pois são únicas. Uma pessoa com nome “Roberto Carlos” não pode ser substituída por outra com o mesmo nome, pois elas fatalmente podem representar pessoas diferentes.

Ué, por que não? O repositório nem mesmo precisa estar acoplado ao banco de dados. Dependendo dos requisitos, seu repositório pode retornar uma lista fixa de estados.

class EstadoRepository { public Collection<Estado> getAll() { List<Estado> estados = new ArrayList<Estado>(); estados.add(new Estado("AC")); estados.add(new Estado("AM")); // ... return estados; } }
Claro, isso é muito menos flexível. Mas é só um exemplo que ilustra como um VO pode existir sem ter uma tabela por trás.

Porque Repository so pode ser usado para recuperar agregados, e o root do agregado tem que ser uma entidade. O spinow tem razão.

Não é verdade que IDs são apenas detalhes de banco de dados. Toda entidade possui um ID com significado para o domínio. O fato do ValueObject que não pertence a nenhum agregado existir numa tabela de banco de dados que é um detalhe de infra que não deveria vazar para o domínio. Usar @Entity num value object você estará fazendo um grande desfavor para sua ubiquitous language.

Na verdade se o domínio precisa obter uma lista de Estados que não pertence a nenhum agregado é sinal que vc precisa de um servico de domínio para retornar objetos Estado (com @Embedded) ou de uma entidade que contém essa lista (Pais por exemplo).

Então todo VO está necessariamente acoplado a um aggregate?

É verdade, quis dizer chaves primárias e acabei falando IDs. Perdão.

Então todo VO está necessariamente acoplado a um aggregate?

Acredito que meu ultimo post foi editado depois da sua resposta, o ultimo paragrafo acho que responde a sua pergunta certo?

Recentemente construí um software em que eu não precisava de países, precisava apenas de estados. Eu poderia ter modelado os estados como um enum por exemplo, mas decidi manter uma tabela e ter um ponto de acesso global para acessá-los que simulava uma collection em memória - o repositório no caso. Isso porque havia várias páginas no sistema em que eu listava os estados em um combo box. Haveria uma forma melhor de fazer isso?

Recentemente construí um software em que eu não precisava de países, precisava apenas de estados. Eu poderia ter modelado os estados como um enum por exemplo, mas decidi manter uma tabela e ter um ponto de acesso global para acessá-los que simulava uma collection em memória - o repositório no caso. Isso porque havia várias páginas no sistema em que eu listava os estados em um combo box. Haveria uma forma melhor de fazer isso?[/quote]

Era o que eu suspeitava…

Se estamos falando de um requisito exclusivamente de apresentação (popular comboboxes) o melhor a fazer é KISS o domínio, ou seja, fazer a aplicação acessar a informação diretamente, ignorando o domínio por completo (e portanto repositorios, services, entidades).

Lembre-se que o domínio deve modelar o negócio, e não suas aplicações. Na verdade o domínio não conhece nada sobre eventuais aplicação que irão fazer uso dele. Me parece que, se o Estado (VO) não pertence a nenhum agregado provavelmente é porque o domínio não precisa ser responsavel por uma lista de Estados, neste caso, muito menos de uma entidade artificial como Pais.

Gente, muito obrigado a todos, este tópico está sendo muito construtivo pra mim!
Seguinte, usando o hibernate tools e fazendo engenharia reversa, quando temos alguma tabela com chave composta, automaticamente a ferramenta gera duas classes, uma (ID) que acredito ser um VO. Exemplo:

@Entity
public class ItemDePedido extends EntidadeBase implements java.io.Serializable {

	private Pedido pedido;
	private Item item;
	private int quantidade;
        private ItemDePedidoId id;

	public ItemDePedido() {
	}


	@EmbeddedId
	@AttributeOverrides( {
			@AttributeOverride(name = "idPedidos", column = @Column(name = "id_pedidos", nullable = false)),
			@AttributeOverride(name = "idItensCardapio", column = @Column(name = "id_itens_cardapio", nullable = false)) })
	public ItemDePedidoId obterId() {
		return (ItemDePedidoId) this.id;
	}

	public void setarId(ItemDePedidoId id) {
		this.id = id;
	}
@Embeddable
public class ItemDePedidoId implements java.io.Serializable {

	private int idPedidos;
	private int idItensCardapio;

	public ItemDePedidoId() {
	}

	public ItemDePedidoId(int idPedidos, int idItensCardapio) {
		this.idPedidos = idPedidos;
		this.idItensCardapio = idItensCardapio;
	}

	@Column(name = "id_pedidos", nullable = false)
	public int getIdPedidos() {
		return this.idPedidos;
	}

	public void setIdPedidos(int idPedidos) {
		this.idPedidos = idPedidos;
	}

	@Column(name = "id_itens_cardapio", nullable = false)
	public int getIdItensCardapio() {
		return this.idItensCardapio;
	}

	public void setIdItensCardapio(int idItensCardapio) {
		this.idItensCardapio = idItensCardapio;
	}

	public boolean equals(Object other) {
		if ((this == other))
			return true;
		if ((other == null))
			return false;
		if (!(other instanceof ItemDePedidoId))
			return false;
		ItemDePedidoId castOther = (ItemDePedidoId) other;

		return (this.getIdPedidos() == castOther.getIdPedidos())
				&& (this.getIdItensCardapio() == castOther.getIdItensCardapio());
	}

	public int hashCode() {
		int result = 17;

		result = 37 * result + this.getIdPedidos();
		result = 37 * result + this.getIdItensCardapio();
		return result;
	}
}

Neste exemplo, ItemDePedido representa uma tabela m:n e ItemDePedidoId representa sua chave.
ItemDePedido é um VO (nítidamente), e ItemDePedido, é mesmo uma entidade, mesmo sabendo que seus valores (que no caso é sua chave) nunca se repetirão? E mochuara, então se não é @Embeddable, quer dizer que não é um VO?

Editado: Outra coisa, quando se trata de um VO, então se “olha” para os valores, desprezando sua chave, correto? Acontece que se eu instanciar um novo objeto, com os valores de um objeto que já exista (i.e. UF), e desprezar o valor da chave, este seria um novo objeto, acontecendo diversas replicações toda vez que eu precisar de um objeto desse… Tudo bem, daí vc pode pensar, mas se o Id do UF é a sigla, é só setar a sigla que eu já sei qual o valor do nome do estado, por exemplo. Mas então ainda assim este objeto é uma Entidade, pois a sigla funcionaria como um CPF… ou não? Isso que eu não entendo…

valew!

Ok, segunda parte sobre o que é um value object:

http://c2.com/cgi/wiki?ValueObject

Value objects são objetos usados para representar valores. Eles são tão simples quanto dos tipos de dados simples de uma linguagem, e normalmente tem algumas operações básicas associados a eles, como somas, comparação. Eles são para representar coisas como dinheiro, datas, até mesmo o String pode ser considerado um VO por ser tão básico e abstrair a implementação de array de caracter dentro dele.

Por isso que o conteúdo é mais importante que a referência dos objetos, aliás é a única coisa que sobra pois eles são só conteúdo. Também por este motivo eles são ótimos candidatos para serem imutáveis.

Com isso dito não acho que deva considerar esse item de pedido como um VO, nem entidade, ele não existe sem o pedido.