tommy-vercetti
@tommy-vercetti
Symfony/Golang

Где можно посмотреть толковые реализации API на Symfony?

Здравствуйте!
Пару месяцев на новом проекте, архитектура которого меня стала раздражать. Конечно, многие косяки я пофиксил, но всё равно этот код мне не дает покоя.

Что напрягает:

1. Сервисы, которые делают все. Например, получает request, делает валидацию, проверку доступа и дальше идет бизнес-логика. По мне, валидация и проверка доступа (на update entity, etc) должна быть в контроллере, и далее логика должна прокидываться в сервисы. Остальные сервисы дробить на более мелкие.

2. HTTP-эксепшены в сервисах. В будущем, возможно, придется вызывать эти сервисы в CLI, поэтому такие эксепшены не имеют смысла. Как вы обычно разруливаете в таких случаях. Бросаете стандартные php исключения, далее ловите их в контроллерах и уже бросаете HTTP-исключения?

3. JMS-сериалайзер. Из-за этих групп сериализации легко сломать клиентские приложения. Благо есть behat-тесты, но часто их гонять - потеря времени. Думаю создать отдельные нормалайзеры для каждой сущности. Всё будет в одном месте: можно инджектить сервисы (роутер, например, для генерации url), но есть проблема с генерацией API DOC.

4. NelmioApiDocBundle - очень крутой бандл, но завязан на группах сериализации. Бесят километровые комментарии перед экшенами, т.к при мерж-конфликтах легко допустить ошибку. Есть ли какая-то альтернатива?

5. Валидация. С простыми данными легко обходиться валидатором и DTO, но в более сложных случаях всё еще юзаю формы. Простой пример - sync many-to-many отношений. Также эвенты формы часто выручают.
  • Вопрос задан
  • 529 просмотров
Решения вопроса 3
@EvgeniiR
https://github.com/EvgeniiR
1. Валидация не в контроллере, а в аргумент резолверах валидируются DTO(через аннотации и Symfony validator), которые после поступают в контроллер.
Бизнес логика - в сущностях а не в сервисах.
2. "Http-эксепшн" - не бывает таких. Бывают просто эксепшены, и просто эксепшены которые горе-программист использует как goto для того чтобы вылезти из стека вызовов и сконвертировать ошибку. Не нужно так делать. Логика кидает исключение, контроллер формирует респонс.
3. JMS-serializer не нужен и заменяется symfony-serializer. Сущности не сериализуются в ответ для пользователя, для этого существуют DTO.
Ответ написан
OnYourLips
@OnYourLips
3. Выбросить, он делает то, что делать не нужно. А то, что нужно, делается штатными средствами symfony. Используйте DTO и ассемблеры, версионируйте их. Имейте живые версии параллельно.

4. Используйте OpenAPI, это развитие вашего подхода. Не пишите документацию внутри кода.
Этот бандл в помойку вслед за предыдущим.
Ответ написан
tommy-vercetti
@tommy-vercetti Автор вопроса
Symfony/Golang
Опишу тут к чему мы в итоге пришли, вдруг кому-нибудь пригодится :)

- Используем RDM (Rich Domain Model)
- Ивенты бросаем внутри сущности. Для этого мы используем интерфейс RaiseEventsInterface и трейт RaiseEventsTrait.

interface RaiseEventsInterface
{
    public function releaseAndResetEvents(): array;
}

trait RaiseEventsTrait
{
    protected array $events = [];

    public function releaseAndResetEvents(): array
    {
        $events = $this->events;

        $this->events = [];

        return $events;
    }

    protected function raise(AbstractDomainEvent $event): void
    {
        $event->initTime();

        $this->events[] = $event;
    }
}

В DoctrineDomainEventPublisher мы пробегаемся по всем новым и измененным сущностям и забираем ивенты. Далее сортируем эти ивенты по времени и диспатчим в Event Bus (компонент symfony/messenger) .

- Не используем бандлы: FosRestBundle, FosUserBundle

- Для валидации используем DTO через action argument resolver. В основном валидация через DTO делает простые проверки: на тип, дату, период времени и тд. Проверки на принадлежность к какой-то сущности проводятся в самой сущности. В случае невалидного состояния из сущности бросается эксепшн, например UncancellableMissionException, MissionOfferSlotCannotBeAcceptedException и тд.

- В контроллере нет бизнес-логики. Сообщение диспатчится в Command или Query Bus.

- Для репозиториев используем интерфейсы с общими методами: get, getOneByCriteria, add и тд. Например, метод add делает внутри $em->persist().

- Стараемся избегать entity manager в коде типа: $em->persist(), $em->flush(). Благо компонент symfony/messenger предоставляет doctrine_transaction middleware. Конечно, не везде удается обходиться без entity manager, например, в ситуации с блокировками транзакций.

- Для генерации доки используем API Blueprint - пакет https://github.com/bukalapak/snowboard

- Для респонсов используем Fractal https://fractal.thephpleague.com/transformers/

- Для тестов API - Behat
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы