DatsunBing
DatsunBing

Reputation: 9076

Where to check for mandatory properties in a domain object?

I have a PHP MVC application, and my 'M' has a service layer, mapper layer and domain layer. Where and how should I check to ensure that an object has all it's required properties?

It seems to me that these responsibilities don't belong in the mapper or service layer, so that leaves the domain layer itself. I put a method, checkRequired(), in my base domain class. It checks the object's properties against an array of $_required properties, and throws an error if any are missing.

When retrieving objects from the database, I have been calling checkRequired() as the last command in the object's constructor. If the object is a new entity (i.e. not retrieved from the database), I supply some default values (in the constructor) and then call checkRequired().

While this has been working OK, I now come to put some behavioural methods on my (somewhat anaemic) domain objects, and I run in to trouble. For example, a User can own many Pets, so on my User model I put an addPet() method. I know that I need to pass the Pet object in, since it's best to inject dependencies, and my real method signature is therefore User::addPet(ConcretePet).

But that's the problem! My Pets can't exist without a User (as their Owner), so I can't instantiate ConcretePet! I could make the User optional on the Pet, but this would be a backward step. I could move the contents of checkRequired() somewhere else, but where?

What's the typical way to solve such a common problem?

Upvotes: 0

Views: 175

Answers (3)

Bartłomiej Szypelow
Bartłomiej Szypelow

Reputation: 2121

That checkRequired is not a DDD way. An enntity should be valid all the time. In other words - you should not have a way to put it in an invalid state (like missing properties). How? When you just have public properties that you set, that's anemic. Properties that you persist in DB should be private. The only way to set them is through methods with a business meaning. These methods should check all the invariants (not just required fields - all kind of constraints that would make an entity valid or not) and prevent the update if some of them are not met.

About User->Pet topic: If the Pet can't exist without the User then probably the User is an Aggregate Root that is responsible for protecting invariants related to User and Pets. That means there should be a method addPet... well... maybe something more meaningful? adoptPet and breedPet (they might have slightly different rules and input)? And this adoptPet should ensure the invariant of the pet having a User... By the way - why User and not an Owner?

But Pet also can be an Aggregate Root. That means it's constructor should require a User parameter.

Note that it depends from the use case what is the aggregate root. In some cases Pet is treated as aggregate root but in case of pet adoption it's a part of User aggregate.

Upvotes: 1

eulerfx
eulerfx

Reputation: 37729

When retrieving objects from the database, I have been calling checkRequired() as the last command in the object's constructor.

Off topic, but this can be problematic. Suppose that the set of required attributes changes at some point such that persisted entities are no longer valid. You still want to be able to reconstitute them, so you shouldn't run that check upon reconstitution. Instead, only run validation upon creation or during behaviors.

With regards to the addPet method, instead of passing an instance of a concrete pet class, pass the data required to create an instance of a pet as method arguments or as an instance of a PetInfo class. This would allow the User class to create a fully valid instance of Pet.

Upvotes: 0

Yugang Zhou
Yugang Zhou

Reputation: 7283

Make a undirectional relationship as possible as you can. If a pet could be tracked alone (I mean without a user), consider it as an aggregate root.

Place a User attribute in Pet but no pets attribute in User. Therefore you don't need to have a addPet() method in User.

If you want to find all pets belonging to a user, use a query instead:

public class PetRepository {
    public List<Pet> findByOwner(String uid) {
        //omitted codes
    }
}

Hope this helps.

Upvotes: 0

Related Questions