跳到主要内容

主题架构最佳实践

事件驱动架构、事件驱动微服务和事件驱动集成是有价值的应用设计模式。这些系统的关键优势在于应用可以通过异步响应事件来协作,而不是依赖于同步轮询或编排进行信号传递。发布-订阅消息交换模式是传递事件的常见方式。一个设计良好的主题层次结构:

  • 有效地在事件驱动系统中路由事件,并确保消费者应用具有选择性地只消费它们想要的事件的灵活性。
  • 允许应用高效地吸引和消费事件,并充分利用Solace智能主题。

丰富、定义明确的主题架构是最大化事件驱动架构价值的关键。它为路由、过滤和治理提供了许多好处。此外,主题架构可以设计并在PubSub+ Event Portal中捕获,允许事件发现和运行时管理。

有关主题的更多信息和背景,请参阅:

  • 理解主题
  • 事件驱动架构是什么?有关事件和消息的一般信息
  • 主题架构案例研究
  • 主题订阅中的通配符字符
  • 关于Solace主题的一切!(视频)
  • 当主题不仅仅是发布/订阅:解释Solace中的主题和订阅(Solace博客)
  • 使用主题控制信息流(Solace博客)
  • Solace博客,有关事件驱动架构的讨论

主题架构定义

您应该熟悉以下事件驱动架构术语。

事件

事件可以广义地描述为您的组织内发生的某事,特别是某种对象的状态变化。事件可以有多种形式,但都有一个共同组成部分,即在对象上发生的动作。例如:

  • 在微服务系统中,事件是一个微服务发出的变化或结果,并在状态更新时被另一个微服务消费。例如,客户下订单或开立账户。在这些系统中,每个微服务独立行动并通过智能主题异步发送变更通知。微服务是松散耦合的,因此发布者不需要知道谁需要接收通知,而是使用智能主题明确描述哪个对象发生了变化以及如何变化。

  • 在物联网系统中,事件代表现实世界对象的状态变化。例如,事件可能是传感器温度超过阈值的通知。同样,这些变更通知或事件需要分发到所有感兴趣的系统。

  • 在数据系统中,事件代表创建、更新或删除操作。由于创建操作,后端可能会更新数据库并发出完成事件。事件驱动架构与数据库的区别在于,事件网是一个开放且分布式的系统,事件可以分发到所有感兴趣的应用。这消除了应用轮询数据库变更的需求。

事件主题

为了在事件驱动架构中实现可重用事件的企业级分发,事件需要能够路由到感兴趣的应用。这种路由是通过智能事件主题实现的。主题是消息层头部中的额外信息(或元数据),由产生事件的应用设置。智能主题允许事件代理在不反序列化、解码和解释整个事件的情况下做出智能路由决策。事件代理不需要理解整个事件就可以采取行动;它们只需要知道如何根据事件的主题信息采取行动。这类似于IP路由器在不检查负载或甚至上层头部的情况下指导互联网流量的方式。然而,与URL或IP地址不同,事件主题不描述目的地,而是描述消息负载的内容。要让事件主题产生价值,它们必须遵循明确定义的主题分类,清晰地定义由生产应用设置的各种层次化主题级别。

事件订阅

虽然事件主题是附加在发布事件上的元数据,允许事件在事件网格中移动,但订阅是客户端应用注册对事件的兴趣的机制。在匹配事件主题和消费者订阅时,系统可以在整个网格中编织动态转发路径,将正确的事件交付给每个消费者。与事件主题不同,单个事件主题订阅可以通过通配符配置以吸引众多事件主题,从而允许对流经事件网格的事件进行细粒度过滤。

主题分类示例

核心上,事件是一个已发生的动作或在对象上已更改的状态。事件的主题应该描述事件。基本的主题层次结构,或者更正式地说,主题分类,必须描述对象(名词)和采取的行动或状态变化(动词)。一些额外属性也可能包含在主题中以丰富层次结构。简单地说,一个好的主题层次结构采取的形式是名词/动词/属性

考虑Example Airline(EA)的主题架构,他们正在设计一个新的应用来跟踪航班。领域将是ops/flights/,用于航班运营。在这种情况下,ObjectType是一个flight,因此航班将是名词。这些航班上可能发生各种不同的行动,例如,航班可能开始boarding或被delayed。为了区分不同的航班,可以在主题中包含flightNumber。订阅者可能还希望接收特定机场的航班信息,因此origindestination是包含在主题中的好候选。综合起来,我们得到flight/[status]/[flightNumber]/[origin]/[destination]作为基本的主题架构。

