前言

asp.net core版本选择2.2,只是因为个人习惯了vs2017,代码以及设计皆可移植到vs2019,用asp.net core 3.0以及以上运行起来

项目类似选择web api,基础设施选择entity frame core + Masstransit + aspectCore

先赘述一下思路业务,中间通讯以及容错/重试交给masstransit,部分流程的解耦交给aspectCore来完成,这部分包括,错误后通过masstransit发布给错误处理的模块,最后落盘到ef core

整个过程,业务处理的层之间可以通过Masstransit的通讯,可采用rpc的模式,也可以同异步的发布订阅通讯

整体设计是可单体可分布式的理念,可以根据项目变化,可以自行配置,拆分成完成单体到分布式的过程,完成业务分解,但是对于业务使用而言,都是一样的,无感知。

刚开始的文章会有很多代码段说明,属于前置知识的铺垫,可能会有些啰嗦,后续文章会忽略掉无关的大片代码段。

业务演示,我们选择传统的银行转账业务以及电商的支付到下单。


整体crud的设计图

包括了业务层和基础设施层的设计


当前文章的设计图如下

整体采用Mq异步的发布订阅,订阅之间也通过发布订阅通知,完成最终一致性


业务实体

  1. public class BaseModel
  2. {
  3. [Key]
  4. public int Id { get; set; }
  5. public DateTime CreateTime { get; set; }
  6. }
  1. public class UserInfo: BaseModel
  2. {
  3.  
  4. public string NickName { get; set; }
  5. public decimal Money { get; set; }
  6. public DateTime LastOptions { get; set; }
  7. }
  1. public class PayOrder: BaseModel
  2. {
  3. public int SourceId { get; set; }
  4. public int TargetId { get; set; }
  5. public decimal Money { get; set; }
  6. }

配置EF Core

  1. public class TransactionContexts:DbContext
  2. {
  3. public DbSet<PayOrder> PayOrders { get; set; }
  4. public DbSet<UserInfo> UserInfos { get; set; }
  5.  
  6. public TransactionContexts(DbContextOptions<TransactionContexts> options):base(options)
  7. {
  8.  
  9. }
  10. }

配置数据源偷懒就用InMemory了

  1. services.AddDbContext<TransactionContexts>(build=> {
  2. build.UseInMemoryDatabase("TransactionContexts");
  3. });

实例编写

建立一个交易的Command

  1. internal class PayOrderCommand
  2. {
  3. public int SourceId { get; set; }
  4. public int TargetId { get; set; }
  5. public decimal Money { get; set; }
  6. }

一个交易的Event

  1. internal class PayOrderEvent: PayOrderCommand
  2. {
  3.  
  4. }

Command对外,Event对内,主要是用于项目区分外部流程和内部流程的区别,代码本身是没有硬编码要求的

构建一个交易的服务,对外公开一个转账的接口

  1. public interface ITransactionService
  2. {
  3. void TransferAccounts(int sourceId, int targetId, decimal money);
  4. }

这个接口完成Publish/Subscribe模式的交易

Publish端

  1. internal class TransactionService: ITransactionService
  2. {
  3. private IBusControl busControl;
  4.  
  5. public TransactionService(IBusControl busControl)
  6. {
  7. this.busControl = busControl;
  8. }
  9.  
  10. public async void TransferAccounts(int sourceId, int targetId, decimal money)
  11. {
  12. await busControl.Publish(
  13. new PayOrderCommand { SourceId = sourceId, TargetId = targetId, Money = money }
  14. );
  15. }
  16. }

很常见的发布一个命令

Subscribe端

  1. internal class TransactionConsumer :
  2. IConsumer<PayOrderCommand>,
  3. IConsumer<PayOrderEvent>
  4. {
  5. private TransactionContexts transactionContexts;
  6.  
  7. public TransactionConsumer(TransactionContexts transactionContexts)
  8. {
  9. this.transactionContexts = transactionContexts;
  10. }
  11.  
  12. public async Task Consume(ConsumeContext<PayOrderCommand> context)
  13. {
  14. var value = context.Message;
  15.  
  16. await Console.Out.WriteLineAsync($"PayOrderCommand Before:{DateTime.Now} SourceId:{value.SourceId} TargetId:{value.TargetId} Money:{value.Money}");
  17.  
  18. await transactionContexts.PayOrders.AddAsync(new PayOrder
  19. {
  20. Id = ,
  21. SourceId = value.SourceId,
  22. TargetId = value.TargetId,
  23. Money = value.Money
  24. });
  25. await transactionContexts.SaveChangesAsync();
  26.  
  27. await context.Publish(new PayOrderEvent
  28. {
  29. SourceId = value.SourceId,
  30. TargetId = value.TargetId,
  31. Money = value.Money
  32. });
  33.  
  34. await Console.Out.WriteLineAsync($"PayOrderCommand After:{DateTime.Now}");
  35. }
  36.  
  37. public async Task Consume(ConsumeContext<PayOrderEvent> context)
  38. {
  39. var value = context.Message;
  40.  
  41. await Console.Out.WriteLineAsync($"PayOrderEvent Before:{DateTime.Now} SourceId:{value.SourceId} TargetId:{value.TargetId} Money:{value.Money}");
  42.  
  43. var source = transactionContexts.UserInfos.First(user => user.Id == value.SourceId);
  44.  
  45. if (source.Money < value.Money)
  46. throw new Exception();
  47.  
  48. var target = transactionContexts.UserInfos.First(user => user.Id == value.TargetId);
  49.  
  50. source.Money -= value.Money;
  51. target.Money += value.Money;
  52.  
  53. transactionContexts.UserInfos.Update(source);
  54. transactionContexts.UserInfos.Update(target);
  55.  
  56. await transactionContexts.SaveChangesAsync();
  57.  
  58. await Console.Out.WriteLineAsync($"PayOrderEvent After:{DateTime.Now}");
  59. }
  60. }

