通过实体快照实现事件建模

  这是来自Snowplow分析公司建模实践的文章:

  在Snowplow 我们花很多时间思考如何对事件建模。将企业重新定位基于事件流的统一日志模式下,正确建模这些事件流变得更加重要。

  我们已经确定一套事件的语义模型:一个是建立在跨整个事件发现的内在特性上。一个语义模型有助于防止企业和技术的假设泄漏到事件流中。

  我们的思维已经从原始事件的语法方案开始演变,下面是我们对事件定义:

  一个事件是任何我们在某个时间点能观察到发生的事情,,每个事件被记录为一系列相关实体,就,这些实体站立在那个时间点上。

 

1.实体定义


  什么是实体,在事件建模术语中,一个实体是一个和我们观察的事件有关的事情或对象,举例,语句:"我在当地剧院观看了《地心引力》",在这里,我、电影《地心引力》和影剧院都是实体。


  我们使用单词"实体"而不是"对象"是因为:对象概念太重了, 它有太多面向对象编程的含义, 它也有一些在谓词"主体"和"客体"方面的语义。

  实体在软件中到处存在,MySQL表 backbon.js模型 Protocol Buffers, JSONs,POJO、 XML文档,Haskell记录等等,作为程序员我们花费了大量时间和实体 打交道,这不是巧合,每天发生在软件中发生的大量事件中实体扮演了重要的角色。

  关于在事件中实体有许多混淆,甚至分析公司都认为实体数据是完全不同于事件数据的,我们将看到我们的事件几乎都是由实体组成,但是在我们观察事件之前实体关系,我们需要理解实体之间是如何随着时间交互的。

 

2.实体和时间


  所有对象都会存在一段时间,在软件系统中的实体总是随时间变化,如果我们能观察到我们的实体是由各种属性组成,我们能切分这些属性为三种:

(1)永久和静态属性,这些属性在实体的生命周期内都不会改变,比如: 我的母语,

(2)不频繁变化的属性:如我的email地址会不是很频繁变化。

(3)频繁变化的属性:比如我的地理位置会常变。

  准确的分类并不是一成不变的,一个人的身高开始时是作为一个经常变化的属性, 成年后是一个静态属性,随着年纪的增长切换到一个很少变化的性质。

  让我们假设,Jack正在玩手机游戏,他离开时保存下游戏流程,Jack是一个实体, 手机游戏也是一个实体,下面说Jack在三个不同阶段其内部属性的改变:


  换句话说,每次Jack保存他的游戏时都有稍微不同,如果我们要分析理解这些 保存游戏的事件,能够重新审查Jack的版本,是什么触发了这些事件将变得非常重要。 我们如何做到呢?

 

3.时间和数据库

   包围我们的软件都是以各种方式实体建模,我们已经知道我们关心的是随时间变化的实体。那么实体建模的软件是记录实体如何随着时间慢慢变化吗?不是。

  大多数系统中的实体,如数据库 shema 语言,序列化协议等等,都是没有 时间维度的概念,这些系统大部分只是简单地跟踪实体的每个状态, 当一些属性改变时,程序员会提前预期修改一些存在的数据。
如果Jack的email地址改变,那么我们必须更新在数据表中已经存在的Email地址值, 在许多系统中,Jack的Email地址总是最新的。

  对于Jack母语那样静止不变的属性没有什么问题,但是这种只记录最新的状态方式对已不频繁或频繁变化的属性来说比较痛苦。有一些软件试图这么做:

(1)值版本,在HBase数据库中,一个元素也就是一个值是由 {row, column, version} tuple 指定的,你能够配置HBase存储一个列column的数百个版本,缺省默认是 返回最新的版本,但是你也能返回指定时间戳的值。

  Datomic database存储的是"Datoms"或由实体 属性和值和事务(时间)组成的 事实,这些datoms是不变的事实,它们从来不会被更新,但是新的能够不断 追加,你能查询某个时间点数据,或跨时间窗口查询阶段快照,在数据仓库中, 一个ETL过程能够规则地(如每天)从事务数据库中捕获某时刻的实体数据,存储在 它的数据仓库中的事实表中。

(2)日志引擎, 日志历史引擎是在数据库中设置,能够自动记录所有的C/U/D(创建 修改 删除) 事件。

(3)Event Sourcing,当应用状态改变的所有事件存储为只可追加的 不可变事件序列,我们再也不关心状态,而是关心状态的切换,如果我们 需要一个实体的当前状态,那么我们只要从相关的事件序列中获取
计算得到。

  所有这些途径都是为了捕获数据的改变,各有优缺点。

 

4实体快照


  存储越来越便宜,网络一直在加快,如果我们和某个事件有关的 实体,为什么我们不审查事件对应的状态,然后将这些状态作为事件 记录呢?先把这放到一边,让我们获得我们事件中的实体快照, 这记录Jack每次他保存手机游戏时的精确属性。

  这种方法有一些明显的优点: 在事件捕获阶段应用很简单:对一个实体感兴趣,那就使用它发生的事件吧。在底层数据系统没有必要实施各种数据改变捕获的功能,但是在分析阶段, 没有必要交叉使用对应一个事件的跨多个实体状态,只使用关联的事件就可以了。

