c#mvc模式进行crud
在微服务开发中,有几种处理数据的模式。 最熟悉的一种是CRUD模式。
在这种模式下,可以对任何数据集(创建,读取,更新和删除)执行的基本数据操作将作为访问数据的接口传递到应用程序级别。 此模式适用于大量用例,并已得到广泛实施。
但是,这并不适合所有情况。 在某些情况下,您可能需要隔离读写模型并分别进行处理。
这可以通过遵循CQRS(命令查询责任隔离)模式来实现。 CQRS通常与事件源模式一起使用,事件源模式可将数据集上所做的突变作为离散事件进行跟踪。
这样,您可以随时获得有关解决方案当前状态的所有信息。
在本文中,我们将研究每种模式的工作原理,并进行案例研究以展示其功能和差异。 我们将使用Ballerina编程语言来实现这些方案,以显示其广泛的功能如何协助微服务的开发。
欺诈
CRUD模式基本上通过掩盖我们对数据存储所执行的操作来对我们的服务操作进行建模。 我们将这些操作直接作为服务接口公开。 此模式主要适用于简单的业务域,并且在域实体及其操作之间没有复杂的关系。 为了更好地理解该概念并分析模式,让我们看一下实现学校学生注册处操作的简单方案。
案例研究:学生注册表
该系统所需的用例很简单。 您需要能够添加新学生,更新学生信息,获取学生信息以及从系统中删除学生信息。 我们将使用关系数据库来创建数据模型。 让我们看一下ER(实体/关系)模型的样子。
图1: 学生数据库ER图
在这里,我们在数据库中仅存在一个表示Student实体的关系。 让我们看看如何基于上面的Student表创建一个服务来公开Student Registry功能。
清单1: 学生注册服务-创建/读取功能
清单1显示了如何在我们的CRUD服务中实现创建和读取操作。 资源addStudent
和getStudent
分别映射到POST
和GET
HTTP方法。 在这里,我们执行相应的SQL INSERT
和SELECT
查询,以实现所需的数据库查询。 另外,一个额外的getStudents
资源被映射到GET
操作,以读取数据库中的所有学生信息。 以类似的方式,我们可以进一步实现更新和删除操作。
清单2: 学生注册服务-更新/删除功能
清单2显示了如何将两个HTTP资源映射到PUT
和DELETE
操作,以分别更新和删除学生记录。
样本执行
通过构建Ballerina项目,运行它并为每个资源调用发送相应的HTTP请求,我们可以看到上述服务的运行情况。
建立
$ ballerina build -a
Compilingsource
laf/student_registry:0.1.0
Creating balos
target/balo/student_registry-2020r1-java8-0.1.0.balo
Running Tests
laf/student_registry:0.1.0
No tests found
Generating executables
target/bin/student_registry.jar
数据库创建
$ mysql -u <user> -p < student_registry.sql
服务部署
$ ballerina run target/bin/student_registry.jar
[ballerina/http] started HTTP/WS listener 0.0.0.0:8080
服务调用请求
- 添加学生记录
$ curl -d'{ "name": "Paul Smith", "birthYear": 1982, "address": "3993, N. First Street, San Jose 95100" }' http://localhost:8080/StudentRegistry/student
{ "id" : "2b8f75ad-4a04-40e8-9204-bebf799310be" , "name" : "Paul Smith" , "birthYear" :1982, "address" : "3993, N. First Street, San Jose 95100" }
$ curl -d '{ "name": "Jack Dawson", "birthYear": 1990, "address": "333 Valley Farms Street Hicksville, NY 11801" }' http://localhost:8080/StudentRegistry/student
{ "id" : "399979df-a08a-42f8-a255-59b110890cbf" , "name" : "Jack Dawson" , "birthYear" :1990, "address" : "333 Valley Farms Street Hicksville, NY 11801" }
- 查找学生记录
$ curl http://localhost:8080/StudentRegistry/student/399979df-a08a-42f8-a255-59b110890cbf
[{"id" : "399979df-a08a-42f8-a255-59b110890cbf" , "name" : "Jack Dawson" , "birthYear" :1990, "address" : "333 Valley Farms Street Hicksville, NY 11801" }]
$ curl http://localhost:8080/StudentRegistry/student/
[{ "id" : "2b8f75ad-4a04-40e8-9204-bebf799310be" , "name" : "Paul Smith" , "birthYear" :1982, "address" : "3993, N. First Street, San Jose 95100" }, { "id" : "399979df-a08a-42f8-a255-59b110890cbf" , "name" : "Jack Dawson" , "birthYear" :1990, "address" : "333 Valley Farms Street Hicksville, NY 11801" }]
- 更新学生记录
$ curl -X PUT -d'{ "name": "Jack Dawson", "birthYear": 1990, "address": "7403 East Hill Field Lane Battle Ground, WA 98604" }' http://localhost:8080/StudentRegistry/student/399979df-a08a-42f8-a255-59b110890cbf
$ curl http://localhost:8080/StudentRegistry/student/399979df-a08a-42f8-a255-59b110890cbf
[{ "id" : "399979df-a08a-42f8-a255-59b110890cbf" , "name" : "Jack Dawson" , "birthYear" :1990, "address" : "7403 East Hill Field Lane Battle Ground, WA 98604" }]
- 删除学生记录
$ curl -X DELETE http://localhost:8080/StudentRegistry/student/399979df-a08a-42f8-a255-59b110890cbf
$ curl http://localhost:8080/StudentRegistry/student/
[{"id" : "2b8f75ad-4a04-40e8-9204-bebf799310be" , "name" : "Paul Smith" , "birthYear" :1982, "address" : "3993, N. First Street, San Jose 95100" }]
业务领域模型与数据模型
在上面的简单CRUD模式示例中,我们看到了如何在读写中使用相同的数据模型。 这并没有什么不寻常的,这是一种访问和处理数据的简便方法。 我们还想要什么,对吗? 好吧,这个故事还有更多。 让我们扩大学生注册表的范围,使其包括课程注册。 这将导致以下ER图。
图2: 学生/课程数据库ER图
如图2所示,新的课程与学生的关联创建了两个新表,一个用于课程信息,另一个用于在学生与课程之间建立多对多关系的映射表。 因此,使用CRUD模式,我们如何创建从学生到课程的链接? 我们可以为StudentCourse
表创建一组CRUD操作,例如addStudentCourse
和deleteStudentCourse
。 但是,我们真正需要说的是enrollInCourse
和leaveCourse
。 我们从服务中需要的操作应与业务领域模型而非数据模型保持一致。 数据模型只是服务内部的实现细节。 服务的使用者不需要知道这一点。
服务接口的清晰性是我们使操作与域模型的行为保持一致时所获得的好处之一。 这也迫使我们遵循业务领域所需的最佳数据操作设计。 例如,在需要在单个操作和事务中更新多个表的情况下,如果我们仅对单独的表进行CRUD操作,则这将非常复杂,并且我们将必须实现一种在服务之间传播事务上下文的机制。电话。
我们较早的CRUD服务恰好在某种程度上与添加学生,更新学生信息和删除学生的业务领域相对应。 但话又说回来,将它们称为registerStudent
, updateAddress
等更为合适。 此外,删除学生是什么意思? 每当我们更新时,特别是在删除条目时,我们都可能丢失有关系统的有价值的信息,而先前所有系统状态都将丢失。 一种更合适的操作是标记学生的毕业状态。 与此类似,会计师将账目追加到分类帐,他们从不更新或删除较早的账目。 如果在较早的条目中出错,则将单独的补偿条目添加到分类帐的末尾。
同样,当我们直接使用数据模型时,我们会失去上下文或执行操作的意图 。 例如,在删除学生记录的情况下,我们必须提出以下问题:学生是否被开除,中途退学或毕业? 学生根本没有就读这所学校吗? 因此,在许多情况下,坚持行动的意图很重要,尤其是在具有法规以对系统中完成的行动提供审计跟踪的行业中。
使用CQRS实现了一种基于域建模的数据访问模式。 让我们看一下如何以实际方式实现和使用它。
CQRS
CQRS模式听起来很复杂,但实际上并非如此。 它只是谈论分离用于写入和读取的数据模型。 甚至在该术语发明之前,你们中的许多人就有很大的机会使用了这种模式。 但是,与任何模式一样,如果您真的想使其复杂,则可以在错误的情况下使用它,或以错误的方式实现它。
图3: CQRS数据模型操作
CQRS中的写操作称为命令,它们是业务域操作的直接表示。 因此,命令将在系统中封装业务操作的上下文/意图。 读操作作为对系统的查询而完成,严格来说是只读操作,并且不会修改系统状态。 这仅返回系统要使用的数据传输对象(DTO)。 由于数据模型的这种分离,我们将能够优化各自领域中的每组数据操作。 通过从数据库中创建读取操作可以使用的实例化视图,可以执行常见的查询模式。
读/写数据模型分离
在CQRS中,生成概念是读写模型的分离使我们可以为每个数据模型使用单独的数据库。 甚至可以使用两种不同的数据库类型。 我们的大多数用例都要求写操作要比读操作的数量低得多。 因此,一般指示我们可以使用
- 写优化模型,例如用于命令的高度规范化关系数据库
- 读取的优化非规范化数据库,例如用于查询的文档数据库。
此外,在单独的数据库中进行读取和写入可能有助于避免可能发生的锁争用和合并冲突。
但是话又说回来,没有免费的午餐了。 当我们分离读/写节点时,写节点仍然应该有一种方法可以将数据同步到读副本。 因此,最后,读取节点仍将获得与各个写入节点相同的写入流量。 同样,考虑到读写节点的分离以及同步是异步进行的,读写模型将不会始终保持一致。 因此,这将在系统中生成总体上最终一致的数据模型。 在大多数情况下,这很好,但是在某些情况下,写入模型的更改必须由读取模型立即可见。 例如,在购物车中,如果我们向购物车中添加商品并立即进行结帐,则购物车应显示我们之前放入的所有商品,否则,用户将认为这是错误情况。
让我们假设对读/写模型使用不同类型的数据库。
图4: CQRS数据模型操作
在这里,我们使用基于SQL的关系数据库进行写入,并使用文档数据库进行读取。 发出命令后,将在关系数据库上完成写操作。 我们必须确保将类似的消息发送到文档数据库以进行同步。 可以使用数据库之间的消息队列将其直接作为同步或异步操作来完成。 无论哪种方式,我们都需要在两个数据库之间或关系数据库与消息队列之间创建分布式事务,以确保保留整体数据一致性。 分布式事务几乎总是坏消息,应尽可能避免使用。 我们期望系统进行的任何性能改进都将被事务处理所产生的开销所抵消。 更不用说增加了系统的整体复杂性,这使得对系统进行测试和故障排除变得更加困难。
保持简单-参考实施
为了构建一个易于维护的CQRS系统,同时提供我们所需的特性,让我们对基于具有只读副本的单个MySQL数据库服务器的系统进行建模。 单个主数据库服务器将接受写请求。 然后,我们有多个只读副本,可以在其中配置完全同步复制,以便在需要时避免最终的一致性行为。
图5: 参考实现-具有只读副本和实例化视图的CQRS
如图5所示,我们在主要MySQL节点中运行命令,它负责将更改传播到只读副本的细节。 根据查询的负载要求,我们可以根据需要扩展任意数量的只读副本。 另外,我们在只读副本中创建实例化视图,以便在规范化数据库中预先执行复杂SQL操作,并为结果数据创建非规范化视图。 这些物化视图基本上充当查询的只读缓存,并将提供最佳性能。 当然,物化视图以最终一致的方式使用,因此应在合适的用例中使用。 例如,在电子商务场景中,可以在物化视图中生成产品的销售数量,因为生成最终数据集所需的查询将很复杂,并且将包含多个表联接和聚合操作。 我们并不在乎统计信息是否过时了一段时间,而是可以对系统状态有一个大致了解。 这些通常在查询密集的地方实现,例如网站上的顶级产品列表,本季度的产品推荐等。
作为一般的经验法则,我们应该始终使用成熟的技术。 当我们尝试在多个数据库服务器之间创建数据同步操作时,我们基本上在解决一个已经仔细解决的问题。 因此,在模仿已经建立好的系统方面,我们很有可能不会做得更好。 这也将我们的注意力从系统的核心业务功能上移开了,而最终我们将更多的时间花费在与基础架构相关的实现上。
活动来源-追踪过去
现在,我们已经研究了CQRS的基本功能,让我们继续进行扩展,将其与事件源结合起来。 使用命令,它封装了业务操作的意图,但是在将操作应用于数据模型之后,该信息就会丢失。 因此,写数据模型当前仅包含系统的最后状态。 事件源意味着我们将针对系统执行的所有命令存储为事件。 通过这种方式,我们可以回到过去并导出系统状态。 它为我们提供了已执行的所有业务操作的审核跟踪。
拥有这些事件意味着我们将存储系统中发生的所有活动,并且我们可以在过去的任何时间点重新创建系统。 例如,如果我们以后决定通过查看某些特定数据(例如网站上的产品价格分析)来创建报告,但没有历史数据,则只能生成该报告以供将来使用。 但是,借助具有CQRS的事件源机制,我们可以生成任意数量的报告,就像我们过去做出决定一样。 为了实现事件源,我们可以使用一种可以提供仅追加数据存储的技术。 在扩展我们先前基于关系数据库的参考实现时,我们可以通过将事件序列存储在数据库表中来实现此目的。 通过这种方式,我们可以做一个简单的本地事务来更新写模型中的当前状态,以及将命令附加为事件。
图6: 参考实现-具有事件源的CQRS
上面的图6中的图显示了我们具有事件源功能的CQRS的参考实现。 将命令提交给系统后,将生成具有与命令相同信息的相应事件。 在使用命令更新写模型的同一时间,该事件也被附加到使用数据库表实现的事件列表中。 由于这两个数据操作都发生在同一数据库中,因此我们可以简单地进行本地事务以确保两个数据操作以一致的方式成功。
更多审核方式?
我们还有许多其他方式可以审核数据操作,可以使用数据库服务器本身的某些功能来设置审核,也可以使用其他专用工具来跟踪数据的更改。 但是事件源的主要区别在于它与域操作一起工作,而其他审核方法将跟踪数据模型中的更改。 这样,命令或意图的上下文将丢失。 因此,重要的是我们在事件流中存储正确的表示形式。
在下一节中,我们将研究如何开发前面提到的带有事件源的CQRS的参考实现。
案例研究:核心银行账户管理
在本案例研究中,我们将创建一个进行帐户交易的核心银行系统的超简化操作。 它将对诸如帐户借记/贷方操作之类的基本操作进行建模,并为其内部使用提供诸如银行中每个分支机构的活动/非活动帐户比率之类的信息。 对于我们这里的场景,我们假设那些查找操作经常进行。
图7中的以下ER图用于创建我们的数据模型。
图7: 银行帐户管理ER图
如上所示,每个帐户都与一个银行分支机构关联,并且具有与其关联的多个帐户日志条目。 帐户日志条目包含针对该帐户执行的所有操作。 这被编码在日志条目中的事件有效负载中。 这些事件具有特定的事件类型,以便了解该事件将执行的操作,并且基本上将在我们代码中的命令处理程序中使用它。 同样,事件类型也可以进行版本控制,以便为帐户生成的事件进行任何演变。
在以下部分中,让我们检查系统将支持的命令和查询。
命令和查询
针对系统执行的命令和查询将包含我们可以对帐户进行的所有交互。 因此,所有可能的操作都必须建模为我们的业务领域操作。 让我们从我们将支持的命令开始。
- 创建帐号
- 关闭账户
- 冻结账户
- 信用账户
- 借方账户
这些命令提供了维护帐户所需的基本功能。 每个命令的信息将作为帐户日志事件记录为所有已发生操作的审核跟踪。 对于监管要求,以及银行在与过去发生的交易有关的任何争议中,银行都必须向客户提供历史信息,这些信息是必需的。 此外,客户也可以直接从在线门户使用此信息来列出过去的交易。 例如,可以通过简单地重放具有特定事件类型的帐户日志中的事件来逐月生成过去的帐户对帐单。
查询列表如下:
- 查询帐户详细信息
- 列出过去的交易
- 分行有效/无效帐户比率
这些包含直接操作,例如可以由包含当前状态的帐户表直接查找的帐户余额。 如果还列出过去的交易,我们可以直接查询帐户日志以检索所需的条目。 至于更多的过程密集型查询(例如创建帐户摘要),其中可能包含复杂的数据库联接和聚合,这将给数据库带来额外的负担,并可能影响数据库常规操作的性能。 因此,我们将创建这些数据集的物化视图并定期刷新这些视图。 我们假设这是在与客户进行一般银行业务的白天所需的用例。
物化视图在刷新之前将具有一些过时的数据,但这对于此处的用例是可以接受的。 由于每次执行完这些查询后,它都不会显着提高性能,因为它不会以昂贵的数据库查询操作结束,但是会返回缓存的结果。
在下一节中,让我们看一下如何在Ballerina中使用微服务方法实际实现这些操作。
实作
该功能将通过Ballerina服务公开。 它具有用于各个命令和查询的单独的资源功能。
我们将要实现的最基本的Ballerina服务如下所示。
清单3: 帐户管理服务框架
清单3显示了对系统功能的所有访问点。 让我们开始看看如何实现命令及其各自的命令处理程序。
指令
命令处理程序是将特定命令提交到系统中时执行的逻辑。 在这里,我们将通过使用函数指针映射对其进行建模,该函数指针映射从命令类型映射到实现。
清单4: 命令处理程序注册
清单4显示了如何通过插入到函数指针映射中来实现和注册命令处理程序。 第一次调用命令时,将使用命令处理程序来执行命令,并且事件重播也将通过相同的处理程序进行调度。
让我们看一下命令分发以及与之对应的事件如何持久化。
清单5: 命令调度和事件持久性
这里的executeCommandAndLogEvent
函数结合了分派命令以及将事件持久保存到日志的功能,并确保在事务中完成此操作以保持数据的一致性。 Ballerina的transaction
块用于此任务。 由于我们将同一数据库用于写入模型和事件日志,因此它将仅执行本地事务以完成工作。
现在,有了命令分派和事件日志持久性功能,我们就可以实现各个服务资源功能。
清单6: 资源功能实现
如清单6所示,资源函数仅需提取命令的JSON表示形式,并与命令名称一起调用executeCommandAndLogEvent
函数。 我们可以对所有命令重复此模式。
现在让我们检查一下如果有这样的要求,如何重播事件日志。 在这里,我们只需要从事件日志中加载所有必需的事件,然后调用我们的调度逻辑即可。 清单7显示了此实现。
清单7: 事件日志重播实现
注意,这里我们使用了相同的调度功能,因此它与我们先前在执行命令时已经实现的逻辑无缝集成。
查询
在访问数据库和返回数据时,查询遵循更直接的方法。 让我们看一下这些是如何实现的。
清单8: 查询操作实现
通过查询我们已在数据库中实现的getAccountActiveRatios
化视图来实现getAccountActiveRatios
函数。 已知此操作是要执行的昂贵的读取查询,并且如果针对每个资源功能执行重复执行此操作,则可能会对数据库服务器造成太大的压力。 因此,我们基本上预填充了表中的数据,然后直接查询该表。 通过数据库触发器或计划的计时器更改源数据时,将刷新该表。 让我们看看如何在MySQL中实现该实例化视图,以及如何在Ballerina中使用计划的计时器对其进行刷新。
清单9: 在MySQL中实现物化视图
清单9显示了数据库表定义,以及用于刷新和填充表以类似于实体化视图工作的存储过程。 清单10显示了调用上述存储过程所涉及的Ballerina代码。
清单10: 刷新实例化视图的实现
样本执行
在这里,我们将通过一个示例流程来构建Ballerina项目,创建数据库以及调用服务操作以测试功能。
建立
$ ballerina build -a --experimental
Compilingsource
laf/account_mgt:0.1.0
Creating balos
target/balo/account_mgt-2020r1-java8-0.1.0.balo
Running Tests
laf/account_mgt:0.1.0
No tests found
Generating executables
target/bin/account_mgt.jar
数据库创建
$ mysql -u root -p < banking.sql
服务部署
$ ballerina run target/bin/account_mgt.jar
[ballerina/http] started HTTP/WS listener 0.0.0.0:8080
命令执行
$ curl -d'{ "name": "James Hunt", "address": "68 Wild Rose St. Goldsboro, NC 27530", "balance": "0.0", "branchId": "BWI" }' http://localhost:8080/AccountManagement/createAccount
{ "accountId" : "db386e20-0335-487c-9779-7af7b191bec1" , "name" : "James Hunt" , "address" : "68 Wild Rose St. Goldsboro, NC 27530" , "balance" : "0.0" , "state" : "ACTIVE" , "branchId" : "BWI" }
$ curl -d '{ "name": "John Wayne", "address": "75 Applegate St. Dawsonville, GA 30534", "balance": "0.0", "branchId": "GNC" }' http://localhost:8080/AccountManagement/createAccount
{ "accountId" : "3d679c5f-f645-4790-acbb-3c054eb4b62f" , "name" : "John Wayne" , "address" : "75 Applegate St. Dawsonville, GA 30534" , "balance" : "0.0" , "state" : "ACTIVE" , "branchId" : "GNC" }
$ curl -d '{ "name": "Jill Jackson", "address": "7439 Armstrong Ave. Lynnwood, WA 98037", "balance": "0.0", "branchId": "GNC" }' http://localhost:8080/AccountManagement/createAccount
{ "accountId" : "083acdbf-d52a-4599-bc8f-ae25b7ab272b" , "name" : "Jill Jackson" , "address" : "7439 Armstrong Ave. Lynnwood, WA 98037" , "balance" : "0.0" , "state" : "ACTIVE" , "branchId" : "GNC" }
$ curl -d '{ "name": "Bill Johnson", "address": "75 Applegate St. Dawsonville, GA 30534", "balance": "0.0", "branchId": "GNC" }' http://localhost:8080/AccountManagement/createAccount
{ "accountId" : "b8880ba4-9d7a-48e6-9760-2d5273aace7b" , "name" : "Bill Johnson" , "address" : "75 Applegate St. Dawsonville, GA 30534" , "balance" : "0.0" , "state" : "ACTIVE" , "branchId" : "GNC" }
$ curl -d '2000' http://localhost:8080/AccountManagement/creditAccount/db386e20-0335-487c-9779-7af7b191bec1
$ curl -d '225.5' http://localhost:8080/AccountManagement/debitAccount/db386e20-0335-487c-9779-7af7b191bec1
$ curl -d '1500' http://localhost:8080/AccountManagement/creditAccount/3d679c5f-f645-4790-acbb-3c054eb4b62f
$ curl -d '1500' http://localhost:8080/AccountManagement/creditAccount/083acdbf-d52a-4599-bc8f-ae25b7ab272b
$ curl -d '1200' http://localhost:8080/AccountManagement/creditAccount/b8880ba4-9d7a-48e6-9760-2d5273aace7b
$ curl -d 'Migrating' http://localhost:8080/AccountManagement/closeAccount/b8880ba4-9d7a-48e6-9760-2d5273aace7b
{ "reason" : "Migrating" }
查询执行
- 帐户详细资料
$ curl http://localhost:8080/AccountManagement/getAccountDetails/b8880ba4-9d7a-48e6-9760-2d5273aace7b [{"accountId" : "b8880ba4-9d7a-48e6-9760-2d5273aace7b" , "name" : "Bill Johnson" , "address" : "75 Applegate St. Dawsonville, GA 30534" , "balance" :1200.0000, "state" : "CLOSED" , "branchId" : "GNC" }]
- 上市账户交易
$ curl http://localhost:8080/AccountManagement/listTransactions/db386e20-0335-487c-9779-7af7b191bec1 [{"accountId" :1, "name" : "db386e20-0335-487c-9779-7af7b191bec1" , "address" : "CreateAccount" , "balance" : "{\"accountId\":\"db386e20-0335-487c-9779-7af7b191bec1\", \"name\":\"James Hunt\", \"address\":\"68 Wild Rose St. Goldsboro, NC 27530\", \"balance\":\"0.0\", \"state\":\"ACTIVE\", \"branchId\":\"BWI\"}" , "state" : "2020-06-17 12:38:46" }, { "accountId" :5, "name" : "db386e20-0335-487c-9779-7af7b191bec1" , "address" : "CreditAccount" , "balance" : "2000" , "state" : "2020-06-17 12:41:26" }, { "accountId" :6, "name" : "db386e20-0335-487c-9779-7af7b191bec1" , "address" : "DebitAccount" , "balance" : "225.5" , "state" : "2020-06-17 13:03:50" }] $ curl http://localhost:8080/AccountManagement/listTransactions/b8880ba4-9d7a-48e6-9760-2d5273aace7b [{ "accountId" :4, "name" : "b8880ba4-9d7a-48e6-9760-2d5273aace7b" , "address" : "CreateAccount" , "balance" : "{\"accountId\":\"b8880ba4-9d7a-48e6-9760-2d5273aace7b\", \"name\":\"Bill Johnson\", \"address\":\"75 Applegate St. Dawsonville, GA 30534\", \"balance\":\"0.0\", \"state\":\"ACTIVE\", \"branchId\":\"GNC\"}" , "state" : "2020-06-17 12:39:22" }, { "accountId" :9, "name" : "b8880ba4-9d7a-48e6-9760-2d5273aace7b" , "address" : "CreditAccount" , "balance" : "1200" , "state" : "2020-06-17 13:04:47" }, { "accountId" :10, "name" : "b8880ba4-9d7a-48e6-9760-2d5273aace7b" , "address" : "CloseAccount" , "balance" : "{\"reason\":\"Migrating\"}" , "state" : "2020-06-17 13:05:59" }]
- 帐户有效比率分析(实例化视图)
$ curl http://localhost:8080/AccountManagement/getAccountActiveRatios [{"branchId" : "BWI" , "ratio" :1.0}, { "branchId" : "GNC" , "ratio" :0.666667}]
- 事件日志重播
$ mysql -u root -p BANKING_DB -e"truncate ACCOUNT" $ curl http://localhost:8080/AccountManagement/getAccountDetails/b8880ba4-9d7a-48e6-9760-2d5273aace7b [] $ curl -X POST http://localhost:8080/AccountManagement/replayLog/b8880ba4-9d7a-48e6-9760-2d5273aace7b $ curl http://localhost:8080/AccountManagement/getAccountDetails/b8880ba4-9d7a-48e6-9760-2d5273aace7b [{ "accountId" : "b8880ba4-9d7a-48e6-9760-2d5273aace7b" , "name" : "Bill Johnson" , "address" : "75 Applegate St. Dawsonville, GA 30534" , "balance" :1200.0000, "state" : "CLOSED" , "branchId" : "GNC" }]
摘要
在本文中,我们回顾了两种流行的微服务数据处理模式。 CRUD模型提供了一种简单的方式来对数据建模并将其公开为服务接口。 这对于简单的数据库表可能很好,但是对于具有多个表和外键关系的数据库,将很难使用CRUD模式对操作进行建模。 而且,这些操作并不代表业务领域的操作,因此很难对使用它的软件系统进行建模。 此外,由于存在更新和删除操作,因此在处理数据时,CRUD被认为是破坏性模式。 完成这些操作后,过去的完整状态将丢失,我们将无法将其读回。
由于这些原因,服务中的CRUD通常被称为反模式。 我们应该对类似于业务领域的数据操作进行建模。 CQRS避免了CRUD固有的主要问题,并且与事件源一起使用时,它提供了全面的审核日志,使我们可以重温历史记录中的任何时刻。 我们提供了带有事件源的CQRS参考实现,它使用带有实例化视图MySQL数据库服务器来执行有效的读/写模型。
可以在下面找到Ballerina项目以及此处提到的示例的源代码:
有关Ballerina及其功能的更多信息,请参见以下资源:
翻译自: https://hackernoon.com/practical-microservices-development-patterns-crud-vs-cqrs-h6m3y5y
c#mvc模式进行crud