元数据、开放数据模型及动态系统--形而下学篇

介绍了一种普适的、弱类型、开放的数据容器——数据集(DataSet),它能适应多种数据形式,如Properties、Database Tables、XML等。数据集通过接口定义了其基本操作,并通过辅助工具实现了数据在不同媒介间的传输。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

普适数据容器



数据集(DataSet)是一个普适的,弱类型的,开放的数据容器。普适是指它可以适应任何基于名值对集合或名值表的数据形式,比如Properties,Database Tables, XML, CSV, JSON, LDAP, ...。弱类型是指所有的容器都用同一个数据结构,存放在容器中的只有两种对象:数值和子结构。数值一律用字符串表示,时间日期用XML标准形式来表示,子结构一律用数据集表示。有关定义域的元数据存放在专门的数据结构中。如果需要类型转换,借助元数据,可以很容易地做到。所以为了突出本质,我宁愿把数据转换的代码放到AccessorWrapper里。事实上在我们的项目里,根本就没有用到类型转换。所谓开放,就是前面说过的,容器可以适应数据模型的变化,以不变应万变。

下面给出两个主要的接口。DataSet继承Iterator和Map,即是为了表明概念继承上的渊源,也是为了与Web模板结合。数据集在元数据只含有字段名,并且不具有滚动性质的情形下,退化为Map。

java 代码
 
  1. public interface DataSet extends Iterator, Map  
  2. {  
  3.     public abstract String getDataSetName();  
  4.     public abstract void setDataSetName(String name);  
  5.     public abstract DataSetMetaData getMetaData();  
  6.     public abstract void setMetaData(DataSetMetaData md);  
  7.   
  8.     public abstract DataSet getDataSet(int column);  
  9.     public abstract void setDataSet(int column, DataSet ds);  
  10.     public abstract DataSet getDataSet(String path);  
  11.     public abstract void setDataSet(String path, DataSet ds);  
  12.   
  13.     public abstract String getValue(int column);  
  14.     public abstract void setValue(int column, String value);  
  15.     public abstract String getValue(String path);  
  16.     public abstract void setValue(String path, String value);  
  17. }  
  18.   
  19. public interface DataSetMetaData  
  20. {  
  21.     public abstract int getColumnCount();  
  22.     public abstract void setColumnCount(int count);  
  23.   
  24.     public abstract int getColumnDisplaySize(int column);       // optional  
  25.     public abstract String getColumnLabel(int column);          // optional  
  26.     public abstract String getColumnName(int column);  
  27.     public abstract int getColumnType(int column);  
  28.     public abstract String getColumnTypeName(int column);  
  29.     public abstract int getPrecision(int column);               // optional  
  30.     public abstract int getScale(int column);                   // optional  
  31.     public abstract boolean isAutoIncrement(int column);        // default "no"  
  32.     public abstract boolean isCaseSensitive(int column);        // default "yes"  
  33.     public abstract int isNullable(int column);                 // default "yes"  
  34.     public abstract void setAutoIncrement(int columnIndex, boolean property);  
  35.     public abstract void setCaseSensitive(int columnIndex, boolean property);  
  36.     public abstract void setColumnDisplaySize(int columnIndex, int size);  
  37.     public abstract void setColumnLabel(int columnIndex, String label);  
  38.     public abstract void setColumnName(int columnIndex, String columnName);  
  39.     public abstract void setColumnType(int columnIndex, int DataType);  
  40.     public abstract void setColumnTypeName(int columnIndex, String typeName);  
  41.     public abstract void setNullable(int columnIndex, int property);  
  42.     public abstract void setPrecision(int columnIndex, int precision);  
  43.     public abstract void setScale(int columnIndex, int scale);  
  44. }  

 

数据集的辅助工具

为了便于应用,需要有一整套的工具类。下面列出与数据集应用有关的核心类和辅助类,结合后面的用例,可以让你对数据集的应用方式有一个大概的了解。
核心接口
    DataSet
    DataSetMetaData
    DataSetException
    ...