我们看看下面案例使用Javascript跟踪文档支持实体快照形式:

window.snowplow_name_here('trackPageView', null , [{

    schema: "iglu:com.example_company/page/jsonschema/1-2-1",

    data: {

        pageType: 'test',

        lastUpdated: new Date(2014,1,26)

    }

},

{

    schema: "iglu:com.example_company/user/jsonschema/2-0-0",

    data: {

      userType: 'tester',

    }

}]);

 

  在这个代码中,我们记录的是两个实体的状态:一个是Web page 一个是用户,这是page view事件发生时的实体快照。

 

5.修订的事件语法

  下面是我们从人类语言转换到事件语义结构的定义:

  • Subject主体, 或者主语名词,也就是正在执行动作的实体,比如"I wrote a letter"中黑体"I"
  • Verb谓语, 这是描述被主体完成的动作: "I wrote a letter"
  • Direct Object直接对象, 宾语中的对象或名词,这是一个被实施动作的实体: "I wrote a letter"
  • Indirect Object间接对象, 与格句式中的名词,一个稍微复杂的概念:这是由动作间接影响实体: "I sent the letter to Tom"
  • Prepositional Object介词对象. 通过介词(in, for, of 等)而不是直接对象或间接对象引入的对象: "I put the letter in an envelope".
  • Context上下文. 不是一个语法术语,但是我们使用上下文来描述时间 方式 地址等等提供有关执行动作的附加信息: "I posted the letter on Tuesday from Boston"

知道我们是如何了解实体,那么现在我们可以有两个简化模型:

  1. 一个 Indirect Object间接对象只是一个介词对象Prepositional Object的类型. 不需要把间接对象作为一个独立的概念语境
  2. Context上下文是由介词对象Prepositional Objects构成的. 比如时间概念都有一个介词at ,而地点概念都有一个介词in 或 on 或 outside 。

在采取这些改变的结果后,事件语义形式如下:

  • 一个必须Subject主体 - 执行动作的实体快照
  • 一个必须的谓词Verb -由主体完成的动作
  • 一个必须的介词Prepositional Object -事件发生的时间。
  • 一个必须的直接对象Direct Object - 动作被实施的实体快照。
  • 一系列可选的介词对象Prepositional Objects - 也是由实体快照组成。

更新后的事件语义如下:

  正如我们前面所说,我们的事件几乎都是由实体组成,分离出谓词以后,我们的事件就只是由实体快照组成,每个都是有语义词语标记,都和事件有关。

 

6.JSON形式的事件语义

  我们使用JSON描述事件模型,假设我们试图建模一个玩家销售设备给另外一个玩家中的事件,下面是JSON的事件定义:

{ 
  "schema": "iglu:com.snowplowanalytics.snowplow/event_grammar/jsonschema/1-0-0", 
  "data": { 
    "subject": { 
      "schema": "iglu:de.acme/player/jsonschema/1-0-0", 
      "data": { 
        "playerId": "1213", 
        "emailAddress": "l33t@gamer.net", 
        "highScore": 220412 
      } 
    }, 
    "verb": "sell", 
    "directObject": { 
      "schema": "iglu:de.acme/armor/jsonschema/1-0-0", 
      "data": { 
        "type": "titanium", 
        "condition": 0.63 
      } 
    }, 
    "at": { 
      "schema": "iglu:com.snowplowanalytics.snowplow/time/jsonschema/1-0-0", 
      "data": { 
        "collectorTstamp": "2015-01-17T18:23:05+00:00", 
        "deviceTstamp": "2015-01-17T18:23:02+00:00" 
      } 
    }, 
    "prepositionalObjects": { 
      "to": { 
        "schema": "iglu:de.acme/player/jsonschema/1-0-0", 
        "data": { 
          "playerId": "7684", 
          "emailAddress": "karl@gmx.de", 
          "highScore": 220412 
        } 
      }, 
      "for": { 
        "schema": "iglu:de.acme/price/jsonschema/1-0-0", 
        "data": { 
          "amount": "30", 
          "currency": "doubloons" 
        } 
      }, 
      "in": { 
        "schema": "iglu:de.acme/level/jsonschema/1-0-0", 
        "data": { 
          "world": "Aquatic", 
          "levelName": "Pirate Bay" 
        } 
      } 
    } 
  } 
} 

 

7.总结

  • 实体和事件是深深纠缠,不能理解参与事件的实体就无法理解事件。
  • 实体随时间变化,因为它们包含频繁或不频繁地变化的属性。
  • 大多数数据系统都是非时间的,变化数据捕获是一种方法学,直接在这些非时间系统试图映射实体状态切换的方法。
  • 实体快照Entity snapshotting给了我们一个简单有效的记录一个事件特定某个时刻的实体状态的方法。
  • 我们修订的事件语法只由谓词加上必须或可选的实体快照组成
  • 我们能够使用自我描述的JSON表达事件语义,使用JSON模型简单灵活。

 

日志是每个软件工程师关心的统一数据抽象

四色原型

Event Sourcing在分布式系统中应用

事件建模专题