抽象出通用的操作行为,让变化的受操作数据自由变更

    上个迭代将公司的菜单服务重构了一遍,持久化层从mysql迁到了mongodb,尽管开始我是不太情愿干这件事情的,因为原本的mysql表设计即使不能叫完美吧,但就目前的业务需求以及可预见的未来变更,基本上都是可以cover的,很多时候我们写代码总是考虑将来的通用、复用,甚至上升到做平台,但往往最终再没有出现其他的情况了,走入了过度设计的境地,投入颇多,产出太少。
    但这个服务我们还是决心将来做好做大的,那么搭建一个可扩展的基础设施越早做成本也就越低了。

    这个server原本提供的主要服务就是,调用方传来一个shopId,返回这家店下面的菜单,数据结构大体是这个样子的:
    {
       shopId:123,
       menu:[
            {
               category:'冷菜',
               dishes:[
                   {name:'凉拌黄瓜',price:15},
                   {name:'凉拌西红柿',price:14}
               ]
            },
            {
               category:'热菜',
               dishes:[
                   {name:'红烧肉',price:25},
                   {name:'口水鸡',price:23}
               ]
            }
       ]
    }
    这么json的数据形式咋一看似乎,哦,用mongo一条记录蛮好嘛,但这是我转换之后的,有些时候我们用mongo用得并不是那么schema less。像这里的数据,用mysql可谓是标准化的两张表设计,一张item表{itemId,shopId,categoryId,itemName,price},一张category表{categoryId,categoryName},join一下就完事了,非常干净利落。
    这个地方用mongo来按照上面的schema设计的话(一家商户的菜单作为一条记录),我反倒要担心,如果经常有菜品被加入的话,会有一些问题。因为mongo在插入一条记录时(比方这条记录大小1M),会在磁盘上预留一定的空间(比方说分配1.5M),紧接着放下一条记录,这预留的0.5M空间以备后续有新的菜品插入menu或dishes数组,如果后面频繁的加菜的话,一旦这0.5M空间不够,mongo就需要移动数据开辟一块新的空间来容纳这家店的菜单,删除原来位置的数据,这个操作的开销还是蛮大的。解决的方案是预估自己的每条记录平均有多大,比方说3M,那么一开始可以先在插入数据时加入一些占位数据,将数据撑大到3M,先让每条记录把这块3M的磁盘空间占住,然后再删掉占位数据,这样便绝大多数情况下留够每条记录的预留空间了。
    好在菜单这个业务不存在这样的场景,主体数据都是在初始导入时插入的,后续只有少量的增删改操作。所以说,有时候有些方案哪怕技术上可能有问题或瓶颈,但如若从业务上看以后并不太可能有这样的情况发生,那还是可以放心大胆地去用的。

    但我们还需要考虑后续这样的情况,这个服务将来可能提供的数据并不仅仅是菜单,因为点评的商户有美食、结婚、美发等各种类型,他们各有各自的产品,比方说婚纱套系、服务套餐等等,这个服务将来可能提供的数据为不同类型商户的产品列表。
    这样来考虑,问题就大了。因为不同的产品,可能会有着完全不同的属性。
    婚纱套系这个production可能会多出'照片张数'这个属性:
    {name:'阳光海岸', price:1200, num:20}
    服务套餐这个production可能又完全不需要category分类,这些套餐都是平级的。

    如此这般,后续不同类型的产品有哪些套系完全是不可预知的,怎么解决?
    1.尝试设计一个通用的schema(比方说通过array、map等方式),来cover所有的产品属性;
    2.每种产品类型的商户,各自使用各自的collection。

    1这种方式问题在于,如果我要做ORM的话,不可能设计出一个entity来映射cover所有可能的后续产品;不做ORM的话,例如就是给调用方返回一个json,一对方使用起来麻烦,二我提供时也需要文档给出schema样例,文档这种东西要能保持持续维护,你信吗?
    2这种方式问题就在于每加入一个商户产品类型,都需要重新开发了,好累。

    思索一番,最终给出了这样的解决方案,先罗列几个我的设计原则:
    1.采用ORM,便于调用方使用,entity才是最好的schema文档;
    2.不尝试设计一个通用的entity来对所有的记录ORM映射,这不可能。

    1这一点不多讲,2这点的话还是打破了原本不少人做ORM的惯性思维的,从关系型ORM hibernate带过来习惯性思维我们觉得对mongodb做ORM,我们也是设计一个entity来映射一个collection。我们用mongodb的schema less特性一般也止步于可以将mongo中的array映射成entity里的list,虽然这已经是一个进步了。
    
    但谁说我们不能拿多个entity来映射同一个collection呢?
    我的这个叫producitons的collection里存储着不同类型的商户产品列表,那我为什么不能拿不同商户类型的entity来对应这个collection里的记录呢?他们本来就是完全不一样的。
   {
       shopId:123,
       productType:'dish'
       menu:[
            {
               category:'冷菜',
               dishes:[
                   {name:'凉拌黄瓜',price:15},
                   {name:'凉拌西红柿',price:14}
               ]
            }
       ]
    }

    {
       shopId:124,
       productType:'wedding'
       list:[
            {
               name: '阳光海岸',
               price: 1200,
               num: 24,
               currency: '张'
            },
            {
               name: '童话城堡',
               price: 1200,
               num: 24,
               currency: '张'
            }
       ]
    }

    这样两条不同类型的记录我们可以完全写两个不同的entity来映射:
    ShopDishListEntity、ShopWedListEntity

   shopDishList = collection.findOne("{shopId:#, productType:#}", 123, 'dish').as(ShopDishListEntity.class);

   shopWedList = collection.findOne("{shopId:#, productType:#}", 124, ShopWedListEntity.class);

    这里ORM lib我使用的是jongo,和morphia比起来,他的查询更接近原生mongodb shell一点,morphia在element match查询的支持上也是有问题的。

    从这里我们不难看出我的数据获取是根据shopId+productType来查询的,而productType明确,那么该拿哪个entity来映射也是明确的。
    我们的entity共性的地方在于shopId、productType。
    更进一步,从上面的语句里我们能否抽象出一些其他共性的东西来?请自行思考1分钟。

    OK,从这两句语句我们可以看出,我们操作的mongo中记录的schema虽然是不同的,他们对应的entity也是不同的,但我们操作这些记录的动作却是一致的
    又比如:
    collection.save(shopDishList);
    collection.save(shopWedList);

    自此我们完全可以抽象出一个公共的dao层出来,他负责对productions这个colletion的增删改查操作,但操作数据的具体类型可以用泛型来指定。
    看,这个地方和泛型结合是多么的自然。

    加上这里本身最终映射entity的不确定定性,为了接口方法的通用性,我们最终也将遇到强制转型这种不那么优雅的操作:
    Object result = collection.findOne("{shopId:#, productType:#}", shopId, productType).as(clazz);
    ShopDishListEntity dishList = (ShopDishListEntity) result;

    但泛型安全性和表述性在这个方面都能很好地帮助我们解决,转型和数据约束的问题。
    直接上最终的代码:

