De MVC a DDD Parte II - ¿Porqué llevar la lógica de negocio a un servicio?

Un vistazo al controlador PublishController

Como podemos apreciar, el controlador tiene lo básico.
Toda la lógica de negocio de la publicación se gestiona en el método publish. Aquí se trata la request por POST si no hay nada devuelve 1 como id por defecto. Normalmente los frameworks cuentan con un objeto request. A partir de la petición obtenemos las entidades de usuario y post. Se cambia el estado, se persiste el cambio y despues se envia un email al usuario avisando de la publicación. Finalmente se guarda un log con el titulo del post y el email.

Se instancian los repositorios correspondientes. Los respositorios nos permiten leer/escribir del almacén de datos. En las típicas aplicaciones XAMP (x: linux/windows, Apache, Mysql, Php) representan operaciones sobre una tabla concreta de Mysql.

Así pues UsersRepository mapearia operaciones contra la tabla users y posts en el caso de PostsRepostitory.

Aqui se me ha pasado usar el plural en el nombre. Tenedlo en cuenta (creo que es un estándar)

Al igual que los repositorios están las entidades. La entidad representa un registro de la tabla y debe contar con atributo/s que sea únicos para identificarlo. (la clave o claves primarias, usualmente el id). A las entidades les he puesto el sufijo Entity con la idea de tener mejor identificados los archivos. Normalmente no llevan esta palabra.

Las entidades y los repositorios forman pate de la capa de datos conocida como modelo.

Esta solución funciona pero nos limita. Si tuvieramos que hacer el proceso de publicar un post usando una API tendríamos que repetir código y si además hay una opción de publicación usando linea de comandos sucederia algo similar.

public function publish(): void { $userId = $this->getRequestSession("userId", 1); $postId = $this->getRequestPost("postId", 1); try { $userRepository = new UserRepository(); $user = $userRepository->ofIdOrFail($userId); $postRepository = new PostRepository(); $post = $postRepository->ofIdOrFail($postId); $post->publish(); $postRepository->save($post); $this->notifyToUser($post, $user); $this->set("post", $post); } catch (Exception $e) { $this->set("error", $e->getMessage()); } pr("rendering saved post ..."); $this->render("post-published"); } private function notifyToUser(PostEntity $post, UserEntity $user): void { pr("sending email ..."); mb_send_mail( $user->email(), "Your post with id {$post->id()} has been published", "Congrats! your post has been published" ); pr("monologging ..."); (new Monolog())->log("Post with title {$post->title()} published by user {$user->email()}"); }

Lo mejor sería tener esta lógica de negocio en una clase aparte que acepte unos datos de entrada (de cualquier origen), en este caso

$userId y $postId
después ejecúte su método público y devuelva el resultado correspondiente.

Quedando algo así:

//servicio con la lógica de negocio. //Este servicio se inyectaría como una dependencia en los controladores u otros servicios final class PostPublishService { public function __construct() { ... } public function publishOrFail(int $userId, int $postId): PostEntity { try { ... } .. } } final class ApiController { public function __construct(PostPublishService $publishService) { ... } public function publish(): Json { $userId = $this->authUser->id; $postId = $this->request->getParam("postId"); $post = $this->publishService->publishOrFail($userId, $postId); return json_encode($post); } } final class ConsoleController { public function __construct(PostPublishService $publishService) { ... } public function publish(): void { $userId = $this->console->getParam("userId"); $postId = $this->console->getParam("postId"); $post = $this->publishService->publishOrFail($userId, $postId); echo json_encode($post); } } final class WebController { public function __construct(PostPublishService $publishService) { ... } public function publish(): void { $userId = $this->sessionUser->id; $postId = $this->request->getParam("postId"); $post = $this->publishService->publishOrFail($userId, $postId); $this->set($post,"post"); $this->render(); } } final class CronService { public function __construct(PostPublishService $publishService) { ... } public function consumePostAndPublish() { $payload = $this->curlGet("https://my-posts.eduardoaf.com/1"); $userId = $payload->userId; $postId = $payload->PostId; $this->publishService->publishOrFail($userId, $postId); } }

Ahora tenemos un código más limpio tolerante a cambios y centrando la responsabilidad en un solo sitio.

Autor: Eduardo A. F.
Publicado: 10-04-2022 01:08
Actualizado: 22-04-2022 20:44