Quiero hablar sobre mi experiencia usando Doctrine ODM en un proyecto PHP relativamente pequeño en el que la base del código principal se concentra en los procesos de daemon. Y en general, cómo fijamos Doctrine ODM a Yii2. Te advierto de inmediato: la historia será muy aburrida y probablemente solo interesante para aquellos que ya han encontrado problemas al trabajar con Doctrine ODM en los procesos de daemon.
Donde vamos a atornillar
Hace unos 6 años, me confiaron la tarea: cortar de un monolito en un Yii1
código en un servicio separado. Dicho y hecho. El marco no se eligió por mucho tiempo, excepto Yii
que no sabía nada, y luego anunciaron un lanzamiento inminente Yii2
, que en ese momento parecía muy elegante / a la moda / juvenil, pero el conocimiento de la primera versión de este marco permitió reducir el umbral para ingresar a la segunda versión. MongoDB también estaba de moda, con lo que ya estábamos familiarizados y trabajamos Yii1
con una extensión separada, pero Yii2
fue compatible de inmediato.
Inicialmente, el microservicio era bastante simple: era necesario recibir archivos de los usuarios y almacenarlos en un almacenamiento persistente, mantener registros de estos archivos en la base de datos, emitir información sobre estos archivos y enlaces a ellos en respuesta a solicitudes relevantes a través de la API.
Con el tiempo, este microservicio comenzó a crecer: apareció el procesamiento de video, la detección de virus, el almacenamiento distribuido en diferentes regiones (a la CDN). El tiempo pasó rápidamente, el proyecto se desarrolló activamente e incluso se refactorizó, pero en algún momento, debido a las nuevas tendencias y al crecimiento profesional, también apareció una sensación de incomodidad al interactuar con este proyecto. El deseo de trabajar en este proyecto se hizo cada vez menos y pensé 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();
});
}
En general, el mecanismo de la sesión resultó ser bastante conveniente. Al menos resolvió todos nuestros problemas. Por supuesto, sería bueno administrar también las sesiones utilizando un enfoque declarativo, como se hace en Spring Framework, por ejemplo. Esto reduciría significativamente la sobrecarga en forma de código redundante, pero por el momento ni siquiera puedo imaginar cómo se puede hacer esto en PHP.