DDD e Value Objects

25 respostas
spinow

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!

25 Respostas

tnaires

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?

spinow

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…

tnaires
spinow:
Ainda não entendi como um VO pode ter uma tabela mas sem um identificador...
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.
// 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;
    }
}
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

spinow

Totalmente esclarecedor meu amigo! Show de bola!

Muito obrigado pela ajuda!

B

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.

spinow

Vlw pela dica Bruno!

spinow

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!

tnaires

Não entendi…

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

M

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

spinow

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!

B

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.

tnaires

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.

M

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).

tnaires

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

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

M

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?

tnaires

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?

M

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?

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.

spinow

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!

B

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.

spinow

ok, então esse ItemDePedido faz parte do aggregate onde o root é Pedido, e não é nem VO nem Entity? Eu pensava que em um modelo de domínio só teríamos esses dois tipos de objeto…

spinow

ok, vamos lá… pra ver se eu estou conseguindo entender:

Um objeto Menu, com os atributos id (por causa do BD), nome, descricao, imagem, url.

Tenho duas instâncias, com o mesmo nome, descrição, imagem e url. Digo que as duas instâncias representam o mesmo Menu, pois em meu contexto, não faz sentido dois menus apontando para o mesmo local…

Tenho então, neste caso, uma Entidade, certo? Pois a url garante que o Menu seja único.

Posso ter duas instâncias de um objeto, com TODOS os atributos iguais e essas instâncias estarem representando indivíduos distintos?
SIM = esse objeto é um Entity.
NÃO = trata-se de um VO.

Pode-se perguntar de outra maneira:
Posso ter dois objetos (ou mais) cadastrados no sistema (BD) com todos os atributos iguais?
SIM = Deverá ser um entity.
NÃO = Deverá ser um VO.

Certo ou errado?

B

spinow:
ok, vamos lá… pra ver se eu estou conseguindo entender:

Um objeto Menu, com os atributos id (por causa do BD), nome, descricao, imagem, url.

Tenho duas instâncias, com o mesmo nome, descrição, imagem e url. Digo que as duas instâncias representam o mesmo Menu, pois em meu contexto, não faz sentido dois menus apontando para o mesmo local…

Tenho então, neste caso, uma Entidade, certo? Pois a url garante que o Menu seja único.

Não, o motivo pela qual ele é uma entidade é por que ele tem uma identidade, algo que o identifique e que o faça ser diferente de outros Menus. Por acaso essa url é a identidade dele.

Antes que diga que troquei seis por meia dúzia, digo que o conceito é importante.

Posso ter duas instâncias de um objeto, com TODOS os atributos iguais e essas instâncias estarem representando indivíduos distintos?
SIM = esse objeto é um Entity.
NÃO = trata-se de um VO.

A identidade de um objeto é a mesma do outro? Então eles são a mesma entidade.

O objetos não tem identidade? Então eles não são entidades.


Pode-se perguntar de outra maneira:
Posso ter dois objetos (ou mais) cadastrados no sistema (BD) com todos os atributos iguais?
SIM = Deverá ser um entity.
NÃO = Deverá ser um VO.

Se eles eles tiverem a mesma identidade, não pode.
Se eles não tiverem identidade, não importa qual você vai pegar. Pode, mas para quer ter dois se poder ter um?

M

Numa Entidade o identificador é um dos atributos do objeto e que por si só representa sua singularidade no domínio, ou falando mais fácil, o identifica. Portanto duas instancias com mesmo atributo são considerada iguais. Value Objects também possuem identificador, que é o mesmo que o conjunto de seus atributos.

Portanto a resposta para sua pergunta é não para ambos entidade e value objects.

spinow:

Pode-se perguntar de outra maneira:
Posso ter dois objetos (ou mais) cadastrados no sistema (BD) com todos os atributos iguais?
SIM = Deverá ser um entity.
NÃO = Deverá ser um VO.

Certo ou errado?

Se estamos falando de DDD, então não existe o BD e o domínio É o “sistema”.

Juk

Uma dica importante pra implementar VOs é não se esquecer que os VOs devem possuir atributos (data) e quando aplicável comportamento (behavior). É comum vermos VOs anêmicos contendo apenas data, o que não é correto.

spinow

Ok, então não é o conjunto de TODOS os atributos, mas sim algum atributo em comum que o faz diferente dos outros (Entity)? E isso independe de como eu trate em questão de persistencia? Ou seja, mesmo que eu não anote o atributo url com um @Id, mas crie um campo id para tanto?

Posso definir que, se todos os atributos (juntos) de um objeto, formam sua singularidade, é VO. Se nenhum dos atributos o faz, necessitando então de um campo identificador, ou mesmo se um (ou mais) satisfaz essa singularidade, é Entity?

Criado 18 de julho de 2009
Ultima resposta 28 de jul. de 2009
Respostas 25
Participantes 5