DDD class design dilemma with Value objects with DB identifier and objects

This is a long question, so I will go straight to the point. This is a pseudo code to better illustrate the problem.

DB structure

User (UserID, Name, LastName)

Address (AddressID, UserID, Street, City, State, ZipCode) => Lots to one user relationship

Phone (PhoneID, UserID, Number, IsPrimary) => Multi-to-One 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 view 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 due to the incorrect spelling wnen, on which it was originally entered.

If I follow Evan’s Bible on DDD, Value objects must be immutable. Meaning, no changes to its properties or fields after its creation. If this is the case, then, I think, none of my classes is a ValueObject, since I cannot just recreate the entire ContactInfo class just because one part of the line in the phone number is incorrect. So, I think, what does all my Entities classes do?

Keep in mind that I have a “persistence identifier” for each of these classes, as they are stored in the database.

Let's say that I decided to make Phone an object of value, since it is easily recreated in the constructor

 public Phone(string newNumber) 

so it would be like adding a method to the user (agg root) AND contactinfo? (Demeter Act)

as...

 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 the persistence id ... grrrrr. what a headache.

Sometimes I feel when I read those blogs that most of the "ddd experts" who evaluate objects are abused or, I would say, misused.

What would be the best solution for this scenario? Thanks you

+8
domain-driven-design value-objects
source share
3 answers

The entity has a rather unique and individual life cycle. It makes sense when he stands alone.

The classic Order / OrderItem can help with this. If a OrderItem becomes Entity, it will have its own life cycle. However, this does not make much sense, as it is part of Order . This always seems obvious when you look at an order, but less so when looking at your own classes, because there may be some links between classes. For example, OrderItem represents some Product that we sell. A Product has its own life cycle. We may have an independent list of Product s. The way we model the relationship between OrderItem and Product may be another discussion, but I would denormalize the Product data I require in OrderItem and also keep the original Product.Id .

So, is the Address class an Entity or Value object? This is always interesting, because we have this favorite of answers: it depends.

It will be context sensitive. But ask yourself if you have (or need) an independent Address list, and then you need a link to Address in your User . If so, then this is the essence. If, however, your Address only makes sense when it is part of your User , then this is a Value object.

The fact that a Value object is immutable does not mean that you need to replace more than just a specific Value object. I don't know if I will have a ContactInfo class in your current project, since it only wraps two collections ( Address / PhoneNumber ), but I would save it if there is more (maybe this). So just replace the corresponding PhoneNumber . If you have something like primary / secondary, then it is as simple as:

AR.ReplacePrimaryPhoneNumber(new PhoneNumber('...'))

If this is a list of arbitrary numbers, then Remove / Add is suitable.

Now for saving Id . You do not need it. When you have a primary / secondary scenario, you know what your use case is, and you can perform the appropriate queries in your database (for example, to update the primary PhoneNumber ). If you have an arbitrary list, you can go on to add all the new numbers to your list and remove these numbers from the database that are not in my list ; otherwise, just delete all the numbers and add everything you have. If this seems like a very heavy move: it is. A sourcing event would move a lot of this into memory processing, and this is what I will push for serious progress.

I hope all this makes sense. Distracting from focusing on the data side is very difficult, but necessary. Focus on the domain as if you do not have a database. When you find friction, do your best to not pull the database thinking into your domain, but try to think about how you can keep your domain clean and still use your own database of choice.

+3
source share

If I follow Evan’s Bible on DDD, Value objects must be immutable. Meaning, no changes to its properties or fields after its creation. If this is the case, then, I think, none of my classes is a ValueObject, since I cannot just recreate the entire ContactInfo class simply because one part of the line in the phone number is incorrect. So, I think, what does all my Entities classes do?

While VO itself may be unchanged, VO does not exist on its own - it is always part of the totality. Therefore, a VO can be immutable, but an object that references a VO does not have to be. What helped me understand VO was to compare them with something like a primitive Int32 value. The value of each individual integer is unchanged - the value of 5 is always equal to 5. But anywhere you have Int32, you can set a different value there.

For your domain, this means that you can have a fixed VO address, but this use object can refer to any instance of the VO address. This will allow for amendments and any other changes. You do not change individual fields on the VO address — you replace it with a new instance of VO.

Further, “Conservation Truths” should not be expressed anywhere in the domain code. They exist solely to meet the needs of relational databases, and NoSQL databases do not require them at all.

The main scenario of the phone should look 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 the VO phone number is unchanged means that you need to delete the existing value and replace it with a new one. What usually happens at the end of a database, especially with ORM, such as NHibernate, will remove SQL and then insert it to effectively replace all phone numbers. This is normal since the VO identifier does not matter.

+6
source share

I would create a PhoneNumber class of the class that contains the line number of the current Phone class and uses it as a Value object in your Phone class:

 class Phone implements IEntity { public int id; // persistence ID, not domain ID public PhoneNumber number {get;set;} } class PhoneNumber implements IValueObject { public String number {get;set;}; } 

Later, when your code evolves, you will need (for example) checking the phone number, and you can put it in the PhoneNumber class. Then this class can be reused throughout the application in different places.

An address is, in my opinion, a Value object that you can consider as a whole. Although you can simulate a street, a city, etc., which are usually objects, but this is probably over-modeling. No part of the address can change; the entire object is always replaced when changing after the initial creation.

The user class is in this example with these boundaries: Aggregate root (and, therefore, also Entity). The ContactInfo class is not a ValueObject (not immutable), not an Entity (no real identifier), but an aggregate. It contains several classes that should be considered as a whole.

Additional information at http://martinfowler.com/bliki/DDD_Aggregate.html

Usually, when persistence identifier is present, you should think about Entity. If, however, you want to add the persistence identifier, I would start splitting up, like the Phone and PhoneNumber classes. For example, an address (Entity containing an identifier) ​​and AddressValue containing all other fields (and logical values ​​of addresses).

This should also solve the headache of managing persistence identifiers, as you replace the entire value object, and the persistence identifier remains unchanged in case of updatePrimaryPhoneNumber.

0
source share

All Articles