New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
!!! TASK: Rework AbstractDoctrineProjection for composibility & expli… #137
Conversation
…citness This change slightly flattens the inheritance hierarchy of the doctrine projectors and makes interaction with their state more explicit. This is a breaking change because it affects the public API of the `AbstractDoctrineProjector` and subclasses. The public methods `get()`, `add()`, `update()` and `remove()` have been moved to a sub object `state`. Adjusting the code is straight-forward: Previously: ``` final class SomeProjector extends AbstractDoctrineProjector { public function whenSomeEvent(SomeEvent $event) { $this->add(new Object()); // ... ``` Now: ``` final class SomeProjector extends AbstractDoctrineProjector { public function whenSomeEvent(SomeEvent $event) { $this->state->add(new Object()); // ... Related: neos#136
public function boot(Bootstrap $bootstrap) | ||
{ | ||
$dispatcher = $bootstrap->getSignalSlotDispatcher(); | ||
$dispatcher->connect(Bootstrap::class, 'finishedRuntimeRun', DoctrineProjectionPersistenceManager::class, 'persistAll'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DoctrineProjectionPersistenceManager
was renamed to DoctrineProjectionState
and is no longer a "singleton".
The persistAll()
call is now invoked in the shutdownObject()
lifecycle method of that class
* @Flow\Inject | ||
* @var DoctrineProjectionPersistenceManager | ||
*/ | ||
protected $projectionPersistenceManager; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not related, but a left-over it seems
if (substr(get_class($this), -9, 9) !== 'Projector') { | ||
throw new Exception(sprintf('The class name "%s" doesn\'t end in "Projector" so the Read Model class name can\'t be determined automatically. Please set the "readModelClassName" field it in your concrete projector.', get_class($this)), 1476799474); | ||
} | ||
$this->readModelClassName = substr(get_class($this), 0, -9); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code moved a few lines down
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tbh, I'm not quite sure about this change (apart from getting rid of AbstractBaseProjector
and the Package
). I understand the intention but it somehow feels awkward and I'm unsure this is the right abstraction.
I mean State
is a very abstract concept, but we're deep down inside a very specific (doctrine) implementation where "State" does not have a meaning. Doctrine is an ORM and deals with Objects (Entities) managed through an EntityManager
inside a UoW, so why should we abstract that away at this level? If the user decides to use a Doctrine Projector, he most likely wants to deal with a relational Entity Model anyway and most likely already knows some Doctrine specifics.
It would make sense if our generic Projector implementation had a ProjectionStateInterface
dependency, which in turn would be implemented by a DoctrineProjectionState
in order to get a "DoctrineProjector". The remaining AbstractDoctrineProjector
contains nothing doctrine specifc any more apart from instanciating a DoctrineProjectionState
so it should then be moved up one level.
Alternative suggestion: How about we just completely get rid of the DoctrineProjectionPersistenceManager
/DoctrineProjectionState
intermediary? If you look closely, the AbstractDoctrineProjector
just delegates a few methods, so IMO it does not have much value anyway and only adds an layer of indirection which you need to follow to understand the code. Let our Abstract*Projectors
be very specific implementations for the underlying persistence mechanic and not try to introduce an unnecessary layer of abstraction that possibly just complicates other Projector implementations and/or the concept.
{ | ||
/** | ||
* @Flow\Inject | ||
* @var DoctrineProjectionPersistenceManager | ||
* @var DoctrineProjectionState |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guess this could be removed then
Au contraire, we're in the middle of high-level user land projection code ;) But more importantly: Projection state & projector are not the same and I think it's not a good idea to mix up the two concepts..
Only my take, but as a general rule of thumb I think we should embrace SRP and try to avoid framework updates that change user land classes (including their base classes!). Ignoring that has lead to many issues in the past. I'm all for getting rid of some Abstract classes though. Also the |
I beg to differ, but I guess it depends on the PoV. My point is that we are in an framework level convenience class, that is only concerned about helping the application to persist some state in the form of doctrine ORM models. This class could easily be completely replaced by some application implementation of how to persist the projection only via DBAL or whatever the user wants.
The distinction is already made via Read Models, which are the units of the projection state in this case. I don't see any benefit in introducing another layer of abstracting in the form of some intermediary "State" class, especially if we don't make that a formal concept of the projection (i.e. an interface). Let's rather not focus on generalizing the Projection concept too much, but instead provide some helpful base implementations for a few concrete persistence methods. For the doctrine case I see that as:
Fair point, but I'm not fully convinced yet this is really an issue in this case. If we provide very flat single class base implementations of projection state persisters, which do not implement a generic framework concept/interface, I don't see the coupling as an issue on our side. The coupling happens to the persistence method, e.g. Doctrine, and we just provide a slim wrapper around that for convenience. We could document those as "Please copy this class into your code base and don't inherit it". |
Thanks for your valuable feedback! |
Yes I'll gladly talk this through more. I'm on vacation the next 2 weeks though, so that will have to wait a bit :) Feel free to discuss with the others and come to a conclusion though. |
Hey! So far I basically agree with @albe's stand point: when you choose to use a Doctrine projector base implementation, it doesn't has to hide the fact that it's an ORM, and adding an additional abstraction (state) is somewhat confusing. As we discussed during our last meeting, I'd like to introduce a |
Thanks for your feedback. I have to admit: I wasn't completely happy with the additional |
Question is, if we need to come up with projector-generic method names or if we rather choose names specifically for the projector type. I'd rather do the latter. However, Doctrine-specific method names would mean So, maybe a compromise would be to use the well-established method names from Flow's Doctrine Persistence Manager. The Persistence Manager provides methods like: |
Thanks for the discussion. |
Interesting, if you find any good practices emerging, feel asked to share :) |
…citness
This change slightly flattens the inheritance hierarchy of the doctrine
projectors and makes interaction with their state more explicit.
This is a breaking change because it affects the public API of the
AbstractDoctrineProjector
and subclasses.The public methods
get()
,add()
,update()
andremove()
havebeen moved to a sub object
state
.Adjusting the code is straight-forward:
Previously:
Now: