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 Yii1
pedaç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 Yii
eu 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 Yii1
com uma extensão separada, mas Yii2
era 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.