Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DDD class design dilemma with Value Objects with DB id and Entities

This is a long question so i am gonna go straight to the point. This is pseudo code for better illustration of the problem

DB Structure

User (UserID, Name, LastName)

Address(AddressID, UserID, Street, City, State, ZipCode) =>Many to One User relationship

Phone (PhoneID, UserID, Number, IsPrimary) =>Many to One User relationship

Domain Classes

class User:IEntity
{
public string Name {get;set;}
public string LastName {get;set;}
public ContactInfo{get;set;}
}

class Phone: IValueObject or IEntity? will see later.
{
public int id; // persistence ID, not domain ID
public string Number {get;set;}
}

class Address: IValueObject or IEntity? will see later.
{
public string Line1 {get;set;}
public string City {get;set;}
public string State {get;set;}
public string ZipCode {get;set;}
}

class ContactInfo: IValueObject or IEntity? will see later.
{
List<Address> Addresses {get;set;}
List<Phone> PhoneNumbers {get;set;}
}

So, so far we have a very basic representation of this domain and its models.

My question is the following. Let's say that i want to Update one of the addreses or fix the area code for one of the numbers because of misspelling wnen it was initially typed in.

If i follow Evan's bible about DDD, Value Objects should be immutable. Meaning, no changes to its properties or fields after it was created. If that's the case, then i guess, none of my classes are a ValueObject, since i can't just recreate the whole ContactInfo class just because one portion of the string in the phone number is wrong. So, i guess that makes all my classes Entities?

Keep in mind that i have a "persistence id" for each of this classes since they are stored in a database.

Let's say that i decide to make Phone a value object, since it's easy to recreate in the constructor

public Phone(string newNumber)

so, it would be something like adding a method to User (agg root) AND contactinfo? (Demeter Law)

like...

User....
public void UpdatePrimaryPhoneNumber(string number)
{
this.ContactInfo.UpdatePrimaryPhoneNumber(number);
}

ContactInfo....
public void UpdatePrimaryPhoneNumber(string number)
{
var oldPhone = Phones.Where(p=>p.IsPrimary).Single();
var newPhone = new Phone(number, oldPhone.persistenceid???-> this is not part of the domain)
oldPhone = newPhone;
}

but i still have to deal with persistence id... grrrrr. what a headache.

Sometimes i feel when i read those blogs that most "ddd experts" that value objects are overused or i would say misused.

What would be the best solution to this scenario? Thank you

like image 739
Pepito Fernandez Avatar asked Dec 09 '12 01:12

Pepito Fernandez


1 Answers

If i follow Evan's bible about DDD, Value Objects should be immutable. Meaning, no changes to its properties or fields after it was created. If that's the case, then i guess, none of my classes are a ValueObject, since i can't just recreate the whole ContactInfo class just because one portion of the string in the phone number is wrong. So, i guess that makes all my classes Entities?

While the VO itself may be immutable, a VO doesn't exist on its own - it is always part of an aggregate. Therefore, a VO can be immutable, but the object which references that VO doesn't have to be. What helped me understand VOs is to compare them to something like a primitive Int32 value. The value of each individual integer is immutable - a 5 is always a 5. But anywhere you have an Int32 you can set another value there.

For you domain, what that means is that you can have an immutable address VO, but a given use entity can reference any instance of an address VO. This is what will allow corrections and any other changes to be made. You don't change the individual fields on the address VO - you replace it with a whole new VO instance.

Next, "Persistence ids" shouldn't be expressed in anywhere in domain code. They exist solely to satisfy the needs of the relational databases and NoSQL databases don't require them at all.

The primary phone scenario should look more like this:

public void UpdatePrimaryPhoneNumber(string number)
{
  var existingPrimaryNumber = this.Phones.FirstOrDefault(x => x.IsPrimary == true);
  if (existingPrimaryNumber != null)
      this.Phones.Remove(existingPrimaryNumber);
  this.Phones.Add(new Phone(phoneNumber: number, isPrimary = true));
}

This method encapsulates the idea of updating an existing primary phone number. The fact that phone number VOs are immutable means that you have to remove an existing value and replace it with a new one. What usually happens on the database end, especially with ORMs like NHibernate, is it will issue a SQL delete and a subsequent insert to effectively replace all phone numbers. This is OK since the ID of the VOs doesn't matter.

like image 83
eulerfx Avatar answered Oct 23 '22 02:10

eulerfx