Replies: 22 comments
-
Current possible solution: |
Beta Was this translation helpful? Give feedback.
-
Can you provide PR with failing scenario? |
Beta Was this translation helpful? Give feedback.
-
Hi. I will add code snippets describing the problem. After clear all existing in database entities doctrines are perceived as new and their insertion and binding occurs, instead of just binding to the aggregate |
Beta Was this translation helpful? Give feedback.
-
@dgafka https://gist.github.com/sushchyk/8ce3c718270f0ac75eb07264f899722a |
Beta Was this translation helpful? Give feedback.
-
So the case is that when we've main Entity (Aggregate Root) like @lifinsky do I understand the problem correctly? |
Beta Was this translation helpful? Give feedback.
-
No, existing tag was saved but as new record (doctrine allows cascade: ['persist']):
I think we should clear only agregates in unit of work. Entity manager supports clear($entityName = null) - null|string $entityName – if given, only entities of this type will get detached. |
Beta Was this translation helpful? Give feedback.
-
Currently for this example we should find tag in interceptor with pointcut "before" on CommandHandler (after transaction start) |
Beta Was this translation helpful? Give feedback.
-
So the problem is that we create @lifinsky is this understanding correct? |
Beta Was this translation helpful? Give feedback.
-
Problem in using of existing entity, with current functionality of Ecotone we should inject tag repository to aggregate class or use command handler interceptor that find entity by id and fill attribute in command or return new command object (command is ugly with default arguments with null, for example int $tagId = null, Tag $tag = null. We should not clear entities in unit of work before command transaction |
Beta Was this translation helpful? Give feedback.
-
I think, it's fine to set up clearing after, as long as we can clean with each Message, we are good. |
Beta Was this translation helpful? Give feedback.
-
I'm running into this same issue. Did anything get sorted in the end as the last comment is quite a while ago and I can't find any related PR. Thanks |
Beta Was this translation helpful? Give feedback.
-
This issue is actual and not resolved |
Beta Was this translation helpful? Give feedback.
-
In Ecotone Framework, the ObjectManagerInterceptor clears the Doctrine EntityManager before proceeding with the method invocation. This causes problems when working with Doctrine ORM. Possible SolutionOptionally Disabling EntityManager Clearing via Interface for commandWe can use a marker interface to specify that a command should not trigger EntityManager::clear() before execution. Solution Steps
Define a simple marker interface that commands can implement to prevent EntityManager clearing: Interface PreventEntityManagerClear {} This interface does not require any methods — its presence is enough to signal behavior.
Add the interface to commands that should bypass clear(): readonly class CreatePostCommand implements PreventEntityManagerClear
{
public function __construct(
public string $title,
public Tag $tag
) {}
} Now, Ecotone can detect that this command should not clear the EntityManager.
Instead of checking metadata, check whether the command implements PreventEntityManagerClear: private function shouldSkipClearing(MethodInvocation $methodInvocation): bool
{
$arguments = $methodInvocation->getArguments();
if (empty($arguments)) {
return false;
}
$command = $arguments[0]; // The first argument is the command or event object
return $command instanceof PreventEntityManagerClear;
} Now, modify the condition before calling clear(): if ($this->depthCount === 1 && !$this->shouldSkipClearing($methodInvocation)) {
$objectManager->clear();
} @dgafka We can, of course, use an additional attribute for the command handler itself to avoid adding an infrastructure interface to the domain layer where the command class is located. |
Beta Was this translation helpful? Give feedback.
-
Attribute name options: #[PreserveEntityState]
#[SkipEntityManagerReset]
#[DisableEntityManagerClear] In general, it is always possible to avoid clearing unless inside a consumer, for example... |
Beta Was this translation helpful? Give feedback.
-
Possible solution but maybe poor performance: // Detach only aggregates implementing AggregateRoot interface, keeping other entities managed.
$unitOfWork = $entityManager->getUnitOfWork();
foreach ($unitOfWork->getIdentityMap() as $className => $entities) {
if (is_subclass_of($className, AggregateRoot::class)) {
foreach ($entities as $entity) {
$entityManager->detach($entity);
}
}
} |
Beta Was this translation helpful? Give feedback.
-
Possible solution but maybe poor performance: if ($this->depthCount === 1) {
$this->clearAggregatesOnly($objectManager);
}
private function clearAggregatesOnly(EntityManagerInterface $entityManager): void
{
$unitOfWork = $entityManager->getUnitOfWork();
foreach ($unitOfWork->getIdentityMap() as $className => $entities) {
$reflectionClass = new \ReflectionClass($className);
// Check if the class has the #[Aggregate] attribute
if (!empty($reflectionClass->getAttributes(Aggregate::class))) {
foreach ($entities as $entity) {
$entityManager->detach($entity);
}
}
}
} We can optimize this approach by caching which classes are aggregates to avoid unnecessary reflection calls on every transaction. Since the set of aggregates is static at runtime, we only need to check once per class and store the result. |
Beta Was this translation helpful? Give feedback.
-
I am still not sure about the problem itself. |
Beta Was this translation helpful? Give feedback.
-
Cascading saves with an aggregate, for example, entities in a many-to-many relationship that we pass directly in the command, rather than retrieving them by ID in the middle of the aggregate's command handler through an injected repository. Currently, the entity manager is cleared before the command handler is executed. |
Beta Was this translation helpful? Give feedback.
-
@lifinsky so you're sending command inside command, where second one contains of an Entity (passed from execution of the first one)? |
Beta Was this translation helpful? Give feedback.
-
This could be an application service where these entities are retrieved by the repository and passed in the command to the aggregate. For example, tags for some state aggregate. Or places where the entity already gives the symphony its mapping for the route and so on. There could also theoretically be a service that calls two command handlers sequentially and can take entities saved in a cascade with the first aggregate for the second. But we don't have such a product case right now. |
Beta Was this translation helpful? Give feedback.
-
Is there a reason why Application Service is not being Service Command Handler? That sounds like you need more flexibility, so you could higher level and take over the orchestration process in that situation. |
Beta Was this translation helpful? Give feedback.
-
@dgafka I am generally against having any asynchronous and distributed handlers in the domain. But I’m curious why we always have to clear the entity manager before executing a handler. It feels like there's a lack of control here. Perhaps we really need to introduce the concept of an Entity (with Ecotone attribute) alongside the aggregate — one that doesn’t use aggregate mechanisms and, therefore, doesn’t require clearing in the entity manager before working with the aggregate. We can put this discussion on hold for now, but such an approach would provide much more convenience, especially considering that Doctrine is a great solution for state aggregates. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Description
In aggregates, it's possible to use, for instance, Doctrine entities with cascading persistence, but only for their insertion. Because ObjectManagerInterceptor clear object manager on transaction start.
Example
The only way to correctly save an aggregate with a one-to-many or many-to-many collection of entities is to inject the repository into the command handler of the aggregate or to use a "Before" interceptor that adds entities into the command before passing it to the handler.
Beta Was this translation helpful? Give feedback.
All reactions