Комментарии 12
Невозможно проверить бизнес-логику с помощью unit-тестов. Обязательно нужны интеграционные (и довольно много).
Если домен сложный, подобный подход быстро приведет к спагетти-коду.
Риск дублирования кода, который может привести к неожиданным багам при дальнейшей эволюции системы.
Если вы пишете тесты не ради галочки и покрытия, а в целях повышения качества, то вам все равно не обойтись без интеграционных тестов БД со всеми ее фокусами.
Хибер и альтернативные решения как-то принципиально защищены от спагетти-кода при росте сложности? На каких примерах вы замеряли "быстроту" сползания джука в спагетти-код?
Хибер и альтернативные решения как-то принципиально защищены от риска дублирования кода и неожиданных багов?
Итого 2 ваших "минуса" джука на самом деле являются особенностями разработки ПО в целом. Соответственно, все дальнейшие рассуждения идут из сомнительного тезиса.
не соглашусь т.к отдельно тестировать бд и бизнес логику это одно, намного проще, а вот тестировать бизнес логику и бд совместно это абсолютно разные вещи. Я уже устал править тесты на 20 и более ассертов просто потому что 'иначе не получится', а нафига вы сильную связность создали.
Что используете у себя на проекте?
Не используем ORM
Соглашусь с автором что для каждой конкретной цели есть свой инструмент. Единственный момент - возможно вы упустили или не указали в статье, что у Hibernate также есть возможность проверки кода во время компиляции. Для этого нужно использовать Criteria API и библиотеку генерации метамоделей классов hibernate-jpamodelgen.
Приведу примеры кода с использованием метамодели и Criteria API.
Репозиторий:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface SpeakerRepository extends JpaRepository<Speaker, Long>, JpaSpecificationExecutor<Speaker> {
}
Сервис с использованием метамодели Speaker:
import lombok.RequiredArgsConstructor;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class SpeakerService {
private final SpeakerRepository speakerRepository;
public List<Speaker> findByLastName(String lastName) {
return speakerRepository.findAll(hasLastName(lastName));
}
private Specification<Speaker> hasLastName(String lastName) {
// Используется сгенерированная метамодель Speaker_.java
return (root, query, cb) -> cb.equal(root.get(Speaker_.lastName), lastName);
}
}
Метамодель Speaker_.java
генерируется во время компиляции и её можно использовать для проверки корректности составленных запросов (типы данных, наименование полей).
Да, вы правы, что jpametamodelgen помогает в построении валидных запросов с помощью Criteria API. Тем не менее, функциональность не такая продвинутая как у JOOQ. Последний может проверить, допустим, что вы используете поле именно из правильной таблицы, проверить типы данных в соответствии с колонками и т д. Jpamodelgen просто сгенерит константы с названиями атрибутов из сущностей. Это, конечно, лучше чем просто использовать строки в коде. Но JOOQ здесь явно ушел дальше)
То что вы описали, а именно бизнес логика в доменных объектах можно сделать и с помощью JOOQ судя по тому по примеру как пишутся запросы на нем. Я лично не пользовался этой библиотекой, но пользуюсь кажется почти аналогом skunk на скале. А там это отлично делается, причем изначально код получается максимально оптимизированным. А сама техника не смешивать бизнес логику с запросами в базу данных или любыми другими эффектами вроде логично и особенно практикуется в функциональном программировании. Бизнес логика должна получать данные и возвращать результат, ничего более, и это очень облегчит тестирование. Более подробно можете поискать "Moving IO to the edges of your app: Functional Core, Imperative Shell"
Поддерживаю. Годы идут, усталость от "магических" проблем все выше. Все больше достает вопрос а какой в итоге запрос получится? Если говорить про хибер, то всякие автозапросы и n+1.... В итоге чем старше и опытнее становишься, тем больше хочется иметь понятные написанные руками аккуратно лежащие где-то запросы, вместо этой магии удобства популярных апи и начинаешь мечтать о старом добром jdbcTemplate или хотя бы простой и понятной батис модели ))))
Да просто включите логгирование запросов в локальном профайле и будет вам счастье, все эти джуки и майбатисы превращаются в неконтролируемого и сложнотестируемого монстра, когда проект более менее разрастается. И добавление одного поля может поломать всю систему, когда все поотваливается в ран тайме.
Статья приписывает недостатки JDBI всем Transaction Script решениям. И выдает спорные решения в Hibernate за преимущества.
DynamicUpdate, например, в Hibernate отключен по умолчанию, так как в обмен на уменьшение данных, передаваемых по сети, он не использует кэш запросов, чем повышает нагрузку на сервер.
"Spring Data JDBC" позволяет код из первого примера сделать втрое короче и в разы читабельнее. С ним удобно работать и с SQL-запросами в репозитории, и с Entity-классами через привычные методы CrudRepository.
При этом он не имеет недостатков Hibernate:
Проблемы N+1 и N+M+1 даже в Eager-режиме.
Проблемы перегрузки соединениями БД из-за неудачно реализованного Lazy-режима и попытки это исправить, используя OSIV.
Проблемы метода save/persist не выполняемого в момент вызова, не позволяющего сразу получить id записи в базе. Это вынуждает прописывать транзакции, где они не требуются по бизнес-логике, с высоким риском замедлить базу избыточными блокировками.
Бизнес-логика сконцентрирована в одном месте, в доменных объектах.
А как-же SpeakerService? В нем тоже фрагмент бизнес-логики.
Когда передадут в реализацию еще 10 API, использующих эту таблицу базы, все методы с их бизнес-логикой будут перемешаны в Entity-классех? Вот уж спагетти, так спагетти.
Завязывать доменную модель приложения на схему базы данных - странное решение. Получается, мы нюансы хранения данных тащим в бизнес логику. А без такого подхода Hibernate не будет работать.
Т.е. мне сама идея ORM, где схема данных матчится 1 к 1 в бизнес сущности приложения, кажется нарушением DDD, Clean Architecture и проч.
JOOQ — не замена Hibernate. Они решают разные проблемы