在Example Airline(EA)的应用中,当状态变化发生时,会发布以下事件:

  • 航班EA 1234正在登机:flight/boarding/ea1234/jfk/ord
  • 航班EA 9999延误:flight/delayed/ea9999/yow/sin
  • 航班EA 1010起飞:flight/wheelsUp/ea1010/ewr/ord

这个主题架构允许订阅者接收事件:

  • 所有离开JFK的航班flight/wheelsUp/*/jfk/>
  • 航空公司所有延误的航班flight/delayed/>
  • 航班EA 1010的所有事件flight/*/ea1010/>

随着时间的推移,您可能需要更多的属性,事件负载可能会演变,新的应用可能会上线。为确保系统的未来发展,每个不同的事件都应该进行版本控制。此外,每个事件主题都应该以数据所有者或领域为前缀。该领域指示哪个部门拥有定义的事件。在这种情况下是运营(ops),以及事件所属的应用领域,在这种情况下是航班。这种做法允许企业未来的增长,无论是在运营部门内添加行李应用,还是完全新的预订部门的应用。

因此,我们得到了最终的主题分类ops/flights/flight/[status]/[version]/[flightNumber]/[origin]/[destination],其中v1发布者可能会发布ops/flights/flight/boarding/v1/ea1234/jfk/ord作为上述登机示例。然后可以将以下事件添加到事件目录中以供发现:

  • ops/flights/flight/boarding/v1/{flightNumber}/{origin}/{destination}
  • ops/flights/flight/delayed/v1/{flightNumber}/{origin}/{destination}
  • ops/flights/flight/wheelsUp/v1/{flightNumber}/{origin}/{destination}

丰富的主题架构的好处

这些关键好处来自于设计良好的主题架构:

  • 路由、过滤和治理可以在事件流经事件网格时在多个级别进行。例如,可以决定哪些事件被允许跨越地理边界,但一旦主题进入地理区域,就可以对哪些消费者可以接收事件进行二次决策。

  • 跨事件收集信息更容易。例如,通配符可以接收与订单ID相关的所有事件类型,或无论发起者或位置如何,接收所有新订单。

  • 当事件文档良好时,例如使用PubSub+ Event Portal,定义明确的主题层次结构使新团队成员和新应用的入职变得更加容易。

  • 事件与业务领域之间的关系易于理解。例如,主题层次结构可能包含从业务单位到应用领域,再到行动的级别;您可能会看到像财务/工资/工资调整这样的层次结构。

  • 通过正确使用事件驱动架构,显式需要编排的需求被消除。复杂任务可以被编排并以高度可扩展和更健壮的方式一致完成。例如,在线商店的新订单可能会触发库存数据变化、支付或账单变化以及运输变化。库存或账单的任何失败都会导致运输取消。这一系列事件允许账单、库存和运输应用独立扩展并以异步方式执行。

  • 通过使用事件网格和定义明确的主题层次结构,消除了数据移动的障碍,因为数据可以在应用领域之间选择性流动,同时数据仍可以被治理和访问控制。企业级主题架构允许事件在应用领域之间流动。例如,营销应用可能希望接收运营事件,并且通过定义明确的主题架构和对访问控制的一些调整,这可以在运营无需进行任何更改的情况下发生。

  • 事件版本控制提供了多个好处:它降低了回归风险,使开发金丝雀应用实例成为可能,促进了蓝/绿部署场景,并使系统免受业务需求变化的影响。随着需求的变化,可以添加新的事件版本,即使需要进行非向后兼容的更改,也不会危及现有应用。

  • 在主题层次结构中添加属性允许细粒度过滤。这种过滤确保消费者只接收他们需要的事件。它还通过减少应用程序的工作负载并保持出站数据成本降低,确保系统更高效。

  • 多个主题级别允许您创建特定于级别的访问控制规则,并且可以包含主题通配符。这种灵活性使得在层次结构的每个级别上指定数据权益变得简单。例如,包含传感器ID在主题层次结构中允许访问控制列表使用替换表达式,确保一个传感器不能冒充来自另一个传感器的数据发布。

设计主题层次结构的最佳实践

设计良好的主题层次结构应遵循我们概述的主题结构。我们建议您在组织中的所有事件主题都遵循此结构。要了解如何将这些最佳实践付诸实践,请参见主题架构案例研究。

事件主题结构

定义主题层次结构的第一步是确定参与事件驱动微服务或事件驱动集成的业务对象。接下来,应确定可能发生在这些对象上的关键行动或状态变化。结合对象和行动通常被称为主题根,以及其他信息,如业务领域和事件的版本。然后,对于这些主题根,应确定特定于发生的对象或行动的可选属性。这些附加信息通常被称为事件的属性。您可以决定是否向主题中添加字段,考虑它是否对路由、应用过滤和执行访问控制有用。

