NoSQL Schemaless数据和静态类型语言

时间:2011-11-04 05:38:00

标签: java mongodb nosql

像MongoDB这样的NoSQL数据存储的一个主要优点是它们是无模式的。使用动态类型语言,这似乎很自然。您可以接收一些任意JSON输入,在已知字段上执行业务逻辑,并在不必先定义对象的情况下保留整个事物。

如果您选择的语言仅限于静态类型,比如Java,该怎么办?我怎样才能达到同样的灵活性?

典型的数据流如下:

  1. JSON输入
  2. 序列化为Java对象以执行业务逻辑
  3. 反序列化为BSON以持续存在于Mongo中
  4. 由于您希望使用POJO而不是JSON字符串执行业务逻辑,因此需要序列化到对象步骤。但是,在我将输入序列化为对象之前,我必须首先定义它。如果输入包含对象中未定义的其他字段,该怎么办?虽然它们可能不会用于业务逻辑,但我仍然希望能够坚持它们。我似乎已经将未定义的字段放入地图中,但我不确定这是否是最好的方法。例如,未定义的字段也可能是复杂的对象。

3 个答案:

答案 0 :(得分:8)

Schemaless 数据并不一定意味着无结构数据;这些字段通常是预先知道的,并且可以在其上应用一些类型安全的模式以避免Magic Container anti-pattern但是情况并非总是如此。有时密钥是由用户输入的,无法事先知道。

我多次使用Role Object Pattern来表示动态结构的连贯性。我认为它适用于这两种情况。

角色对象模式定义了一种访问对象的不同视图的方法。规范示例是可以承担多个角色的用户,例如客户,供应商和卖家。这些视图中的每一个都可以执行不同的操作,并且可以从任何其他视图访问。通常的字段通常在界面级别提供(尤其是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可以使用其动态形式的数据(即Magic Container,如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返回Optional<T>
  • 来完成

答案 1 :(得分:1)

您可以随时拥有一个同时提供以下内容的课程:

  • 轻松访问您知道的属性和可选的后备案例到旧格式(例如,如果存在,则可以返回“name”,如果不存在,则返回“name.first”+“name.last”的旧案例(或某些类似的情况))
  • 轻松访问模拟地图界面的未知元素

是否进行完整验证,是否允许额外的未定义属性取决于您想要实现的目标。但我认为创建一个允许您以任何方式访问数据的抽象是最佳解决方案。

希望随着时间的推移,你将进入你的架构非常稳定的阶段,不再需要直接弄乱属性了。

答案 2 :(得分:0)

由于缺乏动态类型,在Java中没有很好地解决这个问题。可以解决的一种方法是使用地图。

Map

对象可以再次是对象的Map。

这不是一种优雅的方式,但适用于Java。例如:YAML的SnakeYaml库允许以这种方式遍历。