public class ProductionsDao {

    private final Class clazz;
    private final MongoCollection collection;
    private final static String COLLECTION_NAME = "productions";

    public ProductionsDao(Class clazz){
        this.clazz = clazz;
        this.collection = JongoClient.getInstance().getCollection(COLLECTION_NAME);
    }


    public T load(int shopId, String productType){
        return collection.findOne("{shopId:#, productType:#}", shopId, productType).as(clazz);
    }

    public T save(T t){
        collection.save(t);
        return t;
    }
}

ProductionsDao< ShopDishListEntity > productionsDao = new ProductionsDao< ShopDishListEntity >(ShopDishListEntity.class);
ShopDishListEntity shopDishList = productionsDao.load(...);

ProductionsDao<  > productionsDao = new ProductionsDao< ShopWedListEntity.class);
 shopWedList = productionsDao.load(...);

    ProductionsDao封装了对collection的操作行为,泛型对其所操作的数据类型做了很好的约束和表述。这里既保证了无需对获取的数据进行强制类型转换,也保证了对插入数据类型的约束,可谓一举两得。

    后续无论加入多少中商户产品类型,我所需要做的代码更改只是更具需求新添加一个entity,而对应于这种类型数据的增删改查等具体操作则完全无需重新开发。
  
    用一句话来概述就是:变化的是所操作的数据类型,不变的是对数据的操作行为
  (变化的是商品列表的产品属性,不变的是对这些商品列表的操作行为)

    回头再看我们初始考虑的两种解决方式:
    1.尝试设计一个通用的schema(比方说通过array、map等方式),来cover所有的产品属性;
    2.每种产品类型的商户,各自使用各自的collection。
    最终我们的解决方案既达到了方案2的分而治之,更加灵活自由,对于调用方而言,数据的获取操作通过不同entity的方式也更加清晰、明确;对于方案1复用的考量,我们最终又达到了对操作的通用、复用,避免了其为了数据结构的通用而遏制产品属性变化灵活度的弊端



