NoSQL Schemaless данные и статически типизированный язык - PullRequest
10 голосов
/ 04 ноября 2011

Одним из ключевых преимуществ таких хранилищ данных NoSQL, как MongoDB, является отсутствие схем.С динамически типизированными языками это кажется естественным соответствием.Вы можете получать произвольные входные данные JSON, выполнять бизнес-логику с известными полями и сохранять все целиком без предварительного определения объекта.

Что если ваш выбор языка ограничен статически типизированным, скажем, Java?Как я могу достичь того же уровня гибкости?

Типичный поток данных, подобный следующему:

  1. JSON Input
  2. Сериализация в Java Object для выполнения бизнес-логики
  3. Десериализация в BSON для сохраненияв Mongo

, где необходима сериализация на шаг объекта, поскольку вы хотите выполнять бизнес-логику с POJO, а не со строками JSON.Однако прежде чем я смогу сериализовать ввод в объекты, я должен сначала определить его.Что если вход содержит дополнительные поля, неопределенные в объекте?Хотя они могут и не использоваться в бизнес-логике, я все же хочу иметь возможность их сохранить.Мне кажется, реализации, где неопределенные поля помещаются в карту, но я не уверен, что это лучший подход.Например, неопределенные поля могут быть сложными объектами.

Ответы [ 3 ]

8 голосов
/ 22 ноября 2011

данные без схемы не обязательно означают данные без структуры ;поля обычно известны заранее, и поверх них можно применить некоторый типобезопасный шаблон, чтобы избежать анти-паттерна Magic Container Но это не всегда так.Иногда ключи вводятся пользователем и не могут быть известны заранее.

Я несколько раз использовал шаблон объекта роли , чтобы придать динамическую структуру согласованности.Я думаю, что он хорошо подходит для обоих случаев.

Шаблон объекта роли определяет способ доступа к различным представлениям объекта.Каноническим примером является Пользователь, который может принимать несколько ролей , таких как Клиент, Продавец и Продавец.Каждое из этих представлений имеет различные операции, которые оно может выполнять, и к которым можно получить доступ из любого другого представления.Общие поля обычно доступны на уровне интерфейса (особенно userId(), или в вашем случае toJson()).

Вот пример использования шаблона:

public void displayPage(User user) {
    display(user.getName());

    if (user.hasView(Customer.class))
       displayShoppingCart(user.getView(Customer.class);

    if (user.hasView(Seller.class))
       displayProducts(user.getView(Seller.class));
}

В случаеданных с известной структурой, вы можете иметь несколько представлений, объединяющих различные наборы ключей в единое целое.Эти различные представления могут считывать данные json при построении.

В случае данных с динамической структурой авторитетный RawDataView может иметь данные в динамической форме (т. Е. Волшебный контейнер как HashMap<String, Object>).Это может быть использовано для запроса динамических данных.В то же время безопасные для типов оболочки могут быть созданы лениво и могут делегироваться в RawDataView для облегчения чтения / поддержки программы:

 public class Customer implements User {
     private final RawDataView data;
     public CustomerView(UserView source) {
         this.data = source.getView(RawDataView.class);
     }

     // All User views must specify this
     @Override
     public long id() {
         return data.getId();
     }

     @Override
     public <T extends UserView> T getView(Class<T> view) {
         // construct or look up view
     }

     @Override
     public Json toJson() {
         return data.toJson();
     }


     //
     // Specific to Customer
     //
     public List<Item> shoppingCart() {
         List<Item> items = (List<Item>) data.getValue("items", List.class); 
     }

     // etc....
 }

Я добился успеха с обоими этими подходами.Вот несколько дополнительных указателей, которые я обнаружил на этом пути:

  • Как можно больше статической структуры структуры ваших данных.Это делает вещи намного проще в обслуживании.Мне пришлось нарушить это правило и использовать подход RawDataView при работе на устаревшей системе.Возможно, вам также придется разбить его динамически вводимыми данными пользователя, как указано выше.В этом случае используйте соглашение для нединамических имен полей, таких как начальное подчеркивание (_userId)
  • Реализовано equals() и hashcode()так что user.getView(A.class).equals(user.getView(B.class)) всегда верно для одного и того же пользователя.
  • Имеет класс UserCore , который выполняет всю тяжелую работу с обычным кодом, например, создает представления;выполнение общих операций (например, toJson()), возвращая общие поля (например, userId());и реализации equals() и hashcode().Пусть все представления делегируются этому базовому объекту
  • Имеют AbstractUserView , который делегирует UserCore и реализует equals () и hashcode ()
  • Используйте безопасный для типов гетерогенный контейнер(например, ClassToInstanceMap ) создание / кэширование представлений.
  • Разрешить запрашивать существование представления.Это можно сделать с помощью метода hasView() или с помощью getView return Optional<T>
1 голос
/ 20 ноября 2011

Вы всегда можете иметь класс, который обеспечивает оба:

  • легкий доступ к атрибутам, о которых вы знаете, и необязательные запасные варианты для более старых форматов (например, он может возвращать «имя», если оно существует, или более старый регистр «name.first» + «name.last», если этого не происходит) (или похожий сценарий))
  • легкий доступ к неизвестным элементам, имитирующим интерфейс карты

Независимо от того, проводите ли вы полную проверку или нет, разрешаете ли вы дополнительные неопределенные атрибуты или нет, зависит от того, чего вы хотите достичь. Но я думаю, что создание абстракции, которая позволяет вам получить доступ к данным, является лучшим решением.

Надеемся, что со временем вы доберетесь до стадии, когда ваша схема в значительной степени стабильна, и больше не нужно напрямую связываться с атрибутами.

0 голосов
/ 04 ноября 2011

Это не очень хорошо решается в Java из-за отсутствия динамических типов. Одним из способов решения этой проблемы является использование Карт.

Map

Объект снова может быть картой объектов.

Это не элегантный способ, но работает на Java. Пример: библиотека SnakeYaml для YAML позволяет обходить таким образом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...