TL;DR
Value Objects (VO) are a core building block of Domain Driven Design (DDD), but the implementation in Flow is currently pretty broken and makes VOs hard to use properly. This needs to change.
Goal
Value Objects have some core characteristics:
- they have no identity, they are only referenced by their properties
- they are immutable
These characteristics result in some consequences:
- they need a constructor that takes all properties as parameters. There may be factory methods for syntactic sugar, but those are hard to configure properly for automatic instanciation
- they should not contain relations to entities (because entities are mutable). If at all, those relations should be considered transient
- submitting a VO will always result in submitting all properties and hence a complete rebuild of the VO on the receiver side -> no persistence involved
- a VO is never shared among entities, each entity has its own copy of the VO, because VOs don’t have identity. An entity removing a VO will never have any impact on other entities referencing the “same” VO. With VOs stored like entities in their own table, this is hard to achieve, because it depends solely on persistence annotations on the entity side
- optimally, equality of VOs is simply tested by == comparison, but this could be problematic for VOs with injections. A solution would be to have an interface with an equals method.
It should be as easy to create VOs as simple immutable POPOs (Plain Old Php Objects) and have them be usable inside Entities and Forms without special attention.
It should be possible to reuse existing VO implementations (e.g. nicolopignatelli/valueobjects), by at most extending the VO classes and annotating them as @Flow\ValueObject.
##Technical how ##
Currently, the most breaking concept in the implementation is the property values hash, which defines a kind-of identity on the VO and gives them entity semantics. Also, this value hash generation is error prone. This was necessary for persistence because doctrine didn’t support value objects.
This changed with Doctrine ORM 2.5, which added support for embeddables, which are the perfect persistence solution to Value Objects for like 99% of use cases.
Therefore, a first step should be to add Doctrine 2.5 support and make embeddables useable (see https://jira.neos.io/browse/FLOW-260).
The next step would be to improve Flow Value Objects to be optionally embedded (see https://jira.neos.io/browse/FLOW-257).
In a later breaking version, embedded storage could and should even be made the default.
Another big breaking thing is how VOs are treated and referenced internally, especially for uri building and form submission. Currently, they are treated like entities and their value hash identifier is used to reference the VOs.
As soon as those points are solved, it would be possible to completely strip off the value hash generation. This would lead to non-embedded persistence to no longer be supported, but should be favored because all use cases where VOs need to be stored in a seperate table are design smells that they should be entities in the first place.
Benefits
Bringing Flow closer to full DDD support, which is a USP. Makes working with VOs easy and hence improves application design.
Challenges
Some special cases to consider:
-
VOs with additional meta-data fields:
i.e. a Country VO with additional properties that contain country specific information like used currency, IOC code, etc. - in that case the VO itself is just the iso code and the additional properties are just convenience properties. This can be solved by making use of @Transient properties and allowing VOs to fill those fields in their constructor via calculations/lookups. Transient properties are not submitted in URIs/Requests -
VOs with injections:
As noted above, those injections may break == comparison semantics on the VO. -
VOs inside VOs:
Doctrine 2.5 doesn’t yet support embeddables inside embeddables. -
*ToMany relations to VOs:
Doctrine 2.5 doesn’t yet support embeddable collections, see http://www.doctrine-project.org/jira/browse/DDC-2826