Применяется ли паттерн MVC при разработке REST API?

«Применяется ли паттерн MVC при разработке REST API?» — вопрос из категории Архитектура, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, паттерн MVC (Model-View-Controller) применяется, но в адаптированном виде, так как классическое View (Представление) для генерации HTML-интерфейса в чистом API отсутствует.

В контексте REST API компоненты переосмысливаются:

  1. Model (Модель) — остаётся без изменений. Это ядро бизнес-логики: сущности, сервисы, репозитории, правила валидации. Например, класс User с методами activate(), changePassword().
  2. Controller (Контроллер) — его роль сужается. Он становится тонким слоем, который:
    • Принимает HTTP-запрос.
    • Валидирует входные данные (например, с помощью Symfony Validator).
    • Вызывает соответствующий метод Модели или Сервиса.
    • Форматирует результат (обычно в JSON) и возвращает HTTP-ответ.
  3. View (Представление) — трансформируется в Serializer / Response Formatter. Его задача — преобразовать данные из Модели (объекты, массивы) в формат, пригодный для передачи по сети (JSON, XML). В Symfony эту роль часто выполняют компоненты Serializer и JsonResponse.

Пример контроллера REST API в Symfony:

// src/Controller/Api/UserController.php
namespace AppControllerApi;

use AppEntityUser;
use AppRepositoryUserRepository;
use SymfonyBundleFrameworkBundleControllerAbstractController;
use SymfonyComponentHttpFoundationJsonResponse;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentRoutingAttributeRoute;
use SymfonyComponentSerializerSerializerInterface;

#[Route('/api/users')]
class UserController extends AbstractController
{
    public function __construct(
        private UserRepository $userRepository,
        private SerializerInterface $serializer
    ) {}

    #[Route('', name: 'api_user_list', methods: ['GET'])]
    public function list(): JsonResponse
    {
        $users = $this->userRepository->findAll();
        // Сериализатор (View-слой) преобразует объекты в JSON
        $json = $this->serializer->serialize($users, 'json', ['groups' => 'user:read']);
        return new JsonResponse($json, Response::HTTP_OK, [], true);
    }

    #[Route('/{id}', name: 'api_user_show', methods: ['GET'])]
    public function show(User $user): JsonResponse // ParamConverter автоматически находит User по id
    {
        $json = $this->serializer->serialize($user, 'json', ['groups' => 'user:detail']);
        return new JsonResponse($json, Response::HTTP_OK, [], true);
    }
}

Таким образом, применяется MC-Serializer или MVC, где V — это сериализатор. Это сохраняет принцип разделения ответственности: Модель управляет данными, Контроллер — потоком запроса, а Сериализатор — форматом вывода.