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 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 = $this->taskProvider->getNextTask();
try {
$this->someService->runTask($task);
} catch (SomeException $e) {
$this->switchTaskToError($task, $e);
}
}
, - dirty ( flush() ), , , .
— clear() "" . , , . clear() , , , $this->someService->runTask($task);
, ( , — ), . . , , . , , . .
:
while (!$this->isShuttingDown()) {
$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.