核心实现:
    DataSetBaseImpl
    DataSetMetaDataBaseImpl
    DataType
    TypeFormat
    ...

辅助工具包:   

    SAX 2扩展包。
        DataReader < org.xml.sax.XMLReader
            DefaultReader <~ JAXP Default Reader
            DataSetReader
            DBTableReader
            DBLobReader
            JndiReader
            TeeFilter
            ObjectReader
            ResultSetReader
            ...
        DataSource < org.xml.sax.InputSource
        DataSink ~ org.apache.xml.serializer.Serializer
            DefaultSink ~ org.apache.xml.serializer.Serializer
            DataSetSink
            DBTableSink
            DBLobSink
            ObjectSink
        DataPipeFactory
    IO扩展包
        InputStreamBuffer, OutputStreamBuffer
        NullInputStream, NullOutputStream
        ByteMessageInputStream, ByteMessageOutputStream
        TextMessageInputStream, TextMessageOutputStream
        SmartZipInputStream, SmartZipOutputStream
        RewindableInputStream
        FanOutputStream
        StreamCopier
    组件框架
        略过

数据集用例

在应用上,数据集的应用模式,遵循了SAX 2的标准模式。

SAX 2的标准模式

java 代码
 
  1. XMLReader reader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();  
  2. //获取一个InputStream实例is,然后  
  3. InputSource source = new InputSource(is);  
  4. //获取一个ContentHandler实例handler,然后  
  5. reader.setContentHandler(handler);  
  6. reader.parse(source);  

数据集的例子
java 代码
 
  1. EndPoint source_ep = new EndPoint("jdbc://MyDataSource/MySchema/MyName");  
  2. DataReader reader = DataPipeFactory.createDataReader(source_ep);  
  3. DataSource source = DataPipeFactory.createDataSource(source_ep);  
  4. EndPoint sink_ep = new EndPoint("object:///net/hyperdigital/dataset/DataSet");  
  5. DataSink sink = DataPipeFactory.createDataSource(sink_ep);  
  6. DataHandler handler = sink.asContentHandler();  
  7. reader.setContentHandler(handler);  
  8. reader.parse(source);  
  9. DataSet ds = handler.getContent();  

上面这段代码将执行“select * from MySchema.MyName",并将数据读入一个数据集,数据集的名字是MyName。如果我想执行一个特定的SQL语句,我只要在上面第一行后加上一行:
java 代码
 
  1. source_ep.setProperty("read""select xxx,xxx, xxx from xxx where ...");  

相应的代码会执行给定的SELECT语句,并把结果读入一个数据集,数据集的的名字属性是MyName。

如果我想把结果存放在一个XML文件中,可以这样做:

java 代码
 
  1. EndPoint source_ep = new EndPoint("jdbc://MyDataSource/MySchema/MyName");  
  2. DataReader reader = DataPipeFactory.createDataReader(source_ep);  
  3. DataSource source = DataPipeFactory.createDataSource(source_ep);  
  4. EndPoint sink_ep = new EndPoint("file:///absolution/file/path/MyFileName.xml");  
  5. DataSink sink = DataPipeFactory.createDataSource(sink_ep);  
  6. DataHandler handler = sink.asContentHandler();  
  7. reader.setContentHandler(handler);  
  8. reader.parse(source);  

和前面的代码相比,不同的仅仅是数据汇(数据流向的终点)的定义。如果要把数据存作XML并且发到消息队列上,同样只要改动数据汇:
java 代码
 
  1. EndPoint sink_ep = new EndPoint("queue://MyQCF/MyQueue");  

XML是假定的媒介形式,我可以以通过指定端点的属性来改变媒介形式。

