Мне не повезло из-за необходимости работать в приложении, где вызовы службы выполнялись с использованием простого ООП.Я ненавидел это и собираюсь рассказать вам, почему:
Во-первых, ваш пример несколько упрощен, так как обычно граница транзакции не связана с одним взаимодействием с базой данных, а вокруг всего вызова бизнес-службы.Итак, давайте для примера рассмотрим следующий метод:
@Transactional
public Employee hire(Person p, Employee manager, BigDecimal salary) {
// implementation omitted
}
, который вызывается с помощью
Employee hired = service.hire(candidate, manager, agreedOnSalary);
В вашем случае это будет:
class HireAction implements Callable<Employee> {
private final Person person;
private final Employee manager;
private final BigDecimal salary;
public HireAction(Person person, Employee manager, BigDecimal salary) {
this.person = person;
this.manager = manager;
this.salary = salary;
}
@Override
public Employee call() throws Exception {
// implementation omitted
}
}
и вызывается
Employee hired = session.doInTransaction(new HireAction(candidate, manager, agreedOnSalary));
Конструктор необходим для обеспечения назначения всех параметров (поэтому компилятор может пожаловаться, если параметр добавлен в метод без обновления вызывающей стороны).
Второй подход уступает по следующим причинам:
- Он нарушает DRY, так как каждый параметр упоминается 3 раза, а тип возврата дважды.В частности, куда вы помещаете JavaDoc для параметров?Вы тоже это копируете?
Сложно группировать связанные сервисные операции в сервисе и обмениваться между ними кодом.Да, вы можете поместить все связанные операции в один и тот же пакет и иметь общий суперкласс для совместного использования кода, но опять же, это более многословно, чем подход АОП, заключающийся в простом размещении их в одном классе.Или вы можете сделать такие сумасшедшие вещи, как:
class HRService {
class HireAction {
// impl omitted
}
class FireAction {
// impl omitted
}
}
и вызвать его, используя
Employee emp = session.doInTransaction(new HRService().new HireAction(candidate, manager, salary));
Программист может забыть начать транзакцию:
Employee e = new HireAction(candidate, manager, salary).call();
или запустите транзакцию в неправильном сеансе / базе данных.Транзакции и бизнес-логика - это разные проблемы, которые обычно решаются разными разработчиками и, следовательно, должны быть разделены.
Подводя итог, можно сказать, что простой ООП-подход более многословен и подвержен ошибкам, что приводит к увеличению затрат как на разработку, так и на обслуживание.
Наконец, о вашей критике АОП:
Он добавляет «магию» к коду в виде непрозрачной сложности, которую очень трудно отладить,
Сложность всегда трудно отладить, независимо от источника,Я помню некоторые сеансы отладки с исходным кодом гибернации, чье разумное использование шаблона команд сделало не менее трудным поиск кода, который имел значение.
Наличие перехватчика AOP может быть неочевидным (хотя, если метод аннотирован @Transactional
, я бы посчитал это очевидным), поэтому AOP следует использовать экономно (что не является проблемой)так как количество сквозных задач в проекте обычно довольно мало).
и может очень затруднить отладку объектно-ориентированного кода, на который он влияет.
Как так?Я не вижу проблемы, но если бы вы описали ее, я мог бы, вероятно, сказать вам, как я ее решаю / избегаю.
Мне кажется, что это в основном не нужно, и (что еще хуже)часто используется для того, чтобы избежать необходимости хорошего дизайна или для компенсации предыдущего плохого дизайна.
Любая технология может быть использована плохо.Важно то, насколько сложно использовать хорошо, и что мы можем сделать, если мы это сделаем.