RFC: Streamline Value Object support in Flow

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

3 Likes

Hi Alexander, thanks for your RFC - I read it from top till bottom :wink:

I like the ideas you propose, however it requires several steps (as you mentioned) and culminates in a supposedly breaking change. That means, that the full support like you describe can only be aimed for Flow 4.0. But why not start with the necessary preparation (like the Doctrine upgrade). I don’t think that we should or will release 4.0 in 2015 or 2016, but sure let’s improve what we can already!

Certainly not in 2015 but I don’t see that we took any decision about a next major version yet. So I at least wouldn’t want to rule that out just based on the fact that we released 3.0 this year.

Yeah, this will be a huge change because it goes so deep into the core. I see no problem with only targeting all the breaking parts (removing value hash) at 4.0, no matter if that will come 2016 or later, but we should start ASAP with the first few steps towards that goal. First thing should be Doctrine 2.5 support and @kdambekalns and @christianm already worked a little bit on that, but there were some problems that couldn’t be solved for 3.0. Maybe that can be solved for 3.1 or 3.2 now and I’ll try to push in that direction. With that, the embeddable VO support should be relatively easy to finish.

Apart from that, there are probably some more small bugfixes that could be finished around the current implementation, to at least make it more viable until a possible fully usable 4.0.

See https://jira.neos.io/browse/FLOW-401 where I started to collect existing issues and changesets around VOs.

3 Likes