配置依赖注入流程

MassTransit的通讯选择MassTransit.RabbitMQ,这个库,也支持很多MQ,个人图方便就选的rabbitmq,后期要更换,修改一下依赖注入的配置即可

  1. services.AddScoped<TransactionConsumer>();
  2.  
  3. services.AddMassTransit(c =>
  4. {
    c.AddConsumer<TransactionConsumer>();
  5. c.AddBus(serviceProvider =>
  6. {
  7. return Bus.Factory.CreateUsingRabbitMq(cfg =>
  8. {
  9. var host = cfg.Host(new Uri("rabbitmq://localhost/"), hst =>
  10. {
  11. hst.Username("guest");
  12. hst.Password("guest");
  13. });
  14.  
  15. cfg.ReceiveEndpoint("Transaction", config =>
  16. {
  17. config.ConfigureConsumer<TransactionConsumer>(serviceProvider);
  18. });
  19. });
  20. });
  21. });

这样就完成了引入Masstransit做数据通讯部分,Masstransit支持Rpc模式,也支持Publish/Subscribe,后续的文章会混搭Rpc模式和发布订阅的模式,主要根据业务场景的选择做调整


编写演示例子

在Configure里面配置初始化数据

  1. using (var serviceScoped = app.ApplicationServices.CreateScope())
  2. {
  3. var serviceProvider = serviceScoped.ServiceProvider;
  4. var context = serviceProvider.GetRequiredService<TransactionContexts>();
  5.  
  6. context.UserInfos.Add(new UserInfo
  7. {
  8. Id = ,
  9. NickName = "Form",
  10. CreateTime = DateTime.Now,
  11. LastOptions = DateTime.Now,
  12. Money =
  13. });
  14. context.UserInfos.Add(new UserInfo
  15. {
  16. Id = ,
  17. NickName = "To",
  18. CreateTime = DateTime.Now,
  19. LastOptions = DateTime.Now,
  20. Money =
  21. });
  22.  
  23. context.SaveChanges();
  24. }
  25. #endregion

给Configure方法增加一个注入的接口

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime lifetime)
  2. {
  3. //。。。
  4. var busControl = app.ApplicationServices.GetRequiredService<IBusControl>();
  5.  
  6. lifetime.ApplicationStarted.Register(busControl.Start);
  7. lifetime.ApplicationStopped.Register(busControl.Stop);
  8. }

依旧是省事儿的Configure方法里面写的一个管道

  1. app.Run(async (context) =>
  2. {
  3. var serviceProvider = context.RequestServices;
  4.  
  5. var bus = serviceProvider.GetRequiredService<IBusControl>();
  6.  
  7. await bus.Publish(new PayOrderCommand
  8. {
  9. SourceId = 1,
  10. TargetId = 2,
  11. Money = 2000
  12. });
  13.  
  14. await context.Response.WriteAsync("Hello World!");
  15. });

后话

写在默认的index管道,会触发一个有意思的BUG,会两次执行,但是主键是唯一的,这样重复执行会抛出异常,提示主键已存在

这个例子是先铺垫一下,很多有意思的实现还没开展

1、某个业务需要执行过程和下游强一致性,要么一起完成,要么当场失败

2、某个业务完成最终一致性,这个过程,如果失败需要重试/限流/熔断

3、某个业务完成最终一致性,失败了则触发补偿

先铺垫一下,后空余时间逐一编写示例演示

现在的代码一点都不优雅,还有很强的面向于具体实现的,后续会逐步高度抽象化,从相对麻烦的代码,变成纯crud拿来主义,业务层 和后续代码的流程无感

打个小广告

如果有技术交流可以加NCC的群 24791014、436035237,我在群里,有任何关于asp.net core/Masstransit的问题或者建议都可以与我交流,非常欢迎

示例代码:

https://github.com/htrlq/Crud.Sample

