成为程序员时,我学到了几种设计模式:单例模式,存储库,工厂,构建者,装饰者等。设计模式为我们提供了一个经过验证的解决方案,以解决现有的和重复出现的问题。我没有学到的是,类似的机制存在于更高层次上:软件架构模式。这些是用于整个应用程序或应用程序布局的模式。它们都有优点和缺点。它们都解决了具体问题。下面我们一一分析:
分层模式
分层模式可能是最着名的软件体系结构模式之一。许多开发人员在不知道名称的情况下使用它。我们的想法是将您的代码拆分为“层”,其中每个层都有一定的责任,并为更高层提供服务。
没有预定义数量的图层,但这些图层是您经常看到的图层:
Presentation(展示或UI层)
Application(应用层)
Business(业务或管理层)
Persistence(持久性或数据访问层)
Database(数据库层)
这个想法是,用户通过执行一些操作(例如点击一个按钮)来启动表示层中的一段代码。表示层然后调用底层,即应用层。然后我们进入业务层,最后,持久层将所有内容存储在数据库中。所以更高层依赖于并且调用较低层。
根据应用程序的复杂程度,您将看到相应的变体。一些应用程序可能会忽略应用程序层,而其他应用程序则会添加一个缓存层。甚至可以将两个层合并为一个。例如,ActiveRecord模式结合了业务层和持久层。
每层责任
如前所述,每一层都有自己的责任。表示层包含应用程序的图形设计,以及处理用户交互的任何代码。您不应该在此图层中添加不特定于用户界面的逻辑。
业务层是您将模型和逻辑特定于您要解决的业务问题的地方。
应用程序层位于表示层和业务层之间。一方面,它提供了一种抽象,以便表示层不需要知道业务层。理论上,您可以更改表示层的技术堆栈,而无需更改应用程序中的任何其他内容(例如,从WinForms更改为WPF)。另一方面,应用程序层提供了放置不适合业务或表示层的某些协调逻辑的位置。
最后,持久层包含访问数据库层的代码。数据库层是底层数据库技术(例如SQL Server,MongoDB)。持久层是操纵数据库的代码集合:SQL语句,连接细节等。
优点
大多数开发人员都熟悉这种模式。
它提供了一种编写组织良好,可测试的应用程序的简单方法。
缺点
它往往会导致单片应用程序之后很难分开。
开发人员经常发现自己编写了很多代码来通过不同的图层,而没有在这些图层中添加任何值。如果你所做的只是编写一个简单的CRUD应用程序,那么分层模式可能对你来说太过分了。
适合场景
标准业务线应用程序,不仅仅是CRUD(写读改删)操作
微内核
当您的应用程序具有一组核心职责和一组可互换部分时,微内核模式或插件模式非常有用。微内核将提供应用程序的入口点和一般流程,而不知道不同的插件正在做什么。
一个例子是任务调度器。微内核可以包含用于调度和触发任务的所有逻辑,而插件包含特定任务。只要插件遵循预定义的API,微内核就可以触发它们,而无需了解实现细节。
另一个例子是工作流程。工作流的实现包含诸如不同步骤的顺序,评估步骤结果,决定下一步是什么等概念。步骤的具体实现对于工作流的核心代码来说并不重要。
优点
这种模式提供了很大的灵活性和可扩展性。
某些实现允许在应用程序运行时添加插件。
微内核和插件可以由独立的团队开发。
缺点
决定什么属于微内核,哪些不属于微内核是很困难的。
预定义的API可能不适合未来的插件。
适合场景
从不同来源获取数据的应用程序,转换该数据并将其写入不同的目标
工作流应用程序
任务和作业调度应用程序
CQRS
CQRS是命令和查询责任分离(Command and Query Responsibility Segregation)的首字母缩略词。这种模式的核心概念是应用程序具有必须完全分离的读取操作和写入操作。这也意味着用于写操作(命令)的模型将与读模型(查询)不同。此外,数据将存储在不同的位置。在关系数据库中,这意味着将有用于命令模型的表格和用于读取模型的表格。一些实现甚至将不同模型存储在完全不同的数据库中,例如命令模型的SQL Server和读取模型的MongoDB。
这种模式通常与事件采购相结合,我们将在下面介绍。
它是如何工作的?当用户执行操作时,应用程序会向命令服务发送命令。命令服务从命令数据库中检索它需要的所有数据,进行必要的操作并将其存储回数据库中。然后它通知读取服务,以便可以更新读取模型。这个流程如下所示。
当应用程序需要向用户显示数据时,它可以通过调用读取服务来检索读取的模型,如下所示。
优点
命令模型可以专注于业务逻辑和验证,而读取模型可以针对特定场景量身定制。
您可以避免复杂的查询(例如,SQL中的连接),这使得读取更加高效。
缺点
保持命令和读取模型同步可能会变得复杂。
适合场景
期望大量读取的应用程序
复杂域的应用程序
事件采购
正如我上面提到的,CQRS通常与事件采购密切相关。这是一种模式,它不会将模型的当前状态存储在数据库中,而是存储模型中发生的事件。因此,当客户名称发生变化时,您不会将该值存储在“名称”列中。你将存储一个带有新值的“NameChanged”事件(也可能是旧的)。
当您需要检索模型时,您将检索其存储的所有事件并在新对象上重新应用它们。我们称之为补水对象。
事件采购的现实类比是会计。当您添加费用时,您不会更改总额的值。在会计中,会添加一条新行,并执行操作。如果发生错误,只需添加一个新行。为了让您的生活更轻松,您可以在每次添加线路时计算总计。这个总数可以看作是读取模型。下面的例子应该更清楚。
您可以看到我们在添加发票201805时发生了错误。我们添加了两条新线,而不是更改线:首先,一条线用于取消错误线,然后是一条新的正确线。这就是事件采购的工作方式。你永远不会删除事件,因为它们在过去不可否认。为了纠正情况,我们添加了新事件。
另外,请注意我们如何拥有总值的单元格。这只是上面单元格中所有值的总和。在Excel中,它会自动更新,因此您可以说它与其他单元格同步。它是阅读模型,为用户提供了一个简单的视图。
事件采购通常与CQRS结合使用,因为重新水化对象可能会对性能产生影响,特别是当实例有很多事件时。快速阅读模式可以显着提高应用程序的响应时间。
优点
该软件体系结构模式可以提供开箱即用的审计日志。每个事件表示在某个时间点对数据的操纵。
缺点
它需要一些纪律,因为你不能用数据库中的简单编辑修正错误的数据。
改变事件结构并不是一件容易的事情。例如,如果添加属性,则数据库仍包含没有该数据的事件。您的代码需要慷慨地处理这些缺失的数据。
适合场景
需要将事件发布到外部系统
将用CQRS构建
需要对数据进行更改的审计日志
微服务
当您将应用程序编写为一组微型服务时,实际上是在编写可以一起工作的多个应用程序。每个微服务都有自己独特的责任,团队可以独立于其他微服务开发它们。他们之间唯一的依赖是沟通。由于微服务彼此通信,因此您必须确保它们之间发送的消息保持向后兼容。这需要一些协调,特别是当不同的团队负责不同的微服务时。
一张图可以解释。
在上图中,应用程序调用一个中央API,将呼叫转发到正确的微服务。在本例中,用户配置文件,库存,订单和付款有单独的服务。你可以想象这是一个应用程序,用户可以订购一些东西。单独的微服务也可以互相调用。例如,支付服务可以在支付成功时通知订单服务。订单服务可以调用库存服务来调整库存。
目前还没有明确规定微服务的规模。在前面的示例中,用户配置文件服务可能负责数据,如用户的用户名和密码,还包括家庭地址,头像图片,收藏夹等。还可以将所有这些责任分成更小的一个选项微服务。
优点
您可以分别编写,维护和部署每个微服务。
微服务架构应该更容易扩展,因为您只能扩展需要扩展的微服务。没有必要扩展应用程序中不常使用的部分。
重写应用程序的部分更容易,因为它们更小并且与其他部分的耦合更少。
缺点
与您所期望的相反,开始编写结构良好的巨集实际上更容易,稍后将其分解为微服务。随着微服务的出现,许多额外的问题开始发挥作用:沟通,协调,向后兼容性,日志记录等等。错过编写结构良好的巨集的必要技能的团队可能很难写出一套很好的微服务。
用户的单个动作可以通过多个微服务。还有更多的失败点,当出现问题时,可能需要更多时间来查明问题。
适合:
应用程序某些部分将被密集使用并需要缩放
为其他几个应用程序提供功能的服务
如果组合成一个整体,那么应用程序将变得非常复杂
应用程序可以定义明确的有界上下文
总结
上面列出软件开发中的几种软件架构模式,以及它们的优点和缺点。但是有比这里列出的更多的模式。将这些模式中的几种结合起来也并不罕见。它们并不总是相互排斥的。例如,您可以拥有多个微服务,其中一些使用分层模式,而其他使用CQRS和事件源。
需要记住的重要一点是,没有一种解决方案可以在任何地方使用。当我们提出应用程序使用哪种模式的问题时,古老的答案仍然适用:“这取决于你的项目需求!”。您应该权衡解决方案的优缺点,并做出明智的决定。
领取专属 10元无门槛券
私享最新 技术干货