考虑以下示例:

  • 如果我们为更新城市公交车位置的事件系统创建主题层次结构,我们可能有mobile/bus/locUpdate/v1/<routeNumber>/<busNumber>/<location>这样的主题层次结构。在这个示例中,routeNumberbusNumberlocation都是locUpdate行动的属性。公交车编号比路线编号多得多,所以busNumber跟在routeNumber之后。类似地,对于一辆公交车,位置比公交车数量多,所以location跟在busNumber之后。

  • 如果我们为更新每个城市的采购订单的事件系统构建主题层次结构,我们可能有store/order/created/v1/<locality>/<objectID>这样的层次结构。在这种情况下,objectID可以是采购订单或采购的SKU,位置可以从可能的城市列表中选择。在一个成功的业务中,通常销售比销售地点多,所以在这种情况下,objectID是变化性更大的属性。

事件主题部分

一个定义良好的事件主题有两个主要部分:

  • 事件主题根包含足够的信息来描述已发生的事件类型。每个事件主题根是一个描述事件类型的静态字段。事件主题根列表形成了可以生产和消费的事件目录。这个目录可以通过将所有事件添加到Designer来在PubSub+ Event Portal中捕获。每个事件主题根描述事件的细节,以将其映射到单个数据模式所需的程度。
  • 事件主题属性是可选字段,进一步描述特定事件。这部分的主题字段在生产者发布事件时动态填充。这些字段用于描述这个事件实例的具体或独特属性,这些属性可以用于路由和过滤。示例包括前一个城市巴士示例中的对象ID和位置,或前一个航空公司示例中的航班号、起点和目的地。这些字段通常也会编码在事件负载中,但只有对路由、过滤或治理有帮助的字段才应该包含在主题中。

虽然主题层次结构应该丰富,但也应该是简洁的。主题限制为最多250个字符,128个主题级别。包含领域、名词、动词和一个或多个重要属性的主题根应该使用字面值而不是变量,并应该是简洁的。我们建议您只包含对路由、过滤或治理有价值的属性在主题根字面值中。

事件主题根

事件的事件主题根应该具有以下形式:

Domain/ObjectType/Verb/Version/

事件主题根中的字段在以下表格中描述:

字段描述
Domain标识负责系统的组织元素。这应该描述系统的责任(例如operationsbooking),并清晰标识事件的所有者(例如ophr)。使用多个领域级别确保随着企业的发展,事件名称不会冲突,并确保可以为每个事件识别出清晰的数据所有者。
将领域信息编码到主题层次结构中,允许多个领域无缝共享相同的网格,这反过来允许领域之间的协作。它还允许多个业务单位共享相同的事件网格所需的治理,并为事件提供清晰的所有权,这对于事件的有效重用至关重要,并确保事件的可访问性、演变和数据质量。
领域应清晰标识事件的组织所有者,并应采取dataSystem/applicationDomain的形式(例如finance/payrollmarketing/rewards)。dataSystem应尽可能广泛,以实现事件重用,applicationDomain应区分同一部门内的不同潜在应用。组织名称可以以organizationName/dataSystem/applicationDomain的形式包括在内,以实现多供应商系统,并针对收购或合并保护领域。
示例:operations/flights, acme/rideshare/billing
ObjectType标识被操作的对象类型(称为名词)。这个对象可能非常通用,例如订单,或者如果需要为不同产品进行显著不同的处理,则可能更具体(例如,证券交易与共同基金)。
示例:customer, order, inventory, payment
Verb描述已采取的行动或对象状态的变化,可以是典型的CRUD操作或描述采取行动的另一个动词。字段的动词通常使用过去时。
示例:created, deleted, exceeded, pickedUp, paid
Version标识事件的版本。这个字段可以用于路由目的,并且对于区分主题层次结构或负载模式的主要更改是必要的。在主题层次结构中包含版本使得蓝/绿或金丝雀部署成为可能,例如,常规生产消费者可能订阅版本1,而金丝雀消费者订阅版本2。这个版本应该简单地采取v1v2等形式。
示例:v1, v2

事件主题属性

除了事件主题根之外,可以向事件主题层次结构添加属性,使主题更加细粒度。属性特定于每个用例,并不适用于所有用例。属性应该只包含在对订阅者过滤、事件路由或访问控制有用的情况下。一些常见的属性可能是位置信息、产品、客户ID或数据类型的指示符。您可以包含也在事件负载中存在的属性,尽管我们建议您只包含对路由、过滤或治理重要的属性应该包含在主题中。

