1 раз делаю проект на Symfony и я не уверен как правильно построить архитектуру. Сначала покажу код который у меня есть, а затем вопросы.
Проект будет RestApi. Принимаем json, отдаём json в формате
JSend.
Архитектура примерно такая:
Контроллер -> Сервис -> Репозиторий -> Entity
Пример: Создание продукта. У продукта есть связь ManyToOne с Workspace.
ProductController:public function store(
Request $request,
ProductStoreValidation $productStoreValidation,
ValidationTransformer $validationTransformer
): Response {
$body = $request->request->all();
$violations = $productStoreValidation->validate($body);
if ($violations->count()) {
$errorsData = $validationTransformer->transform($violations);
throw new ValidationRawException($errorsData);
}
$product = $this->productService->addProduct($body);
$data = [
'product' => $product
];
$response = new JSendResponse(JSendResponse::SUCCESS, $data);
return new JsonResponse($response, Response::HTTP_CREATED);
}
ProductService:public function addProduct(array $body): Product
{
$product = new Product();
$product->setName($body['name']);
$product->setSku($body['sku']);
$product->setImage($body['image']);
$workspace = $this->workspaceService->getWorkspaceById($body['workspace_id']);
$product->setWorkspace($workspace);
$product = $this->productRepository->save($product);
return $product;
}
DoctrineProductRepository:public function save(Product $product): Product
{
$this->entityManager->beginTransaction();
try {
$this->entityManager->persist($product);
$this->entityManager->flush();
$this->entityManager->commit();
} catch (\Exception $e) {
$this->entityManager->rollback();
throw new ProductRepositoryException('Error while saving product', $e->getCode(), $e);
}
return $product;
}
Вопросы:
1) Где будет правильно проверять "Существует ли Workspace?"? Являет ли это бизнес-логикой?
сейчас я проверяю в классе валидации, но может быть это нужно сделать в Сервисе или где-то еще?
2) Где нужно заполнять объект Product?
сейчас я просто вызываю метод сервиса и передаю ему всё тело(после валидации) и там заполняется каждое свойство. Я посмотрел много примеров с формами и там форма выполняет валидацию и возвращает объект Product, может быть нужно сделать так же, но без форм? Я не использую формы.
3) Если я перенесу валидацию с из
вопрос 1 в сервис. То сервис должен формировать ошибку и вываливать экзепшен?
4) Я понимаю для чего нужен репозиторий, энтети, но не особо понимаю, что должно быть в сервисе? в контроллере?
В целом может быть есть какие-то советы? Может я совсем не так всё делаю? Я стараюсь сделать тонкие контроллеры.
Другой пример:
Обновление Product
Код примерно такой же, но в примере создания я создаю Product в сервисе, а при обновлении получаю Product в контроллере и отдаю его в сервис. Верно или это решение?
POST - /product/{id}
ProductControllerpublic function update(
Request $request,
int $id,
ProductUpdateValidation $productUpdateValidation,
ValidationTransformer $validationTransformer
): Response {
if (null === $product = $this->productService->getProductById($id)) {
throw new NotFoundHttpException('Product not found');
}
$body = $request->request->all();
if (empty($body)) {
throw new BadRequestHttpException('Bad request');
}
$violations = $productUpdateValidation->validate($body);
if ($violations->count()) {
$errorsData = $validationTransformer->transform($violations);
throw new ValidationRawException($errorsData);
}
$this->productService->updateProduct($product, $body);
$data = [
'product' => $product
];
$response = new JSendResponse(JSendResponse::SUCCESS, $data);
return new JsonResponse($response, Response::HTTP_OK);
}
ProductServicepublic function updateProduct(Product $product, array $body): Product
{
if (isset($body['name'])) {
$product->setName($body['name']);
}
if (isset($body['sku'])) {
$product->setSku($body['sku']);
}
if (isset($body['image'])) {
$product->setImage($body['image']);
}
if (isset($body['workspace_id'])) {
$workspace = $this->workspaceService->getWorkspaceById($body['workspace_id']);
$product->setWorkspace($workspace);
}
return $this->productRepository->save($product);
}