Контекст
Grails позволяет очень легко сохраняться и находить вещи, используя доменные классы . Он использует GORM (Grails ‘Object Relational Mapping) под капотом, который по умолчанию использует Hibernate для сопоставления классов домена с таблицами в базе данных. Мощный материал и позволяет легко вставать и бегать очень быстро!
Создание нового приложения в соответствии с так называемыми «лучшими практиками» из блогов, подобных этим & # 55357; & # 56898; и «идиоматический путь Grails», описанный в документах и учебных пособиях, работает в начале, но всегда есть переломный момент — когда приложение выросло до разумного размера — когда нужно начинать следовать другой, возможно, менее Grailsey, стратегии ,
Так что же может пойти не так с использованием динамических искателей в доменном классе?
Возможно, вы можете узнать следующее описание создания одного из ваших ранних приложений Grails?
День 1: Жизнь прекрасна
Вы создаете свои основные классы домена.
Я взял примеры классов предметной области, любезно предоставленные Джеффом Брауном и Грэмом Роше «Классическое руководство по Grails » — классика!
1
2
3
4
|
grails create-domain-class Album grails create-domain-class Song grails create-domain-class Artist ... |
Потрясающие.
Простой HomepageController
создан, чтобы показать что-то на вашей домашней странице. Он должен возвращать коллекцию, скажем, альбомов для определенного исполнителя, и, следовательно, динамический поиск , очевидно, является самым простым способом сделать это.
1
2
3
4
5
|
class HomepageController { def list(Artist artist) { [albums: Album.findAllByArtist(artist)] } } |
Вы и ваши товарищи по команде поражаются волшебству ! Производительность не имеет себе равных . Вы можете запросить почти что-нибудь подобное. Вы нашли лучших друзей на всю жизнь, и в течение следующих дней команда просматривает другие истории пользователей домашней страницы, как будто завтра не наступит.
Перемотка вперед на 14 дней.
День 14: DOA
Домашняя страница останавливается каждый раз, когда ее открывает любой клиент. Спектакль покинул здание несколько дней назад. В воздухе все еще есть магия, но она работает против тебя: ты не понимаешь, что происходит.
Наверняка все добавленные виджеты, графики панели инструментов и отчеты на главной странице не имеют к этому никакого отношения?
Вы включаете ведение журнала запросов Hibernate и пытаетесь увидеть, какие запросы происходят при повторном открытии домашней страницы.
Консоль отображает лавину SQL, пока домашняя страница, наконец, не завершит загрузку.
1
2
3
4
5
6
7
8
9
|
Hibernate: select this_. id as id1_2_0_, this_.version as version2_2_0_, this_.name as name3_2_0_ from artist this_ where this_.name=? limit ? Hibernate: select this_. id as id1_0_0_, this_.version as version2_0_0_, this_.artist_id as artist_i3_0_0_, this_.title as title4_0_0_ from album this_ where this_.artist_id=? Hibernate: select this_. id as id1_2_0_, this_.version as version2_2_0_, this_.name as name3_2_0_ from artist this_ where (this_.name=?) limit ? Hibernate: select this_. id as id1_0_0_, this_.version as version2_0_0_, this_.artist_id as artist_i3_0_0_, this_.title as title4_0_0_ from album this_ where (this_.artist_id=?) Hibernate: select this_. id as id1_2_0_, this_.version as version2_2_0_, this_.name as name3_2_0_ from artist this_ where (this_.name=?) Hibernate: select this_. id as id1_0_0_, this_.version as version2_0_0_, this_.artist_id as artist_i3_0_0_, this_.title as title4_0_0_ from album this_ where this_.artist_id in (?) limit ? Hibernate: select this_. id as id1_2_0_, this_.version as version2_2_0_, this_.name as name3_2_0_ from artist this_ Hibernate: select this_. id as id1_0_0_, this_.version as version2_0_0_, this_.artist_id as artist_i3_0_0_, this_.title as title4_0_0_ from album this_ where this_.artist_id=? etc etc etc |
Это куча похожих запросов, забивающих вашу консоль.
Кроме того, на производстве это тратит драгоценное время вашего клиента, которое теперь делает покупки в другом месте на более быстром веб-сайте конкурента .
Объяснение
Давайте углубимся в код Grails, потому что где-то что-то должно быть причиной этого.
Вы помните тот простой HomepageController
вы лично начинали в первый день, когда жизнь была прекрасной?
1
2
3
4
5
|
class HomepageController { def list(Artist artist) { [albums: Album.findAllByArtist(artist)] } } |
Ну, его там больше нет, по крайней мере, простая версия. Для различных таблиц и диаграмм на домашней странице, материал теперь разделен на несколько классов, таких как сервисы построения представлений и библиотеки тегов.
В журнале SQL выглядит так, что каждый запрос почти запрашивает одну и ту же информацию, но вы не можете больше это сказать . Просматривая код, вы можете обнаружить около 18 различных мест, где разработчики копировали динамические искатели, аналогичные оригинальным.
В одном месте:
1
2
|
Artist artist = Artist.findByName(artistName) Album.findAllByArtist(artist) |
(«Хорошо, не элегантно, но это работает»)
В другом месте:
1
|
Album.findAllWhere(artist: Artist.findWhere(name: artistName)) |
(«Конечно, похоже, делает то же самое, но в качестве одной строки»)
Снова в другом месте:
1
2
|
def artists = Artist.findAllWhere(name: artistName) Album.findAllByArtistInList(artists, [max: 1 ]) |
(«Что, почему? И зачем ограничивать результаты только 1 здесь?»)
Снова в другом месте:
1
2
3
4
5
|
Album.where { artist == Artist.list().find { a -> a.name == artistName } }.findAll() |
( «NOOOooo!»)
Шаблон
Это случалось со мной несколько раз. Я и команда ценим простоту и силу Grails. Таким образом, следуя общепринятой конфигурации и будучи неуместными в нашей работе по разработке Grails, мы можем выполнить поставку быстро.
Случай с альбомом и исполнителями динамического поиска выше часто не далек от реальности. Даже в одной команде разные разработчики в конечном итоге будут везде использовать то, что я называю «локально оптимизированными динамическими поисковиками» . Почему?
- Их слишком легко начать с
- Их слишком легко продолжать использовать
Практически не требуется никаких шаблонов для написания того, какой тип запроса вам нужен, создания конструкций вокруг них или поиска коллабораторов для прохождения: все, что вам нужно , так же просто, как findBy*
и вы получаете класс домена, на котором находитесь.
Динамические искатели очень удобочитаемы (и легко тестируются), поэтому члены команды
- скопируйте и адаптируйте их для немного другого сценария (здесь нам нужна сортировка, здесь нам нужен только один вместо списка)
- попытаться быть умнее , написав запрос более «умным» способом (иногда приводящим к обратному)
- даже не знаю о каких-либо существующих применениях в приложении и просто, с помощью документации или руководителя, пытаюсь выполнить работу с нуля (так они думают)
Недостатки будут проявляться по мере роста приложения:
- Каждый запрос имеет разную структуру, поэтому по внешнему виду вы не можете определить, есть ли у вас на самом деле 18 различных запросов или только 2 . Любые кэши запросов (например, Hibernate), возможно, либо менее эффективны, либо фактически не нужны, но теперь просто занимают память.
- Тщательно созданные индексы , созданные администратором базы данных в первый день, в конце большинства запросов больше не используются на 100% из-за произвольного присутствия и порядка столбцов в предложении WHERE. Любая оптимизация базы данных становится бесполезной , и, возможно, вместо этого вводится медленное сканирование таблиц.
- Любой рефакторинг к запросу «найти альбомы для исполнителя» должен быть тщательно обработан вручную во всех 18 различных местах. Это вызывает у всех головные боли. И ошибки.
Что мы можем с этим поделать?
Может быть, очевидно, но держать его сухим
Централизуйте вещи. Точно так же, как вы делаете, когда код дублируется во 2-й или 3-й раз, вы находите хорошее место в своем приложении, где его может найти каждый разработчик. Вам просто нужно беспокоиться об этом одном месте.
Во-первых, лучшее, что мы можем сделать в Grails, — это создать такое центральное место , например, создав обычную службу Grails, такую как, например, AlbumService
.
1
2
|
grails create-service Album | Created grails-app /services/example/AlbumService .groovy |
Во-вторых, проанализируйте все ваши запросы «найти альбомы для исполнителя» и попробуйте сойтись только на 1 или 2, которые должны быть помещены в новый сервис.
Попробуйте найти разумные имена для некоторых новых методов, которые в некоторых случаях могут быть просто именами исходного динамического поиска, такого как findAllByArtist
:
1
2
3
4
5
6
7
|
@Transactional class AlbumService { Album findAllByArtist(Artist artist) { Album.findAllByArtist(artist) } } |
В-третьих, замените все случаи использования вызовом на новый сервис. Помните наш контроллер домашней страницы? Это изменится так:
1
2
3
4
5
6
7
8
|
class HomepageController { AlbumService albumService def list(Artist artist) { [albums: albumService.findAllByArtist(artist)] } } |
Обновив все 18 отдельных динамических видоискателей путем вызова недавно созданного сервиса, вы даете себе меньше, а не больше обслуживания и свободу внутренне реорганизовать методы в новом сервисе в более производительные варианты.
При этом вам, возможно, придется изменить все виды модульных тестов (таких как, например, HomepageControllerSpec
, если они у вас есть): где вы впервые использовали хранилище в памяти для фактического тестирования динамических искателей на классах домена, в этих тестах теперь Вам просто нужно убедиться, что вызывается правильный сервис (например, AlbumService
).
Только тест для AlbumService
должен иметь дело с тестированием запросов сейчас.
В конце концов, AlbumService
может содержать только 1 или 2 результирующих запроса, которые используются из любого необходимого места в кодовой базе, но высоко оптимизированы для скорости, используя все виды функций, таких как кэширование, индексы базы данных и т. Д. Устроить нас в беспорядок должно быть в прошлом. Теперь мы можем создавать другие проблемы & # 55357; & # 56898;
Динамические искатели — это здорово!
Если вы до сих пор не уверены, защищаю ли я abanondon динамические искатели? Нет, конечно, динамические искатели — это лучшее, что когда-либо происходило со времен нарезанной пиццы! Начать новый проект или прототип с ними — отлично! И сделайте умную вещь очень скоро и вместе с командой _ разместите свои запросы вместе _ в организованном месте.
Самый важный урок, который я усвоил: если все запросы находятся в аккуратном, четко определенном месте, чтобы все могли его увидеть и перейти , наиболее вероятно, что члены команды по умолчанию будут придерживаться базового человеческого поведения — разработка программного обеспечения иногда является просто психологией — следуйте по заданному пути и раскрасьте хорошо определенные линии этой части кодовой базы. Понимание того, что уже есть, заставляет людей задуматься о том, стоит ли вводить совершенно новый запрос по сравнению с изменением одного из существующих.
Ссылка: | Антишаблон Grails: локально оптимизированные динамические искатели Везде от нашего партнера по JCG Теда Винке в блоге |