Problems Using Doctrine ODM in Daemon Processes

I want to talk about my experience using Doctrine ODM in a relatively small PHP project in which the main code base is concentrated in daemon processes. And in general, how we fastened Doctrine ODM to Yii2. I warn you right away - the story will be very boring and most likely only interesting to those who have already encountered problems when working with Doctrine ODM in daemon processes.


Where we will screw


About 6 years ago, they entrusted me with the task: to cut from one monolith into a Yii1piece of code into a separate service. No sooner said than done. The framework didnโ€™t choose for a long time, except YiiI didnโ€™t know anything, and then they announced an imminent release Yii2, which at that time seemed very stylish / fashionable / youthful, but knowledge of the first version of this framework allowed reducing the threshold for entering the second version. MongoDB was also fashionable, with which we were already familiar and worked Yii1with a separate extension, but Yii2it was supported out of the box.


Initially, the microservice was quite simple: it was necessary to receive files from users and store them in a persistent storage, keep records of these files in the database, issue information regarding these files and links to them in response to relevant requests through the API.


Over time, this micro-service began to grow: video processing, virus checking, distributed storage across different regions (a la CDN) appeared. Time passed quickly, the project actively developed and even underwent refactoring, but at some point, due to new trends and professional growth, too, a feeling of some discomfort appeared when interacting with this project. The desire to work on this project became less and less and I seriously thought.


One of the reasons for the discomfort was the inability to work conveniently with the Embedded Document . The inconvenience was that I had to deal with native PHParrays. At first, this did not cause problems, but as the project grew, these arrays began to appear everywhere, they became more and more, and the knowledge about the old code was forgotten and the understanding of everything deteriorated. And in general, I wanted to have beautiful objects, and not an incomprehensible set of fields whose indices must be constantly remembered. I'm not talking about the fact that such code is very poorly connected statically.


PHP 7- , . ActiveRecord Yii2 โ€” "" , , phpdoc. MongoDB, - , - {user_id : '12'}


, . - , Yii2 . , , , , , .



, , MongoDB.
Symfony Doctrine ORM, Doctrine ODM โ€” ORM MongoDB.


Yii2 Symfony , , :
- โ€” , , .
- โ€” ActiveRecord " " Yii2


, ActiveRecord Doctrine ODM, . , ORM .


. Doctrine DocumentManager ( EntityManager Doctrine ORM). " ".


Dependency Injection Container Yii2 , .


Yii2 DataProvider'a. -, CRUD- . " " . , Yii2. , , โ€” , .


, Doctrine UnitOfWork , .



, : DI- , . - , Symfony .


, :


while (!$this->isShuttingDown()) {
    //  Task,  ,     
    //  MANAGED    ,
    //     
    $task = $this->taskProvider->getNextTask();
    try {
        //   -  , ,  
        $this->someService->runTask($task);
    } catch (SomeException $e) {
        //    :       
        //     MANAGED,      
        //       -     
        $this->switchTaskToError($task, $e);
    }
}

, - dirty ( flush() ), , , .


โ€” clear() "" . , , . clear() , , , $this->someService->runTask($task);



, ( , โ€” ), . . , , . , , . .


:


while (!$this->isShuttingDown()) {
    //  ,  , 
    //   -  ,   
    //    detached,      , 
    //      
    $task = $this->taskProvider->getNextTask();
    try {
        //   -     
        $this->someService->runTask($task);
    } catch (SomeException $e) {
        //    ,  -   
        $this->switchTaskToError($task, $e);
    }
}

taskProvider someService , .


merge. runTask() :


public function runTask(Task $task) {
    $dm = $thic->dmFactory->createDocumentManager();
    $task = $dm->merge($task);
    ....
    $task->setStatus(Task::STATUS_OK);
    $dm->flush();
}

switchTaskToError() :


public function switchTaskToError(Task $task, Throwable $) {
    $dm = $this->dmFactory->createDocumentManager();
    $task = $dm->merge($task);
    $task->setStatus(Task::STATUS_ERROR);
    $task->setError($e->getMessage());
    $dm->flush();
}

, $dm .


, , - . , :


$tasks = $this->taskProvider->getProblemTasks();
$dm = $thic->dmFactory->createDocumentManager();
foreach ($tasks as $task) {
     $task = $dm->merge($task);
     $task->setState(..);
}
$dm->flush();

getProblemTasks , , "" , Task merge(). , merge() โ€” .. , .


, , :


$dm = $thic->dmFactory->createDocumentManager();
$tasks = $this->taskProvider->getProblemTasks($dm);
foreach ($tasks as $task) {
     $task->setState(..);
}
$dm->flush();

.


JPA


, Doctrine JPA: , UnitOfWork, (managed, detached ..). JPA , , . , โ€” . (Spring Framework ). JPA , , .


, , . - , , - (? ), ( ). , , .


, hibernate Doctrine.


Doctrine ODM


" doctrine" , - http , , , .


, . , . . , :


$session = $this->sessionManager->createNewSession();
try {
    $dm= $session->getDocumentManager();
    $tasks = $this->taskProvider->getProblemTasks();
    foreach ($tasks as $task) {
        $task->setState(..);
    }
    $dm->flush();
} finally {
    $this->sessionManager->close($session);
}

getProblemTasks


public function getProblemTasks(): array
{
    $repo = $this->sessionManager
                      ->getCurrentSession()
                      ->getDocumentManager()
                      ->getRepository(Task::class);

    return $repo->findBy(['status' => Task::STATUS_ERROR]);
}

, , , , .


:


public function switchTaskToError(Task $task, Throwable $) {
    //   ,  ,    
    $session $this->sessionManager->createNewSession();
    $dm = $session->getDocumentManager();
    try {
        $task = $dm->merge($task);
        $task->setStatus(Task::STATUS_ERROR);
        $task->setError($e->getMessage());
        $dm->flush();
    finally {
        $this->sessionManager->close($session);
    }
}

, , , wrap, , .


, 3 SessionManager


createNewSession โ€” , "" .
getCurrentSession โ€” .
close โ€”


, ( php-ds).


:
createNewSession โ€” , .
getCurrentSession โ€” , , .
close โ€” .


, , , .


wrap, , :


public function switchTaskToError(Task $task, Throwable $) {
    $this->sessionManager->wrapBySession(function(DocumentManager $dm) use ($task, $e) {
        $task = $dm->merge($task);
        $task->setStatus(Task::STATUS_ERROR);
        $task->setError($e->getMessage());
        $dm->flush();
    });
}


In general, the session mechanism turned out to be quite convenient. At least he solved all our problems. Of course, it would be nice to also manage sessions using a declarative approach, as is done in the Spring Framework, for example. This would significantly reduce overhead in the form of redundant code, but at the moment I can not even imagine how this can be done in PHP.


All Articles