如果我要把数据存入数据库表,假定数据集里的字段名和数据库表中的字段名相同,(convention over configuration),相应的代码如下:
java 代码
 
  1. EndPoint source_ep = new EndPoint("object://java/net/hyperdigital/dataset/DataSet");  
  2. DataReader reader = DataPipeFactory.createDataReader(source_ep);  
  3. DataSource source = DataPipeFactory.createDataSource(source_ep, dataset);  
  4. EndPoint sing_ep = new EndPoint("jdbc://MyDataSource/MySchema/MyTable");  
  5. DataSink sink = DataPipeFactory.createDataSource(sink_ep);  
  6. DataHandler handler = sink.asContentHandler();  
  7. reader.setContentHandler(handler);  
  8. reader.parse(source);  

数据迁移是小菜一碟:
java 代码
 
  1. EndPoint sing_ep = new EndPoint("jdbc://MyDataSource/MySchema/MyTable");      
  2. DataReader reader = DataPipeFactory.createDataReader(source_ep);      
  3. DataSource source = DataPipeFactory.createDataSource(source_ep, dataset);      
  4. EndPoint sing_ep = new EndPoint("jdbc://YourDataSource/YourSchema/YourTable");      
  5. DataSink sink = DataPipeFactory.createDataSource(sink_ep);      
  6. DataHandler handler = sink.asContentHandler();      
  7. reader.setContentHandler(handler);      
  8. reader.parse(source);    

不厌其烦贴了那么多,是为了显示在发布系统中任意两个端点间的数据通讯,都是同一模式。现在让我们来看一看EndPoint是个什么东西。java.net.URI是不可继承的,所以下面extends的意思是EndPoint包括URI的所有方法。
java 代码
 
  1. public class EndPoint extends URI  
  2. {  
  3.     public EndPoint(String endpoint) {}  
  4.     public EndPoint(String context, String relative) {}  
  5.     public EndPoint(URI uri) {}  
  6.     public EndPoint(URI context, String relative) {}  
  7.    
  8.     public String getContextPath() {}  
  9.     public String getRelative() {}  
  10.     public String getTextType() {}  
  11.     public URI getURI() {}  
  12.   
  13.     public String getProperty(String name) {}  
  14.     public void setProperty(String name, String property) {}  
  15.     public Set getPropertyNames() {}  
  16. }  

主要的属性(Property)有方法(method)和文本类型(text_type)。方法可以是Read, Write, Append, Delete, Get == Reader, Insert == Put == Write, Post == Update。Read是假定的也是唯一的源方法,Write是假定的汇方法。文本类型的值可以是text, xml, html,这些是Xalan中已经实现的,也可以很容易地增加cvs, json等等类型。EndPoint里的URI,基本上都是URL。EndPoint支持的传输协议,包括所有java.netURLConnection支持的协议,再加上jdbc(数据库表和LOB),jndi, queue,topic,object。Object的主要支持字符串、数据集、Map、。。。,字符串里的内容,可以是任何文本类型(text_type)。

再回过头去看上面的代码片断,设想一下各种数据源和数据汇的组合,上面的代码,远远超过了仅仅作数据库访问的意义,它可以用于网络上任意两个组件或者服务之间的数据传输,它可以看成是一个分布式数据访问和数据传输的框架。

数据集的应用模式

对数据的操作可以有两种模式:一种是OO模式,另一种我暂且称之为数据集管道模式,是我要介绍的模式。

在管道模式看来,数据集(DataSet)代表一个向量或者一个向量的序列。那么,对数据的操作就是作用于这个向量的一个矩阵,而ContentHandler就是一个事件驱动的矩阵实现。普通的ContentHandler更适于同一空间中的线性变换(实际上不一定线性),而Transformer对应于不同空间之间的变换。数学上,矩阵是可以连乘的,在SAX 2,Transformer是可以像管道一样串连的,二者一一对应。这种管道化的特性,提供了又一个分解复杂逻辑的手段,使我们能够把复杂逻辑分步骤实现,每一个子逻辑块实现一个明确的子目标。