事件主题属性应按基数排序:从最不具体最具体。考虑可能的事件主题属性,例如既编码了销售发生城市又编码了下达订单的订单ID的销售订单事件。企业运营的城市可能比下达的订单少,因此,在主题层次结构中城市应该出现在订单ID之前。

对于主题层次结构中的每个属性,还应考虑数据的结构。特别是,属性的价值空间应清晰记录,无论是自由形式的字符串、枚举、ID格式、整数还是浮点数。例如,如果事件主题属性中包含机场字段,机场代码的格式应记录以及有关如何找到应用程序使用的最更新机场列表的信息。

字段描述
ObjectID对被操作的对象实例的唯一标识符。可能有多个ObjectID,例如,如果客户下达订单,orderIdcustomerId都可能被包括在内。只有对路由、过滤或治理有帮助的ID才应被包括。
示例:orderID, sensorID, customerID, routeNumber, productSKU
ObjectID的一些用途可能包括订阅接收特定路线编号的所有通知,或使用访问控制只允许物联网传感器发布更新它们自己的sensorID
Locality事件发生的地理或结构位置。本地可能是一个级别,如国家、地区或换乘站号码,或者可能需要多个级别,如纬度/经度、地区/位置、起点/目的地等。
根据本地的具体程度,它可能被包括在属性部分的开始(例如,国家代码如US或UK),或者它可能出现在属性部分的末尾(例如,坐标如纬度和经度)。
本地的一些用途可能是用于地理围栏(通过纬度和经度接收数据),或确保基于区域信息尊重数据主权法律。
Category通常,数据属于某些类别。这些类别可能不足以在主题根中描述的ObjectType字段中区分,但仍然可能是主题中的有价值信息。例如,拼车应用可能有各种车辆类型,或者对于某些产品可能有不同的后续行动,而不是其他产品。
Category的一些用途可能是只接收某个数据子集的更新,而不是整个集合,例如为产品的某个方面提供报告。
HandlingInstruction(高级)在高级用例中,可能需要在事件主题中包含有关负载或事件重要性的额外处理信息,以便进行路由和过滤决策。
例如,如果您的系统通过将数据源转换为事件流的连接器将事件发布到事件网格,这些连接器具有不同的编码类型,如jsonprotobuf,可能需要在事件主题中包含这些信息以区分两者。如果基于编码的应用实例分别处理事件,处理指令可能很有用。
如果需要根据编码分别对不同的处理进行版本控制,此字段可能被提升到事件主题根(例如,在版本字段之前)。例如,如果json编码可能独立于protobuf编码进行更改,json编码可以在不影响protobuf消费者的情况下进行版本提升。
如果HandlingInstructions的唯一用户是指导消费者如何处理事件,请考虑使用消息属性。HandlingInstructions只应该在对路由、过滤或治理有帮助,或者在无法使用消息属性的情况下包含。例如,当使用不支持消息属性的现成连接器时。

完整的事件主题

将事件主题根和事件主题属性结合起来,就创建了一个描述事件的主题,该主题由从最不具体到最具体的一系列字段组成。 这导致了一个主题模板:

Domain/ObjectType/Verb/Version/Properties...

请记住,这些是需要根据每个个别用例定制的最佳实践,任何级别都可以跳过或扩展到多个级别,如果有路由、过滤或访问控制的好处。在决定哪些字段包含或从主题架构中省略之后,必须在对象类型领域中一致地应用。主题的级别必须系统地使用并且具有不变的含义,否则路由和过滤将提供不一致的结果。此外,不要在主题结构的根部吝啬,特别是领域;在层次结构中过早地过于具体通常会导致主题看起来与上下文脱节,并且可以使系统的合并变得非常困难。

记录事件

每个事件都应该有良好的文档记录,每个单独的主题级别都应该在数据格式方面有明确的定义。以这种方式记录事件通常被称为编目。编目事件的好处是清楚地说明事件上有哪些属性可用,以及每个单独属性的功能。事件发现也很重要,它允许现有事件被新应用重用。所有这些都可以在使用PubSub+ Event Portal完成。

对于每个事件主题级别,值空间应该清晰定义(例如,整数、浮点数、枚举或字符串格式)。例如,属性可能包含机场代码,可能的代码列表应该枚举或链接到真理来源。此外,组织内的事件应该遵循一致的模式,例如,在事件主题架构中的所有命名都使用camelCase。推荐使用camelCase,因为snake_case需要额外的字符来表达相同的语义。这些决策应该被记录,以便系统中的所有事件都遵循一致的格式。