基于asp.net core 从零搭建自己的业务框架(一)的更多相关文章

  1. 基于asp.net core 从零搭建自己的业务框架(三)

    前言 根据业务处理部分,单体马上就能得知错误与否,快速做出处理,而分布式系统,会因为各种原因,无法如同单体一样立刻处理,所以这个时候需要 处理异常 的,做 补偿.转移.人工干预. 当然也可以直接在消费 ...

  2. 基于asp.net core 从零搭建自己的业务框架(二)

    前言 对于项目的迭代,如何降低复杂性的要求高于性能以及技术细节的 一个易用的项目,才能迭代到比拼性能,最后拼刺刀的阶段 传统单体项目,都是传统三层,直接请求响应的模式,这类称为Rpc模式,易用性上非常 ...

  3. 基于ASP.Net Core开发的一套通用后台框架

    基于ASP.Net Core开发一套通用后台框架 写在前面 这是本人在学习的过程中搭建学习的框架,如果对你有所帮助那再好不过.如果您有发现错误,请告知我,我会第一时间修改. 知其然,知其所以然,并非重 ...

  4. 基于ASP.NET Core 3.0快速搭建Razor Pages Web应用

    前言 虽然说学习新的开发框架是一项巨大的投资,但是作为一个开发人员,不断学习新的技术并快速上手是我们应该掌握的技能,甚至是一个.NET Framework开发人员,学习.NET Core 新框架可以更 ...

  5. 用VSCode开发一个基于asp.net core 2.0/sql server linux(docker)/ng5/bs4的项目(1)

    最近使用vscode比较多. 学习了一下如何在mac上使用vscode开发asp.netcore项目. 这里是我写的关于vscode的一篇文章: https://www.cnblogs.com/cgz ...

  6. 基于ASP.NET Core 6.0的整洁架构

    大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享学习心得,希望我的文章能成为你成长路上的垫脚石,让我们一起精进. 本节将介绍基于ASP.NET Core的整洁架构的设计理念,同时基于理论落地的代码 ...

  7. 如何基于asp.net core的Identity框架在mysql上作身份验证处理

    首先了解这个概念,我一开始也是理解和掌握基本的概念,再去做程序的开发.Identity框架是微软自己提供,基于.net core平台,可拓展.轻量 级.面向多个数据库的身份验证框架.IdentityS ...

  8. 基于Asp.Net Core的简单社区项目源代码开源

    2019年3月27号 更新版本 本项目基于 ASP.NET CORE 3.0+EF CORE 3.0开发 使用vs2019 +sqlserver 2017(数据库脚本最低支持sql server 20 ...

  9. AServer - 基于Asp.net core Kestrel的超迷你http服务器

    AServer是基于ASP.NET Core Kestrel封装的一个超迷你http服务器.它可以集成进你的Core程序里,用来快速的响应Http请求,而不需要集成整个ASP.NET Core MVC ...

随机推荐

  1. C++中复杂声明和定义的辨析

    0x00 前言 c++中的复杂声明往往令人无法下手,经常使人搞错这到底声明的是一个指针还是指针函数.但其实c++对于复杂声明是遵循一定的规则的,叫做变量名—>右--左-右规则. 0x01 规则解 ...

  2. Django---进阶16<XSS攻击>

    目录 后台管理 添加文章 kindeditor富文本编辑器 编辑器上传图片 修改用户头像 bbs项目总结 后台管理 """ 当一个文件夹下文件比较多的时候 你还可以继续创 ...

  3. Kafka入门(1):概述

    摘要 在本文中,我将从为什么需要消息队列开始讲起,举两个小例子,跟你聊聊目前消息队列的一些使用场景. 比如消息队列在复杂系统中的解耦,又比如消息队列在高并发下的场景如果让流量变得更平缓. 随后我会跟你 ...

  4. include文件包含漏洞

    发现allow_url_include 是on状态 既然已经直接包含了phpinfo()是文件,首先搜索了一下allow_url_include,发现是处于打开的状态. 既然 allow_url_in ...

  5. 三、python函数详解

    函数的定义: 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 定义规则: 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号(). 任何传入参数和自变量必须放在圆括号中间 ...

  6. java IO流 (四) 缓冲流的使用

    1.缓冲流涉及到的类: * BufferedInputStream* BufferedOutputStream* BufferedReader* BufferedWriter 2.作用:作用:提供流的 ...

  7. Unity-ECS(一)浅谈CPU缓存命中和Unity面向数据技术栈(DOTS)--笔记

    一,缓存类型 概念:局部性. 时间局部性:当前用到的一个存储器位置,不久的将来会被用到. 空间局部性:当前用到的一个存储器位置,附近的位置会被用到. 那么在CPU的层面,这两个局部性的特性就会被Cac ...

  8. day2 python六大标准数据类型简介

    1.number( int , float , bool , complex ) # int 整型 intvar = 2020 print(type(intvar),id(intvar)) ​ # f ...

  9. Java常用API(String类)

    Java常用API(String类) 概述: java.lang.String 类代表字符串.Java程序中所有的字符串文字(例如 "abc" )都可以被看作是实现此类的实例 1. ...

  10. 集训 T1-找试场

    大致题意: 按照给定的指令移动,输出最后到达的点. 若没有走动过则输出(0,0). 基本思路 这题就是模拟,主要是判断指令的时候不太好判断, 先用字符串把指令读取进来,看看第一位是否是数字(如果是数字 ...