我们是如何设计并将通知系统集成到我们的基础架构中的。
什么是面向企业的通知?
毫无疑问,没有任何数字化企业可以在没有通知的情况下生存。通过通过电子邮件、短信或推送通知等各种渠道发送及时通知,企业可以提高客户参与度和留存率,促使重复购买。通知还为企业提供了一个机会,可以收集客户反馈和见解,这可以用于改善其产品或服务。
最终,一个设计良好的通知服务可以帮助企业与客户建立牢固持久的关系,从而实现长期的成功。因此,无论何时,所有产品都需要为技术提供通知集成的要求。
规划集成
由于通知的功能性/非功能性要求在公司/企业之间基本相似,并且每个人都了解什么是通知功能,因此我们可以迅速记录一些业务需求,并立即开始讨论技术方面的问题。
1.从业务角度看,系统中发生的任何事情都可以触发通知。2.要发送的通知可以定向到一个或多个接收者或一组接收者。3.我们需要支持不同的通知渠道:电子邮件、短信、Android/iOS设备的推送、应用内通知。4.通知应该是本地化的,必须能够支持不同的语言和时区。5.用户应该能够访问他们以前/旧的通知(也称为通知中心)。6.一些推送通知或电子邮件通知不应该在用户的通知中心中被记录/显示(例如有关折扣的营销警报或其他短期警报),反之亦然,一些在用户的通知中心中可见的通知不应该生成警报,如推送通知(或已发送电子邮件)。7.用户应该能够自定义通知的消耗方式(频率、类别、渠道等),例如为某种类型的通知设置首选渠道,甚至关闭它。
因此,上述要求是大多数公司中关于通知的基本业务需求。我们需要设计一个满足这些需求的服务/基础架构,并通过满足一些直接影响整体开发的关键技术需求来鼓励无缝的开发过程。其中一些是:
1.设置代码应易于理解/维护。2.集成新通知简单、快速和直接,无需修改任何现有代码,只需按照用于先前通知的模式/约定进行扩展即可。3.更改现有通知很容易,而不会影响其他部分(例如,删除特定类型的推送通道,而无需触及任何设置代码,而不会影响其他通知)。4.轻松添加新的通道/提供商的方法(例如,浏览器通知、WhatsApp消息)。5.一种非常简单的方式来添加/编辑特定通知的新翻译(本地化)。并且快速甚至立即反映新的翻译到工作系统中。6.添加用户自定义通知设置不是一件头痛的事情。7.整体服务易于测试,因此覆盖集成/后端端到端测试不会耗费太多时间。8.易于调试特定情况。
现在让我们进入技术设计的详细说明
由于我们的基础架构由多个小型服务组成,每个服务都在整个系统中具有清晰的职责,因此我们的通知系统也将是一个或几个小型服务,通过同步和异步方法与其他服务进行通信。至少,我们将需要一个后端服务来处理触发器并执行与通知相关的核心操作。
系统设计
主要而言,任何通知都将始于一个事件,无论是来自业务服务还是特殊的事件服务。事件本身将被发布到事件总线中。稍后,通知服务(核心服务)将获取通知需要的数据,使用事件提供的数据。然后,它将使用准备/映射的数据来构建一个通知案例,这是处理通知的声明性机制。
在发送通知之前,它应该被“转换”为特定提供程序/通道期望的一种内容/有效载荷类型。为此,我们使用模板化服务来获取根据特定模板使用数据(参数)和用户语言填充的电子邮件内容。
对于推送通知或短信通知,这更容易,因为它只是一个文本。因此,我们使用用户语言简单地获取翻译文本并替换参数/属性。
在翻译方面,每次调用本地化服务都没有意义,因此解决方案可能是临时在每个通知服务副本中本地持久化与通知相关的翻译,并通过简单地轮询以获取本地化服务中的新更改来维护更新。或者我们可以采用基于事件的方法。或者通过维护的分布式缓存机制也可以轻松解决此问题。
在我们拥有内容/有效载荷之后,通知服务只需通过发送它来向提供程序(通道提供程序)发出 HTTP 调用,然后铃铃铃,我们得到了推送通知。
此外,通知服务将为用户公开端点,以便获取其通知,将这些标记为已读/未读等。它还将为管理应用程序公开另一组端点,用于管理通知、相关配置等。或者甚至用于创建营销通知,例如警示整个商店折扣。
基础架构中的通知块将如下图所示:
微服务中的通知系统
在我们确定了高层架构之后,就是深入研究特定组件并通过更具体的设计决策来使这些组件更加清晰。我们可以决定这个服务将使用哪种编程语言。此外,数据库用于存储数据。为了利用严格的模式(在这里我们没有任何任意的数据,一切都是预先决定的)、关系、约束和 ACID,我们选择了关系数据库,如 MySQL 或 PostgreSQL。
服务架构
现在让我们设计实体及其关系。
微服务实体关系图
上述 ERD 可能会稍微更改以根据公司的需求进行定制。但是,它很简单,已规范化到 5NF,不仅是一个起点,而且是一个最终的模式,只要我们没有可能影响 ERD 的特定要求。当然,我们存在一些冗余,但这不是在服务/数据库级别上,而是在基础架构级别上。例如,这里有一个用户表的小副本,这在微服务中是完全正常的。另一个注意点是,我们不想为用户记录生成主键(PKs),而是使用我们在用户服务中的主用户表中具有的相同 UUID(因为我们知道如果用户未注册,即用户没有在用户服务中的记录,他将不会在此处记录)。在我们的情况下,这种方法的优点大于缺点。
现在让我们更详细地描述服务。
消费者
通知服务有特定于领域的消费者,例如订单消费者,它具有订单相关事件的消费者处理程序。当触发它时,它调用特定于领域的服务,如订单服务。
服务
订单服务构建一个名为 Case(通知案例)的实体。订单服务还可以根据需要与其他微服务进行同步通信,以获取构建特定通知案例所需的更多数据。
案例
一个 Case 是一个实体,表示一个特定的通知案例,它有一个类型,这是一个具体的通知类型。每个案例都继承了一个称为 BaseCase 的类,它是一个包含一些可重用逻辑、一些抽象属性和方法的抽象类。因此,每个案例都必须在被视为完整通知案例之前实现一些基本要求。在每次触发通知时,都会创建 Case 的新实例,该实例表示发送给一个或多个接收者的特定通知,以及有关如何处理此确切通知的一些指令。它已准备好由通知引擎处理。
通知引擎
它是一个小引擎,知道如何处理任何通知案例。我们可以说通知引擎知道如何做,但不知道要做什么。但通知案例对如何做没有任何想法/指示,但知道应该做什么。因此,案例与引擎的协作完成了整个工作。通知引擎是一个小型处理器,可以执行与通知相关的所有例程,例如调用通知服务以在数据库中创建通知和通知接收者记录;调用模板化和翻译服务以准备通知有效载荷;调用通知传输模块(通道提供商)以发送实际通知(如果需要);甚至知道如何处理可能在流程中发生的常见异常。
通过采用这种架构,我们可以编写尽可能少的代码来创建新的通知。这就像只需添加特定事件的消费者并创建一个新的通知 Case 类,这就是为一个通知添加更多通知的全部工作。
模板化模块
基本上是一个用于模板化服务的 HTTP 提供程序。它使用 HTTP 请求根据特定模板使用数据(参数)和用户语言构建电子邮件内容。
对于推送通知,它接受翻译后的文本和属性,并返回准备发送的有效载荷。
本地化模块
此模块具有两个关键功能:
1.从本地化微服务请求完整的翻译并维护它。有几种解决方案可以解决翻译的维护问题,如上述,通过轮询获取新的翻译更新、基于事件的更新,或分布式缓存。所有方法都有其优缺点。2.翻译通知类型。通知案例的翻译变得很容易,因为每个通知案例都有一个通知类型,并且通知翻译键可以通过遵循使用通知类型的约定来创建。例如,`app.notifications.cases..titleapp.notifications.cases..body`。因此,现在我们可以通过使用此键来获取任何通知案例的正文翻译。
用户模块
它是公司用户的一个小副本。包括为了发送电子邮件而进行的相同 id,以及用户类型。它还可以具有一些对于通知服务非常重要的额外字段。
对于推送通知,每个用户可以有一个或多个设备。设备是根据接收者 id(用户 id)在需要构建接收者的通知时获取的,并且也用于通过设备令牌发送通知。
通道提供商
这是处理引擎的最后一步,因此它只需调用 provider.send(payload, device)
。我们创建自定义客户端,例如,对于 APNS 调用,我们使用 http2 和 p12 证书创建 APNS 客户端,该客户端知道如何发送和处理来自 APNS 的响应,同时发送推送通知[1]。对于电子邮件或 Android 通知也是类似。这样便宜又可靠。正是我们想要的。
暴露的 API
我们公开了两种类型的 API:
1.针对客户(常规用户)至少要? 发送设备令牌和其他设备相关信息? 列出通知? 将一个或多个通知标记为已读/未读? 更改与通知相关的首选项2.针对“管理员”用户以? 管理通知? 管理各种配置? 发送/操作新/现有通知? 监视
通知核心服务的简要图示如下:??
通知服务架构
??构建通知系统可能会很复杂,但通过采用微服务架构并在识别和隔离每个微服务时做出正确的决策,可以大大减少代码重复并增加系统的模块化和可扩展性。
将微服务架构视为可组合的构建块是关键,
通过清晰定义各个组件的职责和交互方式,可以在系统中保持良好的解耦,使系统更容易维护、扩展和创新。
引用链接
[1]
发送推送通知: https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns
原创文章,作者:小技术君,如若转载,请注明出处:https://www.sudun.com/ask/33896.html