Совсем недавно у меня было требование хранить неструктурированные данные JSON, которые возвращались из веб-службы. Веб-сервис возвращал различные футбольные команды со всего мира. Среди данных, содержащихся в большинстве футбольных команд, был список футболистов, которые были частью команды. У некоторых из команд было 12 игроков, у некоторых было 20, у некоторых даже было больше 20. У игроков был свой атрибут, некоторые было легко предсказать, а некоторые невозможно. Для всей структуры данных единственным атрибутом, который, как я знал, определенно будет возвращаться, было имя команды. После этого все зависело от каждой команды.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
{ "teams": [{ "teamname":"Kung fu pirates", "founded":1962, "players": [ {"name": "Robbie Fowler", "age": 56}, {"name": "Larry David", "age": 55} ... ]}, { "teamname":"Climate change observers", "founded":1942, "players": [ {"name": "Jim Carrey", "age": 26}, {"name": "Carl Craig", "age": 35} ... ]}, ... ]} |
Есть несколько разных способов хранения этих данных. Я решил пойти на MongoDB. Основные причины:
- Я хотел сохранить данные в максимально близком формате к ответам JSON, которые я получал от веб-службы. Это будет означать меньше кода, меньше ошибок, меньше хлопот.
- Я хотел что-то, что имело низкую кривую обучения, хорошую документацию и хорошую поддержку отрасли (потоки stackoverflow, сообщения в блогах и т. Д.)
- Кое-что, у которого был плагин Grails, который был задокументирован, имел шаг и выглядел так, как будто его поддерживали
- Такие функции, как текстовые stemming, были приятны. Некоторая поддержка была бы хорошей, но это не должно было сокращать возраст.
- Поддержал бы хорошие средства поиска JSON, индексацию и т. Д.
MongoDB поставил галочку во всех клетках. Вот так у меня все получилось. После того, как я установил MongoDB в соответствии с инструкциями Mongo и плагином MongoDB Grails , пришло время написать некоторый код. Теперь вот аккуратная часть, почти не было кода. Я создал объект домена для Команды.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
class Team implements Serializable { static mapWith = "mongo" static constraints = { } static mapping = { teamname index: true } String teamname List players static embedded = ['players']} |
Относительно объекта домена Team:
- Первое, что я хотел сказать об объекте домена Team, это то, что мне даже не нужно было его создавать. Причина, по которой я использовал этот подход, заключалась в том, что я мог использовать API- интерфейсы в стиле GORM, такие как Team.find (), если бы захотел.
- Игроки это просто список объектов. Я не удосужился создать объект Player. Мне нравится идея всегда обеспечивать, чтобы игроки для команды всегда были в структуре данных List, но я не видел необходимости вводить что-либо еще.
- Игроки помечены как встроенные . Это означает, что команда и игроки хранятся в единой денормализованной структуре данных. Это позволяет, помимо прочего, получать и обрабатывать данные группы в одной операции базы данных.
- Я пометил название команды в качестве индекса.
- Я пометил объект домена как:
1
staticmapWith ="mongo"Это означает, что, если я также использовал другое решение для персистентности с моим GORM (postgres, MySQL и т. Д.), Я говорю GORM, что этот класс домена Team предназначен только для Mongo — держите свои реляционные руки подальше от него. Смотрите здесь для информации.
Примечание. Это хорошее напоминание о том, что GORM — это более высокий уровень абстракции, чем спящий. Возможно иметь объект GORM, который не использует hibernate, но вместо этого отправляется в хранилище NoSQL и не приближается к hibernate.
Вы заметите, что в JSON есть такие атрибуты команды, как found , которые не были явно объявлены в классе Team. Именно здесь Groovy и NoSQL очень хорошо играют друг с другом. Мы можем использовать некоторые возможности метапрограммирования Groovy для динамического добавления атрибутов к объекту домена Team.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
private List importTeams(int page) { def rs = restClient.get("teams") // invoke web service List teams = rs.responseData.teams.collect { teamResponse -> Team team = new Team(teamname: teamResponse.teamname) team.save(); // Save is needed to dynamically add the attribute teamname.each {key, value -> team["$key"] = value } teamname.save(); // We need the second save to ensure the variants get saved. return teamname } log.info("importTeams(),teams=teams); teams} |
Итак, основные моменты в нашем методе importTeams ():
- После получения ответа JSON мы запускаем функцию сбора данных в массиве groups. Это создаст объекты домена Team.
- Мы используем метапрограммирование для динамического добавления любого атрибута, который возвращается в структуре команды JSON, к объекту Team. Примечание. Сначала нужно вызвать save (), чтобы иметь возможность динамически добавлять атрибуты, объявленные в объекте домена Team, в объект домена Team. Мы также должны снова вызвать save (), чтобы гарантировать, что атрибуты, объявленные в объекте домена Team, гарантируют, что они сохранены. Это может измениться в будущих версиях плагина MongoDB, но это то, что я должен был сделать, чтобы заставить его работать (я использовал плагин MongoDB версии 3.0.1)
Так что же дальше? Напишите несколько запросов. Итак, два варианта здесь. Во-первых, вы можете использовать динамические поиски и запросы критериев с GORM благодаря плагину MongoDB. Но я этого не делал. Почему? Я хотел написать запросы как можно ближе к тому, как они должны быть написаны на монго. Для этого было несколько причин:
- Утечка абстракции здесь неизбежна. Рано или поздно вам придется написать запрос, который GORM не очень хорошо выполнит. Лучше подойти к этому головы.
- Я хотел сначала иметь возможность выполнять запросы в консоли Mongo, проверить планы объяснения, если мне нужно, а затем использовать тот же запрос в своем коде. Проще сделать это, если я напишу запрос напрямую, не беспокоясь о том, что собирается делать GORM.
Общий формат запросов:
|
1
|
teams = Team.collection.find(queryMap) // where queryMap is a map of fields and the various values you are searching for. |
Хорошо, несколько примеров запросов …
|
1
2
3
4
5
|
Team.collection.find(["teamname":"hicks"]) // Find a team name hicksTeam.collection.find(["teamname":"hicks", "players.name": "Robbie Fowler"] // As above but also has Robbie FowlerTeam.collection.find(["players.name": "Robbie Fowler"] // Any teams that has a Robbie FowlerTeam.collection.find(["teamname":"hicks", "players.name": "Robbie Fowler", {"players.$":1}] // Returns matching player onlyTeam.collection.find(["teamname":"/ick/"]) // Match on the regular expression /ick/, i.e. any team that contains text ick. |
Что-нибудь еще? Да, конечно. Я хотел подключиться к экземпляру Mongo на своей машине в процессе разработки, но к машине Mongo на выделенном сервере в других средах (CI, stage, production). Для этого я обновил свой DataSource.groovy как:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
environments { development { grails { mongo { host = "localhost" port = 27017 username = "test" password = "test" databaseName = "mydb" } } dataSource { dbCreate = "create-drop" // one of 'create', 'create-drop', 'update', 'validate', '' url = "jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000" } } ci { println("In bamboo environment") grails { mongo { host = "10.157.192.99" port = 27017 username = "shop" password = "shop" databaseName = "tony" } } dataSource { dbCreate = "create-drop" // one of 'create', 'create-drop', 'update', 'validate', '' url = "jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000" } }} |
Вы увидите, что я настроил несколько источников данных (MongoDB и PostGres). Я не защищаю использование MongoDB и реляционной базы данных, просто указываю, что это возможно. Другое дело, что конфигурация MongoDB всегда находится под: grails {mongo {
Хорошо, это простой вводный пост, я постараюсь опубликовать что-то более сложное в ближайшее время. До следующего раза, береги себя.
| Ссылка: | MongoDB и Grails от нашего партнера по JCG Алекса Стейвли в техническом блоге Дублина . |