相关推荐

  • linux c编程操作数据库(sqlite3应用) C/C++语言中调用sqlite的函数接口来实现对数据库的管理(创建数据库、创建表格、插入数据、查询、数据、删除数据等)。首先要编译好sqlite的库文件:libsqlite3.alibsqlite3.lalibsqlite3.solibs
  • 源码推荐(7.23):FMDBModel使你的实体类具备直接操作数据库的功能,Switch 开关动 FMDB的封装,使你的实体类具备数据库操作的功能,极大简化你的数据库操作,对于自己的扩展也非常简单。该框架是本人在项目中用到的对FMDB的封装,它的特点如下:1.自动创建数据库、自动创建数据库表。2.自动检测字段添加新字段。3.一行代码实现
  • abap操作数据库 本文转自翱翔云天的博客,是为了学习时候方便查阅现在介绍一些abap的数据库操作…….TheDatabaseInterface(数据库接口)为了避开各种数据库的操作语句,函数,功能的不同,R/3系统在每个工作进程(workprocess)里面
  • 在Linux下用C语言操作数据库sqlite3(插入) 插入:insert 刚刚我们知道了怎么调用sqlite3的C/C++的API函数接口,下面我们看看怎么在C语言中向数据库插入数据。  好的,我们现编辑一段c代码,取名为insert.c//name:insert.c//Thisprogisu
  • Android Studio平台使用GreenDao操作数据库 一.GreenDao是什么?简单说就是一个可以方便操作SQLite数据库的第三方库;二.使用流程1.创建一个java程序;2.在java程序中书写代码,运行后会生成一系列实体类和其他类(详细下面会说),代码中指定生成的类的路径、数据库表的字
  • vb使用adodb操作数据库常用方法 ADO常用方法下面是我所掌握的使用ADO对数据库操作的一些常用方法,主要是提供给初学者作为参考,有不对的地方请指正。如有补充不胜荣幸准备工作========DimconnAsNewADODB.Connection'创建一个Connectio
  • JDBC(用Eclipse操作数据库Oracle)的基础操作集合 JDBC:JDBC(JavaDataBaseConnectivity,java数据库连接)是一种用于执行SQL语句的JavaAPI,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以
  • C++ ADO操作数据库 #include"stdafx.h"#include"winSock2.h"#import"c:\programfiles\commonfiles\system\ado\msado15.dll"no_namespacerename("EOF
  • c# 操作数据库要点(一) 处理BLOB现在,许多应用程序除了处理较为传统的字符和数值数据以外,还需要处理诸如图形和声音之类的数据格式,甚至需要处理更为复杂的数据格式,如视频。存在许多不同类型的图形、声音和视频格式。不过,从存储角度而言,它们都可以视为二进制数据块,通
  • 操作数据库“增删该查” C#操作数据库“增删该查”//////增加记录/////////privatevoidbuttonX2_Click(objectsender,EventArgse){stringconstr="Server=ADMIN-PC\\SQLEXP
  • 一.操作数据库 1.登录数据库本地mysql-uroot-p登录服务器数据库mysql-h192.168.1.251-uroot-p2.查看已经存在的数据库showdatabases;3.查看默认存储引擎showvariableslike'storage_
  • “简单事务操作”数据库(NO-SQL数据库)应用系统的可扩展性设计的十条原则 原文:《TenRulesforScalablePerformancein“SimpleOperation”Datastores》ByMichaelStonebrakerandRickCattell作者简介:(MIT教授,多家公司和项目的开创

你的评论

就没有什么想说的吗?

最新博客

关于我们 移动版

©2017传客网    琼ICP备15003173号-2    

本站部分文章来源于互联网,版权归属于原作者。
本站所有转载文章言论不代表本站观点,如是侵犯了原作者的权利请发邮件联系站长(weishubao@126.com),我们收到后立即删除。
站内所有资源仅供学习与参考,请勿用于商业用途,否则产生的一切后果将由您自己承担!

X