После
предыдущего поста я, признаться, решил уже, что хоть проект и в ужасном состоянии, но мы достигли дна и потихоньку все будет налаживаться. Начальство опомнится, что уже два спринта мы ничего не производим, программистские рефакторинги начнут наконец упрощать и повышать надежность, и т.д., и т.п. О, как я ошибался!
Начнем издалека. Про то, что народ много знает, я в предыдущем посте, конечно, приукрасил. Технологии мы познавали в процессе. Итак, изначально были hibernate и spring. Hibernate пропатчили, чтобы научить хранить в базе объекты, меняющиеся во времени (т.н. версии). Архитектор, придумавший это, то ли сошел с ума, то ли попал в дурку - меня тогда еще не было, жаль, не встретились. Ни транзакций, ни управления hibernate sessions не было, точнее, они были написаны, и все думали, что они есть, хотя они не работали. Народ ловил LazyInitializationException, тихонько матерился и дописывал все больше джойнов в метод loadDetails(id). Чтобы, ткскзть, данные-то вытащились сразу и все. Ну, например:
protected DetachedCriteria applyAdditionalJoins(DetachedCriteria criteria) {
return criteria
// TODO (dao) DRY (see applySearchEntity)
.createAlias("provider", "pa")
.createAlias("owner", "owner")
.createAlias("subscriber", "sa")
.createAlias("sa.document", "sa.document")
.createAlias("services", "services", Criteria.LEFT_JOIN)
.createAlias("services.templateService", "templateService", Criteria.LEFT_JOIN)
.createAlias("templateService.serviceName", "serviceName", Criteria.LEFT_JOIN)
.createAlias("services.inactivityPeriods", "inactivityPeriods", Criteria.LEFT_JOIN)
.createAlias("registrations.person", "person", Criteria.LEFT_JOIN)
.createAlias("person.privileges", "privileges", Criteria.LEFT_JOIN)
.createAlias("privileges.privilegeCategory", "privilegeCategory", Criteria.LEFT_JOIN)
.createAlias("registrations.person.document", "document", Criteria.LEFT_JOIN)
.createAlias("registrations.registrationType", "type", Criteria.LEFT_JOIN)
.createAlias("registrations.departurePeriods", "departurePeriods", Criteria.LEFT_JOIN)
.createAlias("address", "address", Criteria.LEFT_JOIN)
.createAlias("address.building", "building", Criteria.LEFT_JOIN)
.createAlias("building.street", "street", Criteria.LEFT_JOIN)
.createAlias("street.city", "city", Criteria.LEFT_JOIN)
.createAlias("city.district", "district", Criteria.LEFT_JOIN)
.createAlias("district.area", "area", Criteria.LEFT_JOIN)
.setFetchMode("services", FetchMode.JOIN)
.setFetchMode("services.templateService", FetchMode.JOIN)
.setFetchMode("templateService.serviceName", FetchMode.JOIN)
.setFetchMode("registrations", FetchMode.JOIN)
.setFetchMode("services.inactivityPeriods", FetchMode.JOIN)
.setFetchMode("registrations.departurePeriods", FetchMode.JOIN);
}
Везде, где нужна была сущность, мы вытаскивали и все ее связи до N-го колена. А что, лучше лишнего вынуть, чем нужного не вынуть. Радует также комментарий про DRY. Если вам интересно, что написано в applySearchEntity, то там то же самое и симметричный комментарий про DRY. Понятно, что стоило Васе добавить/убрать джоин, как что-нибудь отваливалось у Пети. Отстрел таких вот багов был основным времяпрепровождением народа в то время. Ладно. Сессии с транзакциями я починил, и стало внутри @Transactional методов хорошо и lazy. Постепенно пришло осознание того, что вся глубина замысла свалившего архитектора не укладывается в наши скромные по меркам его амбиций возможности. А в том виде, что он оставил, ничего не работало, так что от патченного hibernate мы тихонько отказались и реализовали версионирование стандартными средствами.
Все хорошо, и мы даже успели несколько задач для бизнеса сделать. Но на коде все еще сохранился отпечаток того безумия, который магическим образом подчинял себе всех, кто этот код прочитал. В какой-то момент захотели мы SessionPerRequest. Замечательный типовой прием (©
rainman_rocks), спору нет. Но тут вдруг полезло. Один этот SessionPerRequest включил и недолго думая закоммитил сразу в транк. Тестеры в панике, они не могут найти работающей страницы. Начали разбираться. Оказалось, у нас сохраняемый объект, он детачнутый, в каких-то проверках перед update достается в сессию, и оба-на: a different object with the same identifier, не могу сохранить, я не я и привет от хибернейта. Казалось бы, ну чини! И цель благородная, и обществу польза. Но наш герой решает, что чинить - дело не барское. Назад отступать тоже как-то не в его идеалах. И делает вызов update в отдельной транзакции. От греха подальше. Посовещавшись с архитектором. Я, к горю своему, тоже участвовал в этом обсуждении, хотя и стараюсь сидеть в наушниках и беседы за архитектуру игнорировать - тяжело я переношу все эти нервные потрясения последнего времени. Предложения разобраться, починить, изучить, откатить, были отвергнуты: ся сделаем начальника всё хоросо будет дазе не заметись. В конце концов, спринт короткий, а есть ведь и более важные задачи™ (переложить ресурс бандлы из одного файла в другой, например - но об этом позже). Собственно, этот случай показателен в двух смыслах: а) у нас начисто отсутствует какое-либо стремление к стабильности кода на всех уровнях, от разработчиков до архитектора, другими словами, костыли и ямочный ремонт - рекомендуемая линия партии, и б) печально, что библиотека таки оказалась сильнее разработчика и превратилась из инструмента, сокращающего путь к цели, в помеху на этом пути. Хоть лично я никогда не считал хибернейт технологией сильно сложной, кажется, он действительно может стать непобедимым монстром, если проигнорировать при знакомстве с ним документацию. Хотя, блин, ну казалось бы. Чай, не Глобус Тулкит. И теперь либо отказываться в пользу чего-то более простого, либо всех собирать и читать доку вслух. А что делать? Жду проблем с освоением log4j.
В принципе, на один спринт мне хватило бы потрясений. Но интересно же, в пользу чего решили отказываться от починки SessionPerRequest. Это должна была быть какая-то важная бизнес-задача, да? Не на тех напали. Я
уже писал как-то, что у нас работа с базой идет через два слоя, Dao и Service. По паре на каждую сущность. Выглядит это примерно так (Service):
public class CompanyServiceImpl extends LongIdServiceImpl implements CompanyService {
public CompanyServiceImpl(CompanyDAO dao) {
super(dao);
}
public List findByName(String name) {
return getDao().findByName(name);
}
public List findBySimilarName(String name) {
return getDao().findBySimilarName(name);
}
public List findByOwner(Company owner) {
return getDao().findByOwner(owner);
}
public List findByName(String name, CompanyFilterEnum filterItem) {
return getDao().findByName(name, filterItem);
}
public List findBySimilarName(String name, CompanyFilterEnum filterItem) {
return getDao().findBySimilarName(name, filterItem);
}
public List findByAddress(Address address) {
return getDao().findByAddress(address);
}
public List getSecurityPage(EntityFilter filter) {
return getDao().getSecurityPage(filter);
}
public int getSecurityCount(EntityFilter filter) {
return getDao().getSecurityCount(filter);
}
public Company findByCode(String code) {
return getDao().findByCode(code);
}
}
Ну и считается, что нужно везде использовать Service, а не Dao. А вдруг где-то это не так? Вдруг какой-то негодяй вызвал Dao в обход Service? Проверить код? Да в этой куче черт ногу сломит. Разъяснить разработчикам? Все равно ведь в обход полезут. В конце концов, слишком легко это все. Решили написать Чекер™. Чтоб рушил все нахрен, если какой-то партизан не за тот провод дернул. Причем как бы с благой целью повышения стабильности, только видится мне, что и в Чекере™ баги, и ситуации неоднозначные будут. Бог с ним, тут интересно другое. В МСС то же самое было, но с секьюрити, я хорошо помню: как сделать такую систему, чтобы другой, тупой разработчик, то есть не я, не смог написать некорректный код. Этот симптом, он даже не про то, что неладно что-то в Датском королевстве, он про то, что все пиздец закрывайте лучше лавочку. Началась война. Верить нельзя никому. Каждый считает себя умнее другого. Менять можно только свой собственный код, либо писать новый. Победит тот, кто первый посадит соперника в клетку. И вот тогда заживееем.......
Я как бы всегда считал себя человеком сильно спокойным, но что-то в последнее время то на смех истерический сорвусь, то за голову хватаюсь, то проклятия вполголоса бормочу. Дайте мне, что ли, другой какой-нибудь глобус. Только не Глобус Тулкит.