订阅异常

PubSub+事件代理支持以感叹号!为前缀的负订阅,这可以从更大的订阅集中排除指定的主题。考虑需要调试特定航班号数据的情况。可能有助于有一个应用实例处理特定航班号EX1234,另一个应用实例处理所有其他航班号。处理特定航班的应用实例可以订阅flight/ex1234,处理所有其他航班的应用实例可以订阅flight/*并使用订阅!flight/ex1234排除航班EX1234。

如果您正在设计一个始终需要特殊处理的解决方案,最佳实践是有一个单独的主题级别来编码属性。这确保了订阅者不需要紧密协调以防止丢失数据。我们还建议在使用负订阅的客户端配置文件上启用reject-msg-to-sender-on-no-subscription-match,以确保排除的消息不会丢失。

仅支持保证消息传递的订阅异常。有关更多信息,请参见系统级订阅异常配置。

空值

可能有时候主题级别可能是空的。如果这种情况发生在您的分类中,首先考虑该字段是否仍然对路由、访问控制或治理有用,如果没用,就简单地从分类中移除该级别。如果仍然有价值,考虑是否可以使用合理的默认值。如果没有,应该定义空值,这样您的应用就可以订阅包含该值的任何主题,并且可以对主题应用访问控制和治理配置。

选择空值可能就像选择一个合理的默认值一样简单,通常被称为_哨兵_值。一些哨兵值的示例包括:

  • 对于数字字段,默认为0
  • 为枚举定义默认值
  • 当没有客户登录时,默认客户ID为0000

自由形式的字符串是最复杂的,需要逐个案例考虑。_null_是一个很好的起点,如果_不能自然出现在字段的价值空间中。重要的是要注意用户输入,在这种情况下,并确保输入不能与空值重叠(例如,_必须是用户输入中的无效字符)。应避免在所选的空值中使用SMF中的特殊字符,如#_*>

无论选择什么值,无论是哨兵还是特殊的空值,都必须与事件一起记录,作为字段的可能值。

主题架构反模式

在设计主题架构时,有一些模式应该避免。概述了一些常见的反模式。

不要使用消息属性进行过滤

许多旧的消息系统依赖于功能,如JMS选择器,根据用户定义的消息属性对消费者提供的数据进行过滤。PubSub+对选择器有旧的支持,但是基于选择器的过滤是一个反模式。选择器会导致许多架构问题,例如将过滤责任与事件内容混合,使事件模式复杂化。此外,基于消息属性的访问控制、路由和治理决策无法做出。通常,应避免使用选择器,并将路由、过滤或治理信息编码到主题层次结构中。消息属性应该只用于向事件的消费者提供有关事件的元数据。

不要在主题层次结构中包含追踪信息以进行追踪用例

事件的追踪信息,如TraceID、SpanID或任何其他追踪数据,不应包含在主题层次结构中。相反,应使用Solace对分布式追踪的原生支持以及追踪后端来启用追踪用例。在主题层次结构中包含TraceID会占用主题字符串中宝贵的空间,并提供比Solace分布式追踪更少的可见性。TraceID对路由、过滤或治理也没有用,因为它是一个任意值,每个消息都会变化。

不要在主题层次结构中包含部署环境名称

不应在主题层次结构中使用环境设计ator,如devqaprod。如果您在主题层次结构中包含环境信息,它会导致几个问题:

  • 迫使您的应用程序代码在从开发环境推广到预生产或生产环境时发生变化。至少,您需要一个环境变量或另一种方式,以便发布者在主题中放入正确的环境,这意味着您的配置不会是静态的。
  • 要求您在许多领域更改配置,如访问控制列表(ACL)、主题订阅、消息重放配置、复制配置、分布式追踪等。因此,您的基础设施作为代码和CI/CD管道可能偏离最佳实践,并需要根据目标环境进行修改。
  • 防止您使用PubSub+ Event Portal跟踪事件跨开发环境的推广。重要的是要认识到Event Portal符合行业接受的基础设施作为代码方法,例如,您在开发环境中推广单一工件。

不要在主题中包含空格或其他特殊字符

避免在主题分类中使用非字母数字字符,如空格和特殊字符。包含空格会使主题更难阅读,也使主题的可重用性降低,并不必要地占用字符。此外,*>!应避免在任何发布主题上使用,因为它们在您发布主题时被视为字面值,但在主题订阅中被视为特殊字符。因此,当主题包含这些字符时,订阅事件非常困难。

出于类似的原因,应避免使用snake_case,因为使用分隔符不如其他命名约定高效。因此,您应该使用PascalCase或camelCase。