Problemas ao usar o Doctrine ODM nos processos de daemon

Quero falar sobre minha experiência no uso do Doctrine ODM em um projeto PHP relativamente pequeno, no qual a principal base de código está concentrada nos processos daemon. E, em geral, como fixamos o Doctrine ODM no Yii2. Eu o aviso imediatamente - a história será muito tediosa e provavelmente interessante apenas para aqueles que já encontraram problemas ao trabalhar com o Doctrine ODM em processos de daemon.


Onde vamos estragar


Cerca de 6 anos atrás, eles me confiaram a tarefa: cortar de um monólito em um Yii1pedaço de código em um serviço separado. Não antes de dizer que acabou. A estrutura não escolheu por um longo tempo, exceto que Yiieu não sabia de nada, e eles anunciaram um lançamento iminente Yii2, que na época parecia muito estiloso / moderno / jovem, mas o conhecimento da primeira versão dessa estrutura permitiu reduzir o limiar de entrada na segunda versão. O MongoDB também estava na moda, com o qual já estávamos familiarizados e trabalhamos Yii1com uma extensão separada, mas Yii2era suportada imediatamente.


Inicialmente, o microsserviço era bastante simples: era necessário receber arquivos dos usuários e armazená-los em um armazenamento persistente, manter registros desses arquivos no banco de dados, emitir informações sobre esses arquivos e links para eles em resposta a solicitações relevantes por meio da API.


Com o tempo, esse microsserviço começou a crescer: processamento de vídeo, verificação de vírus, armazenamento distribuído em diferentes regiões (à la CDN). O tempo passou rápido, o projeto desenvolveu-se ativamente e até passou por refatoração, mas em algum momento, devido a novas tendências e crescimento profissional, também apareceu um sentimento de desconforto ao interagir com esse projeto. O desejo de trabalhar nesse projeto tornou-se cada vez menor e pensei seriamente.


Embedded Document . , PHP . , , , . , , . , .


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


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



, , MongoDB.
Symfony Doctrine ORM, Doctrine ODMORM 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();
    });
}


Em geral, o mecanismo da sessão acabou sendo bastante conveniente. Pelo menos ele resolveu todos os nossos problemas. Obviamente, seria bom também gerenciar sessões usando uma abordagem declarativa, como é feito no Spring Framework, por exemplo. Isso reduziria significativamente a sobrecarga na forma de código redundante, mas no momento não consigo nem imaginar como isso pode ser feito no PHP.


All Articles