数据集的管道模式借用了SAX 2的管道模式。上面的几个例子显示了数据传输的应用,它们是最短的管道系统,一个输入缓冲池(DataSource),一个泵(DataReader),一个输出缓冲池(DataSink)。系统把流体(数据)从输入缓冲池运送到输出缓冲池,不做任何处理。如果要对数据进行处理,只要在泵和输出缓冲池之间接上一个或几个过滤器(XMLFilter/Transformer)就可以了。过滤器可以是自己实现的ContentHandler,也可以是由XSLT产生的Transformer。

管道模式实际上就是用XML Schema实现领域模型,用ContentHandler/XSLT实现领域逻辑的模式。管道模式的好处是:1。最弱的类型依赖,所有的数值都是字符串类型,程序员不必费心类型转换。2。最佳的动态性能。XML Schema和XSLT都是文本文件,可以动态部署,动态修改。领域模型和领域逻辑的改变后,代码不必跟着修改。坏处是你得学XML和SAX 2。较之于一大堆繁不胜繁,遮遮掩掩,修修补补而又欲盖弥彰的设计模式起来,XML要简单直白多了。另一个坏处是,XSLT的功能不够强大,不足以应付在某些复杂情况。不过,某些OO模型下看来很复杂的问题,用XSLT来实现的话,也可能很简单。

OO模式也可以由两种做法。一种是传统的做法。一种是在数据集的基础上构造领域模型,将领域模型封闭在领域逻辑层。传统的领域模型+ORM的模式,不可避免地会把领域模型的细节暴露给领域逻辑层以外,而造成系统对领域模型不必要的依赖,大大降低系统的稳定性和动态性。我对传统的领域模型+ORM产生的大量的空壳类和映射文件十分厌恶,在我看来,它们就像一具具没有灵魂,没有血肉的骷髅,可以用尸横遍野来形容。

从对数据所负的责任来看,系统的参与者(actor)可以分为两类。一类是中介(agent),一类是客户(client)。客户是数据的最终消费者,负责对数据的解读和处理加工。数据只有对客户才有逻辑的和物理的意义。而中介只是传递数据,数据对于中介来说是没有意义的毛数据(raw data),中介的责任是将毛数据不失真的传递到客户的手中。从系统的壳层(layer)结构来看,客户位于领域逻辑层内部而中介位于领域逻辑层外部,因此中介们不必要也不应该知道领域模型的细节。这才符合编程的基本道理。通常的Domain Model+ORM的模式,就是因为违背了这个基本的道理,才有了种种的弊端。

 

小结

  • 数据集是一个普适的,开放的数据容器。数据集可以用来承载任何基于名值对的数据媒介。
  • 在数据集中,数值和元数据是分离的。元数据,除了字段名以外,可以在需要的时候延迟加载,不需要就不必加载。
  • 用数据集构造的系统,在领域逻辑层外,数据被视为毛数据,不再有类型的桎梏。
  • 在纵向,数据集可以应用于系统的各层之间,以来取代大量的XO(DTO,PO,VO,...)。
  • 在横向,已经开发出的工具可以使数据方便地在组件和服务之间传递。
  • 系统的配置信息,也是基于名值对的持久数据,当然也可以用数据集以及相关的技术。
  • 简单一致的数容器和传输模式,大大简化了组件之间的连通和阻抗匹配问题,开发团队可以集中精力解决领域逻辑问题。
  • 服务数据库表和服务本地XML文件的代码几乎是一样的,所以只需截取几条典型记录,存作XML,就可以用于离线测试。
  • 因为输入输出都是纯数据,都是同一个类型,Mock Object不再需要,大大简化了单元测试。
  • 易于离线开发和离线测试。我做J2EE项目,开发中基本不需要EJB容器。结果我的离线开发工具/环境被其他团队拿去作为正式的离线工具(standalone tool)。离线系统和在线系统具有完全相同的功能,唯一的差别是单线程和多线程。
  • 用数据集构造的系统,可以实现简单项目到复杂项目的平滑过渡,或者说复杂项目各阶段之间的平滑过渡。
  • 用数据集构造的系统,符合REST和SOA的要求。这里就不展开了。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值