鼎鼎知识库
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

1、领域驱动实战.md 35KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486
  1. ---
  2. typora-root-url: ./
  3. ---
  4. # 1、什么是领域驱动
  5. 领域驱动,Domain Driven Design。
  6. 一张桌子有4条腿。程序员可能的第一反应是通过`Table`类和`Leg`类描述两者之间的关系。一个人戴着一顶帽子,程序员可能的第一反应是一个`Person`类有一个`Hat`属性。
  7. 这似乎是顺其自然的、合乎事理的。但是,大家是否会觉得这是一种`Code Driven Implementation`?的确,领域驱动最终会落实到代码层面,**但领域驱动的意义在于:在写代码之前,我们先理解领域,理解业务概念、约束、行为、规则等等**。
  8. 领域驱动似乎传递着*问题第一代码第二*、*市场第一代码第二*、*业务第一代码第二*的理念。程序员不再是拿着锤子到处找钉子的人,而是让自己首先成为一个领域专家,或者成为一个领域专家的倾听者。程序员不仅对代码负责,还对利益相关方负责,对业务负责。程序员关注的焦点不仅在解决问题本身,而是是否真的解决了问题。
  9. 当我们谈领域驱动,其实与编程语言、代码、数据库、微服务都无关,而是一种更高层面的设计。
  10. # 2、分层结构
  11. ## 2.1 领域抽象层
  12. > 聚合根标记接口
  13. ```
  14. public interface IAggregateRoot{}
  15. ```
  16. 什么是聚合根?理解聚它首先要理解聚合`Aggregate`。假设有一个银行转账场景,程序员A和领域专家B正在进行对话:
  17. A:我们目前想解决的问题是什么?
  18. B:做一个客户转账的功能。
  19. A:能具体说一下吗?
  20. B: 客户X给客户Y转账100元,X的账户上少了100元,Y的账户上增加100元。我们希望所有的转账记录都可以被追溯。
  21. 接着,A和B在白板上进行了一次事件风暴`Event Storming`。首先,在列出了影响系统状态的事件。
  22. ![](/ddd1.png)
  23. 事件作用在哪里?谁触发事件?触发什么事件?
  24. ![](/ddd2.png)
  25. 显然,事件围绕账户而进行。在`DDD`中适用聚合`Aggregate`这个概念对相关性事件进行逻辑划分。
  26. 有两种做法。**一种是把聚合看作名词**,比如在这里定义`Account`类。
  27. ```
  28. public class Account : IAggregateRoot{}
  29. ```
  30. 以上的`Account`就是聚合,可以被持久化到数据库,我们目前的架构采用的就是这种方式。
  31. **另一种做法是把聚合看作动词**。其中有两个关键的视角:组成和规则。
  32. **聚合组成:**
  33. - 转账的唯一性:每次转账都有其唯一编号。创建`TransferNumber`用来生成唯一编号
  34. - 出账: 从一个账户中扣除转账金额。创建`Debit`类用来描述出账
  35. - 入账: 把转账金额存入某个账户。创建`Credit`类用来描述入账
  36. - 账户:出账和入账都用到了账户。创建`AccountNumber`类用来描述账户
  37. **聚合规则:**
  38. - 出账:转账的金额必须大于零,出账账户不能为空
  39. - 入账:入账的金额必须大于零,入账账户不能为空
  40. ```
  41. //每次转账唯一编号的封装
  42. public class TranferNumber
  43. {
  44. public string Value {get; private set;}
  45. public Guid EntityId {get; private set;}
  46. public TranferNumber(string value, Guid entityId)
  47. {
  48. Value = value;
  49. EntityId = entityId;
  50. }
  51. }
  52. //出账
  53. public class Debit
  54. {
  55. public decimal Value {get; private set; }
  56. public AccountNumber Account {get; private set; }
  57. public Debit(decimal value, AccountNumber account)
  58. {
  59. Value = value;
  60. Account = account;
  61. value.Must(v => v >=0);//business rule
  62. account.MustNotBeNull();//ensure value object is valid
  63. }
  64. }
  65. //入账
  66. public class Credit
  67. {
  68. public decimal Value {get; private set; }
  69. public AccountNumber Account {get; private set; }
  70. public Credit(decimal value, AccountNumber account)
  71. {
  72. value.Must(v => v >=0);//business rule
  73. account.MustNotBeNull();//ensure value object is valid
  74. Value = value;
  75. Account = account;
  76. }
  77. }
  78. //账号的封装
  79. public class AccountNumber : IEquatable<AccountNumber>
  80. {
  81. public string Number {get; private set;}
  82. public AccountNumber(string number)
  83. {
  84. Number = number;
  85. }
  86. public bool Equals(AccountNumber other) => other !=null && Number == other.Number;
  87. }
  88. public class TransferedRegistered
  89. {
  90. public Guid EntityId {get; set;}
  91. public string TransferNumber {get; set;}
  92. public string DebitAccountNo {get;set;}
  93. public string CreditAccountNo {get;set;}
  94. public DateTimeOffset CreatedOn {get;set;} = DateTimeOffset.Now;
  95. }
  96. //Aggregate Root
  97. public static class TransferAccount
  98. {
  99. public static TransferRegistered Create(TransferNumber number, Debit debit, Credit credit)
  100. {
  101. number.MustNotBeNull();
  102. debit.MustNotBeNull();
  103. credit.MustNotBeNull();
  104. debit.Account.MustNotBeNull(credit.Account);
  105. var ev = new TransferedRegistered();
  106. ev.EntityId = number.EntityId;
  107. ev.TransferNumber = number.Value;
  108. en.Amount = debit.Value;
  109. ev.DebitAccountNo = debit.Account.Number;
  110. ev.CreditAccountNo = credit.Account.Number;
  111. return ev;
  112. }
  113. }
  114. ```
  115. 以上`TransferAccount`就是聚合,聚合无需持久化到数据库,状态的改变需要持久化到数据库。
  116. > 领域接口和抽象实现
  117. ```
  118. public interface IEntity{}
  119. public interface IEntity<TKey> : IEntity{}
  120. public abstract class Entity : IEntity
  121. {
  122. //保存所有的事件,针对领域的事件在领域内部调用
  123. private List<IDomainEvent> _domainEvents;
  124. public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents?.AsReadOnly();
  125. public void AddDomainEvent(IDomainEvent eventItem)
  126. {
  127. _domainEvents = _domainEvents ?? new List<IDomainEvent>();
  128. _domainEvents.Add(eventItem);
  129. }
  130. public void RemoveDomainEvent(IDomainEvent eventItem)
  131. {
  132. _domainEvents?.Remove(eventItem);
  133. }
  134. public void ClearDomainEvents()
  135. {
  136. _domainEvents?.Clear();
  137. }
  138. }
  139. ```
  140. > 领域事件接口和领域事件处理接口
  141. ```
  142. using MediatR;
  143. public interface IDomainEvent : INotification{}
  144. pubic interface IDomainEventHandler<TDomainEvent> : INotificationHandler<TDomainEvent> where TDomainEvent : IDomainEvent{}
  145. ```
  146. > 值对象: 如果两个值对象的值都一样,这两个值对象可以相互替换
  147. ```
  148. public abstract class ValueObject{}
  149. ```
  150. ## 2.2 基础设施抽象层
  151. > 工作者单元接口
  152. ```
  153. public interface IUnitOfWork : IDisposable
  154. {
  155. Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
  156. Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default);
  157. }
  158. ```
  159. > 事务接口
  160. ```
  161. public interface ITransaction
  162. {
  163. IDbContextTransaction GetCurrentTransaction();
  164. bool HasActiveTransaction { get; }
  165. Task<IDbContextTransaction> BeginTransactionAsync();
  166. Task CommitTransactionAsync(IDbContextTransaction transaction);
  167. void RollbackTransaction();
  168. }
  169. ```
  170. > 自定义上下文
  171. ```
  172. public class EFContext : DbContext, IUnitOfWork, ITransaction
  173. {
  174. protected IMediator _mediator;
  175. ICapPublisher _capBus;
  176. public EFContext(DbContextOptions options, IMediator mediator, ICapPublisher capBus) : base(options)
  177. {
  178. _mediator = mediator;
  179. _capBus = capBus;
  180. }
  181. public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
  182. {
  183. var result = await base.SaveChangesAsync(cancellationToken);
  184. await _mediator.DispatchDomainEventsAsync(this);
  185. return true;
  186. }
  187. ...
  188. }
  189. ```
  190. > 仓储接口
  191. ```
  192. public interface IRepository<TEntity> where TEntity : Entity, IAggregateRoot{}
  193. public interface IRepository<TEntity, TKey> : IRepository<TEntity> where TEntity : Entity<TKey>, IAggregateRoot{}
  194. ```
  195. > 仓储抽象实现
  196. ```
  197. public abstract class Repository<TEntity, TDbContext> : IRepository<TEntity> where TEntity : Entity, IAggregateRoot where TDbContext : EFContext
  198. {
  199. }
  200. ```
  201. > 管道行为
  202. 在管道内提交事务
  203. ```
  204. //在MedatR的IPipelineBehavior基础上注入上下文
  205. public class TransactionBehavior<TDbContext, TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TDbContext : EFContext
  206. {
  207. ILogger _logger;
  208. TDbContext _dbContext;
  209. public TransactionBehavior(TDbContext dbContext, ILogger logger)
  210. {
  211. _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
  212. _logger = logger ?? throw new ArgumentNullException(nameof(logger));
  213. }
  214. public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
  215. {
  216. var response = default(TResponse);
  217. var typeName = request.GetGenericTypeName();//object类型的TRequest的扩展方法,获取泛型类型名称
  218. try
  219. {
  220. if (_dbContext.HasActiveTransaction)
  221. {
  222. return await next();
  223. }
  224. var strategy = _dbContext.Database.CreateExecutionStrategy();
  225. await strategy.ExecuteAsync(async () =>
  226. {
  227. Guid transactionId;
  228. using (var transaction = await _dbContext.BeginTransactionAsync())
  229. using (_logger.BeginScope("TransactionContext:{TransactionId}", transaction.TransactionId))
  230. {
  231. _logger.LogInformation("----- 开始事务 {TransactionId} ({@Command})", transaction.TransactionId, typeName, request);
  232. response = await next();
  233. _logger.LogInformation("----- 提交事务 {TransactionId} {CommandName}", transaction.TransactionId, typeName);
  234. await _dbContext.CommitTransactionAsync(transaction);
  235. transactionId = transaction.TransactionId;
  236. }
  237. });
  238. return response;
  239. }
  240. catch (Exception ex)
  241. {
  242. _logger.LogError(ex, "处理事务出错 {CommandName} ({@Command})", typeName, request);
  243. throw;
  244. }
  245. }
  246. }
  247. ```
  248. 扩展方法,获取泛型类型名称:
  249. ```
  250. public static class GenericTypeExtensions
  251. {
  252. public static string GetGenericTypeName(this Type type)
  253. {
  254. var typeName = string.Empty;
  255. if (type.IsGenericType)
  256. {
  257. var genericTypes = string.Join(",", type.GetGenericArguments().Select(t => t.Name).ToArray());
  258. typeName = $"{type.Name.Remove(type.Name.IndexOf('`'))}<{genericTypes}>";
  259. }
  260. else
  261. {
  262. typeName = type.Name;
  263. }
  264. return typeName;
  265. }
  266. public static string GetGenericTypeName(this object @object)
  267. {
  268. return @object.GetType().GetGenericTypeName();
  269. }
  270. }
  271. ```
  272. ## 2.3 共用层
  273. - 异常处理
  274. 异常处理接口:`IknownException`
  275. ```
  276. public interface IKnownException
  277. {
  278. string Message { get; }
  279. int ErrorCode { get; }
  280. object[] ErrorData { get; }
  281. }
  282. ```
  283. 异常处理接口实现:`KnownException`
  284. ```
  285. public class KnownException : IKnownException
  286. {
  287. public string Message { get; private set; }
  288. public int ErrorCode { get; private set; }
  289. public object[] ErrorData { get; private set; }
  290. public readonly static IKnownException Unknown = new KnownException { Message = "未知错误", ErrorCode = 9999 };
  291. public static IKnownException FromKnownException(IKnownException exception)
  292. {
  293. return new KnownException { Message = exception.Message, ErrorCode = exception.ErrorCode, ErrorData = exception.ErrorData };
  294. }
  295. }
  296. ```
  297. ## 2.4 领域层
  298. > 引用
  299. 引用领域抽象层。
  300. > 聚合
  301. ```
  302. public class Order : Entity<long>, IAggregateRoot
  303. {
  304. public string UserId {get; private set;}
  305. public string UserName {get; private set;}
  306. public int ItemCount {get; private set;}
  307. public Address Address {get; private set;}
  308. public Order (string userId, string userName, int itemCount, Address address)
  309. {
  310. this.UserName = userName;
  311. this.UserId = userId;
  312. this.ItemCount = itemCount;
  313. this.AddDomainEvent(new OrderCreatedDomainEvent(this));
  314. }
  315. public void ChangeAddress(Address address)
  316. {
  317. this.Address = address;
  318. //TODO:这里也可以添加事件
  319. }
  320. }
  321. ```
  322. > 领域事件
  323. ```
  324. public class OrderCreatedDomainEvent : IDomainEvent
  325. {
  326. public Order Order {get; private set;}
  327. public OrderCreatedDomainEvent(Order order)
  328. {
  329. this.Order = order;
  330. }
  331. }
  332. ```
  333. ## 2.5 基础设施层
  334. > 引用
  335. - 基础设施抽象层
  336. - 领域层
  337. > 上下文
  338. ```
  339. public class OrderingContext : EFContext
  340. {
  341. public OrderingContext(DbContextOptions options, IMediator mediator, ICapPublisher capBus) : base(options, mediator, capBus){}
  342. public DbSet<Order> Orders { get; set; }
  343. protected override void OnModelCreating(ModelBuilder modelBuilder){
  344. modelBuilder.ApplyConfiguration(new OrderEntityTypeConfiguration());
  345. base.OnModelCreating(modelBuilder);
  346. }
  347. }
  348. ```
  349. > 领域约束
  350. ```
  351. class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
  352. {
  353. public void Configure(EntityTypeBuilder<Order> builder)
  354. {
  355. builder.HasKey(p => p.Id);
  356. builder.ToTable("order");
  357. builder.Property(p => p.UserId).HasMaxLength(20);
  358. builder.Property(p => p.UserName).HasMaxLength(30);
  359. builder.OwnsOne(o => o.Address, a =>
  360. {
  361. a.WithOwner();
  362. a.Property(p => p.City).HasMaxLength(20);
  363. a.Property(p => p.Street).HasMaxLength(50);
  364. a.Property(p => p.ZipCode).HasMaxLength(10);
  365. });
  366. }
  367. }
  368. ```
  369. > 管道
  370. ```
  371. public class OrderingContextTransactionBehavior<TRequest, TResponse> : TransactionBehavior<OrderingContext, TRequest, TResponse>
  372. {
  373. public OrderingContextTransactionBehavior(OrderingContext dbContext, ILogger<OrderingContextTransactionBehavior<TRequest, TResponse>> logger) : base(dbContext, logger)
  374. {
  375. }
  376. }
  377. ```
  378. ## 2.6 应用层和接口层
  379. > 引用
  380. - 基础设施层
  381. > `IServiceCollection`的扩展
  382. > 领域事件的处理链路
  383. - 在接口层或应用层定义`IRequest`
  384. ```
  385. public class CreateOrderCommand : IRequest<long>
  386. {
  387. public CreateOrderCommand(int itemCount)
  388. {
  389. ItemCount = itemCount;
  390. }
  391. public long ItemCount {get; private set;}
  392. }
  393. ```
  394. - 在接口层把`IRequest`传入
  395. ```
  396. [HttpPost]
  397. public async Task<long> CreateOrder([FromBody]CreateorderCommand cmd)
  398. {
  399. return await _medator.Send(cmd, HttpContext.RequestAborted);
  400. }
  401. ```
  402. - 在接口层或应用层的`IRequestHandler`对`IRequest`进行处理
  403. ```
  404. public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, long>
  405. {
  406. IOrderRepository _orderRepository;
  407. ICapPublisher _capPublisher;
  408. public CreateOrderCommandHandler(IOrderRepository orderRepository, ICapPublisher capPublisher)
  409. {
  410. _orderRepository = orderRepository;
  411. _capPublisher = capPublisher;
  412. }
  413. public async Task<long> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
  414. {
  415. var address = new Address("wen san lu", "hangzhou", "310000");
  416. var order = new Order("xiaohong1999", "xiaohong", 25, address);
  417. _orderRepository.Add(order);
  418. await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
  419. return order.Id;
  420. }
  421. }
  422. ```
  423. - 领域层的领域在创建`Order`的同时添加了领域事件
  424. ```
  425. public class Order : Entity<long>, IAggregateRoot
  426. {
  427. public Order(string userId, string userName, int itemCount, Address address)
  428. {
  429. this.UserId = userId;
  430. this.UserName = userName;
  431. this.Address = address;
  432. this.ItemCount = itemCount;
  433. this.AddDomainEvent(new OrderCreatedDomainEvent(this));//添加领域事件
  434. }
  435. }
  436. ```
  437. - 领域抽象层的`Entity`抽象基类定义了对事件的处理
  438. ```
  439. public abstract class Entity : IEntity
  440. {
  441. private List<IDomainEvent> _domainEvents;
  442. public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents?.AsReadOnly();
  443. public void AddDomainEvent(IDomainEvent eventItem)
  444. {
  445. _domainEvents = _domainEvents ?? new List<IDomainEvent>();
  446. _domainEvents.Add(eventItem);
  447. }
  448. public void RemoveDomainEvent(IDomainEvent eventItem)
  449. {
  450. _domainEvents?.Remove(eventItem);
  451. }
  452. public void ClearDomainEvents()
  453. {
  454. _domainEvents?.Clear();
  455. }
  456. }
  457. ```
  458. - 基础设施抽象层的`EFContext`在把数据持久化到数据库的同时会发布领域事件
  459. ```
  460. public class EFContext : DbContext, IUnitOfWork, ITransaction
  461. {
  462. public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
  463. {
  464. var result = await base.SaveChangesAsync(cancellationToken);
  465. await _mediator.DispatchDomainEventsAsync(this);//把事件发布出去
  466. return true;
  467. }
  468. }
  469. ```
  470. - 基础设施抽象层定义了对`MedatR`的扩展方法,本质上是把上下文所有领域的事件发布出去。
  471. ```
  472. public static async Task DispatchDomainEventsAsync(this IMediator mediator, DbContext ctx)
  473. {
  474. var domainEntities = ctx.ChangeTracker
  475. .Entries<Entity>()
  476. .Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any());
  477. var domainEvents = domainEntities
  478. .SelectMany(x => x.Entity.DomainEvents)
  479. .ToList();
  480. domainEntities.ToList()
  481. .ForEach(entity => entity.Entity.ClearDomainEvents());
  482. foreach (var domainEvent in domainEvents)
  483. await mediator.Publish(domainEvent);
  484. }
  485. ```
  486. - 应用层和接口中定义对领域事件的处理
  487. ```
  488. public class OrderCreatedDomainEventHandler : IDomainEventHandler<OrderCreatedDomainEvent>
  489. {
  490. ICapPublisher _capPublisher;
  491. public OrderCreatedDomainEventHandler(ICapPublisher capPublisher)
  492. {
  493. _capPublisher = capPublisher;
  494. }
  495. public async Task Handle(OrderCreatedDomainEvent notification, CancellationToken cancellationToken)
  496. {
  497. await _capPublisher.PublishAsync("OrderCreated", new OrderCreatedIntegrationEvent(notification.Order.Id));
  498. }
  499. }
  500. ```
  501. 这样,领域事件就被发布到`Event Bus`上,与外界产生了交互,很有可能与外界的另外一个微服务产生交互。
  502. # 3、集成事件
  503. ![ddd3](/ddd3.png)
  504. > 在各个微服务的数据库中一般有2张表,一张用来记录发布事件,另一张用来记录订阅事件。这些事件会随着领域事件的发布持久化到数据库,CAP可以做到业务逻辑和发布订阅事件的一致性。
  505. ![](/ddd4.png)
  506. 在应用层或接口层,定义一个集成事件。
  507. ```
  508. public class OrderCreatedIntegrationEvent
  509. {
  510. public OrderCreatedIntegrationEvent(long orderId) => OrderId = orderId;
  511. public long OrderId { get; }
  512. }
  513. public class OrderPaymentSucceededIntegrationEvent
  514. {
  515. public OrderPaymentSucceededIntegrationEvent(long orderId) => OrderId = orderId;
  516. public long OrderId { get; }
  517. }
  518. ```
  519. 在应用层或接口层,定义`IDomainEventHandler<IDomainEvent>`发布集成事件。
  520. ```
  521. public class OrderCreatedDomainEventHandler : IDomainEventHandler<OrderCreatedDomainEvent>
  522. {
  523. //中国开源社区,用于消息的发布和订阅,可以发布到RabbitMQ或者Kafa等
  524. ICapPublisher _capPublisher;
  525. public OrderCreatedDomainEventHandler(ICapPublisher capPublisher)
  526. {
  527. _capPublisher = capPublisher;
  528. }
  529. public async Task Handle(OrderCreatedDomainEvent notification, CancellationToken cancellationToken)
  530. {
  531. await _capPublisher.PublishAsync("OrderCreated", new OrderCreatedIntegrationEvent(notification.Order.Id));
  532. }
  533. }
  534. ```
  535. 在应用层或接口层,定义订阅接口。
  536. ```
  537. public interface ISubscriberService
  538. {
  539. void OrderPaymentSucceeded(OrderPaymentSucceededIntegrationEvent @event);
  540. }
  541. ```
  542. 订阅接口实现。
  543. ```
  544. public class SubscriberService : ISubscriberService, ICapSubscribe
  545. {
  546. IMediator _mediator;
  547. public SubscriberService(IMediator mediator)
  548. {
  549. _mediator = mediator;
  550. }
  551. [CapSubscribe("OrderPaymentSucceeded")]
  552. public void OrderPaymentSucceeded(OrderPaymentSucceededIntegrationEvent @event)
  553. {
  554. //Do SomeThing
  555. }
  556. [CapSubscribe("OrderCreated")]
  557. public void OrderCreated(OrderCreatedIntegrationEvent @event)
  558. {
  559. //Do SomeThing
  560. }
  561. }
  562. ```
  563. # 4、使用RabbitMQ实现EventBus
  564. 安装`RabbitMQ`。
  565. CAP的事件如何与业务逻辑放在同一个事务呢?在基础设施抽象层的`EFContext`中定义如下:
  566. ```
  567. public class EFContext : DbContext, IUnitOfWork, ITransaction
  568. {
  569. protected IMediator _mediator;
  570. ICapPublisher _capBus;
  571. public EFContext(DbContextOptions options, IMediator mediator, ICapPublisher capBus) : base(options)
  572. {
  573. _mediator = mediator;
  574. _capBus = capBus;
  575. }
  576. ......
  577. public Task<IDbContextTransaction> BeginTransactionAsync()
  578. {
  579. if (_currentTransaction != null) return null;
  580. _currentTransaction = Database.BeginTransaction(_capBus, autoCommit: false);
  581. return Task.FromResult(_currentTransaction);
  582. }
  583. ......
  584. }
  585. ```
  586. 这样保证了CAP事件的提交回滚与业务事务的提交回滚保持一致。
  587. 在应用层或接口层,注册CAP使用`RabbitMQ`。
  588. ```
  589. public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration)
  590. {
  591. services.AddTransient<ISubscriberService, SubscriberService>();//有关CAP的接口和实现
  592. services.AddCap(options =>
  593. {
  594. options.UseEntityFramework<OrderingContext>();
  595. options.UseRabbitMQ(options =>
  596. {
  597. configuration.GetSection("RabbitMQ").Bind(options);
  598. });
  599. //options.UseDashboard();
  600. });
  601. return services;
  602. }
  603. ```
  604. 在`appsettings.json`中:
  605. ```
  606. "Mysql": "server=localhost;port=3306;user id=root;password=123456;database=geektime;charset=utf8mb4;ConnectionReset=false;",
  607. "RabbitMQ": {
  608. "HostName": "localhost",
  609. "UserName": "guest",
  610. "Password": "guest",
  611. "VirtualHost": "/",//将RabbitMQ空间划分为不同的空间,每个空间可以被认为是一个租户。相同的VirtualHost可以被认为是一个集群
  612. "ExchangeName": "queue"
  613. }
  614. ```
  615. 在`Startup.cs`中注册:
  616. ```
  617. services.AddEventBus(Configuration);
  618. ```
  619. # 5、`gRPC`内部服务间的通讯利器
  620. `gRPC`是一个远程调用框架,由`Google`公司发起并开源。通过`gRPC`让我们可以像调用本地类一样调用远程的服务。提供几乎所有语言的实现。基于HTTP/2,开放协议,受到广泛支持,易于实现和集成。默认使用`Protocol Buffers`序列化,性能较于`RESTful Json`好很多。工具链成熟,代码生成便捷,开箱即用。支持双向流式的请求和响应,对批处理、低延时场景友好。`gRPC`使用自制证书,使用非加密的`HTTP2`。
  621. `.NET`提供基于`HttpClient`的原生框架实现。提供原生的`ASP.NET Core`集成库。提供完整的代码生成工具。`Visual Studio`和`Visual Studio Code`提供`proto`文件的智能提示。可以通过命令行工具创建服务。
  622. 服务端引用包:
  623. - `Grpc.AspNetCore`
  624. 客户端核心包:
  625. - `Google.Protobuf`
  626. - `Grpc.Net.Client`
  627. - `Grpc.Net.ClientFactory`
  628. - `Grpc.Tools`:提供命令行工具使用的包,用来基于`protocol`文件生成服务代码。`.proto`文件定义包、库名,定义服务`service`,定义输入输出模型`message`。然后借助`Grpc.Tools`生成服务端和客户端代码。
  629. `gRPC`异常处理引用包:
  630. - `Grpc.Core.RpcException`
  631. - `Grpc.Core.Interceptors.Interceptor`
  632. > 服务端
  633. `order.proto`
  634. ```
  635. syntax = "proto3";//协议版本
  636. option csharp_namespace="GrpcServices";//命名空间
  637. package GrpcServices;
  638. service OrderGrpc {//服务
  639. rpc CreateOrder(CreateOrderCommand) returns (CreateOrderResult);
  640. }
  641. message CreateOrderCommand { //输入,字段的顺序决定了序列化顺序
  642. string buyerId = 1;
  643. int32 productId = 2;
  644. double unitPrice = 3;
  645. double discount = 4;
  646. int32 units = 5;
  647. }
  648. message CreateOrderResult {//响应
  649. int32 orderId = 1;
  650. }
  651. ```
  652. 会自动生成`Order`和`OrderGrpc`两个类。
  653. `OrderService.cs`
  654. ```
  655. public class OrderService : OrderGrpc.OrderGrpcBase
  656. {
  657. public override Task<CreateOrderResult> CreateOrder(CreateOrderCommand request, ServerCallContext context)
  658. {
  659. return Task.FromResult(new CreateOrderResult {OrderId=24;});
  660. }
  661. }
  662. ```
  663. `Startup.cs`
  664. ```
  665. sevices.AddGrpc(options => {
  666. options.EnaleDetailErrors = false;
  667. options.Intercoptions.Add<ExceptionInterceptor>();
  668. });
  669. app.UseEndpoints(endpoints => {
  670. endpoints.MapGrpcService<OrderService>();
  671. });
  672. ```
  673. `appSettings.json`
  674. ```
  675. "Kestrel":{
  676. "Endpoints":{
  677. "Http":{
  678. "Url":"http://+:5000"
  679. },
  680. "Https":{
  681. "Url":"http://+:5001"
  682. },
  683. "Http2":{
  684. "Url":"http://+:5002",
  685. "Protocols":"Http2"
  686. }
  687. }
  688. }
  689. ```
  690. > 客户端
  691. 项目文件中引用服务端的`order.proto`,这样可以自动生成客户端代码。
  692. ```
  693. <ItemGroup>
  694. <Protobuf Include="order.proto" GrpService="Client" />
  695. </ItemGroup>
  696. ```
  697. `Startup.cs`
  698. ```
  699. AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport",true);//允许使用不加密的HTTP/2协议
  700. services.AddGrpcClient<OrderGrpc.OrderGrpcClient>(options => {
  701. //options.Address= new Uri("https://localhost:5000");//服务端地址
  702. options.Address= new Uri("https://localhost:5002");//不配置证书使用Grpc
  703. }).ConfigurePrimaryHttpMessageHandler(provider => {
  704. var handler = new SocketHttpHandler();
  705. handler.SslOptions.RemoteCeretificateValidationCallback = (a, b, c, d) => true;
  706. return handler;
  707. });
  708. app.UseEndpoints(endpoints => {
  709. endpoints.MapGet("/", async context => {
  710. OrderGrpcClient service = context.RequestServices.GetService<OrderGrpcClient>();
  711. try
  712. {
  713. var r = service.CreateOrder(new CreateOrderCommand {BuyerId=""});
  714. }
  715. catch(Exception ex)
  716. {
  717. }
  718. });
  719. });
  720. ```
  721. # 6、`Polly`失败重试和熔断机制
  722. 组件包:
  723. - `Polly`
  724. - `Polly.Extensions.Http`
  725. - `Microsoft.Extensions.Http.Polly`
  726. `Polly`的能力
  727. - 失败重试
  728. - 服务熔断:服务不可用时快速响应一个熔断结果,避免持续请求不可用的服务导致跪掉
  729. - 超时处理:当超时发生,比如说可以返回一个缓存结果
  730. - 舱壁隔离:为服务定义最大的流量和队列,避免请求过服务被压崩
  731. - 缓存策略
  732. - 失败降级:当服务不可用时,响应一个更友好的结果而不是报错
  733. - 组合策略
  734. `Polly`的使用步骤
  735. - 定义要处理的异常类型和返回值
  736. - 定义要处理的动作(重试、熔断、降级响应)
  737. - 使用定义的策略来执行代码
  738. 适合失败重试的场景
  739. - 服务失败时短暂的,可自愈的
  740. - 服务时幂等的,重复调用不会有副作用
  741. - 网络闪断
  742. - 部分服务节点异常
  743. 最佳实践
  744. - 设置失败重试次数
  745. - 设置带有步长策略的失败等待间隔:否则会持续不断地重试,类似`DDOS`攻击
  746. - 设置降级响应:当失败重试次数达到上限,为服务提供一个降级响应,更好的响应结果
  747. - 设置短路器:当重试一定次数,可能服务还是不可用
  748. # 7、网关与`BFF`
  749. `BFF`是指`Backend For Frontend`,负责认证授权,负责服务聚合,目标是为前端提供服务。网关和`BFF`的职责可以是重叠的。
  750. ![](/ddd5.png)
  751. ![](/ddd6.png)
  752. ![ddd7](/ddd7.png)
  753. 打造网关
  754. - 添加`Ocelot`
  755. - 添加配置文件`ocelot.json`
  756. - 添加配置读取代码
  757. - 注册`Ocelot`服务
  758. - 注册`Ocelot`中间件
  759. 网关和负载均衡的区别
  760. | 方面 | 网关 | 负载均衡 |
  761. | ---------------- | ------ | ------------------ |
  762. | OSI模型 | 第7层 | 第4层 |
  763. | 基于url的路由 | 可以 | 不可以 |
  764. | 基于cookie的路由 | 可以 | 不可以 |
  765. | web防火墙 | 可以 | 不可以 |
  766. | 地域性 | 任意ip | 云服务商下的某个ip |
  767. | | | |
  768. 项目介绍
  769. - `Ordering.API`
  770. ```
  771. "applicationUrl":"https://localhost.5001;http://localhost:5000"
  772. ```
  773. ```
  774. public class TestController : ControllerBase
  775. {
  776. [HttpGet]
  777. public IActionResult Abc()
  778. {
  779. return Content("Ordering.API");
  780. }
  781. }
  782. ```
  783. - `Mobile.ApiAggregator`
  784. ```
  785. "applicationUrl":"https://localhost.5005;http://localhost:5004"
  786. ```
  787. ```
  788. public class TestController : ControllerBase
  789. {
  790. [HttpGet]
  791. public IActionResult Abc()
  792. {
  793. return Content("Mobile.ApiAggregator");
  794. }
  795. }
  796. ```
  797. - `Mobile.Gateway`网关项目
  798. `appSettings.json`
  799. ```
  800. "Apollo": {
  801. "AppId": "geektime-mobile-gateway",
  802. "Env": "DEV",
  803. "MetaServer": "http://192.168.67.76:8080",
  804. "ConfigServer": [ "http://192.168.67.76:8080" ]
  805. },
  806. "AllowedHosts": "*",
  807. "ReRoutes": [
  808. {
  809. "DownstreamPathTemplate": "/api/{everything}",
  810. "DownstreamScheme": "http",
  811. "DownstreamHostAndPorts": [
  812. {
  813. "Host": "localhost",
  814. "Port": 5004
  815. }
  816. ],
  817. "UpstreamPathTemplate": "/mobileAgg/api/{everything}",
  818. "UpstreamHttpMethod": []
  819. },
  820. {
  821. "DownstreamPathTemplate": "/api/{everything}",
  822. "DownstreamScheme": "http",
  823. "DownstreamHostAndPorts": [
  824. {
  825. "Host": "localhost",
  826. "Port": 5000
  827. }
  828. ],
  829. "UpstreamPathTemplate": "/mobile/api/{everything}",
  830. "UpstreamHttpMethod": []
  831. }
  832. ],
  833. "GlobalConfiguration": {
  834. "RequestIdKey": "OcRequestId",
  835. "AdministrationPath": "/administration"
  836. },
  837. "SecurityKey": "aabbccddffskldjfklajskdlfjlas234234234"
  838. ```
  839. `Startup.cs`
  840. ```
  841. services.AddOcelot(Configuration);
  842. app.UseOcelot().Wait();
  843. ```
  844. `TestController.cs`
  845. ```
  846. public IActionResult Abc()
  847. {
  848. return Content("GeekTime.Mobile.Gateway");
  849. }
  850. ```
  851. # 8、防跨站请求伪造
  852. ![ddd8](/ddd8.png)
  853. 如何防御
  854. - 不适用`Cookie`存储或传输身份信息
  855. - 适用`AntiforgeryToken`机制
  856. - 避免适用`GET`作为业务操作的请求方法
  857. 两种选择
  858. - `ValidateAntiForgeryToken`
  859. - `AutoValidateAntiforgeryToken`
  860. 举例
  861. ```
  862. [ValidateAntiForgeryToken]
  863. public IActionResult CreateOrder(string itemId, int count)
  864. {
  865. _logger.LogInformation("创建了订单itemId:{itemId},count:{count}", itemId, count);
  866. return Content("Order Created");
  867. }
  868. ```
  869. # 9、防开放重定向攻击
  870. ![ddd9](/ddd9.png)
  871. 防范错误
  872. - 使用`LoalRedirect`处理重定向:适合重定向仅限于本站的情况
  873. - 验证重定向的目标域名是否合法
  874. 举例
  875. ```
  876. public async Task<IActionResult> Login([FromServices]IAntiforgery antiforgery, string name, string password, string returnUrl)
  877. {
  878. HttpContext.Response.Cookies.Append("CSRF-TOKEN", antiforgery.GetTokens(HttpContext).RequestToken, new Microsoft.AspNetCore.Http.CookieOptions { HttpOnly = false });
  879. var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);//一定要声明AuthenticationScheme
  880. identity.AddClaim(new Claim("Name", "小王"));
  881. await this.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
  882. if (string.IsNullOrEmpty(returnUrl))
  883. {
  884. return Content("登录成功");
  885. }
  886. try
  887. {
  888. var uri = new Uri(returnUrl);
  889. ///uri.Host 根据配置表数据库表数据来验证
  890. return Redirect(returnUrl);
  891. }
  892. catch
  893. {
  894. return Redirect("/");
  895. }
  896. //return Redirect(returnUrl);
  897. }
  898. ```
  899. # 10、防跨站脚本
  900. ![ddd10](/ddd10.png)
  901. 防范措施
  902. - 对用户内容进行验证,拒绝恶意脚本
  903. - 对用户提交的内容进行编码`UrlEncoder`,`JavaScriptEncoder`
  904. - 慎用`HtmlString`和`HtmlHelper.Raw`
  905. - 身份信息`Cookie`设置为`HttpOnly`
  906. - 避免使用`Path`传递带有不受信的字符,使用`Query`进行传递
  907. 举例
  908. ```
  909. services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
  910. .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
  911. {
  912. options.LoginPath = "/home/login";
  913. options.Cookie.HttpOnly = true;//这里的设置生效
  914. });
  915. ```
  916. # 11、跨域请求
  917. 同源
  918. - 方案相同(`HTTP/HTTPS`)
  919. - 主机或域名相同
  920. - 端口相同
  921. `CORS`是什么
  922. - 浏览器允许跨域发起请求
  923. - 是浏览器的行为协议
  924. - 并不会让服务器拒绝其它路径发起的`HTTP`请求
  925. - 开启时需要考虑是否存在被恶意网站攻击的情形
  926. `CORS`请求头
  927. - `Origin`请求源
  928. - `Access-Control-Reqyest-Method`
  929. - `Access-Control-Request-Headers`
  930. `CORS`响应头
  931. - `Access-Control-Allow-Origin`
  932. - `Access-Control-Allow-Credentials`
  933. - `Access-Control-Expose-Headers`
  934. - `Access-Control-Max-Age`
  935. - `Access-Control-Allow-Methods`
  936. - `Access-Control-Allow-Headers`
  937. 默认支持的`Expose Headers`
  938. - `Cache-Control`
  939. - `Content-Language`
  940. - `Content-Type`
  941. - `Expires`
  942. - `Last-Modified`
  943. - `Pragma`
  944. 举例
  945. ```
  946. services.AddCors(options =>
  947. {
  948. options.AddPolicy("api", builder =>
  949. {
  950. builder.WithOrigins("https://localhost:5001").AllowAnyHeader().AllowCredentials().WithExposedHeaders("abc");
  951. builder.SetIsOriginAllowed(orgin => true).AllowCredentials().AllowAnyHeader();
  952. });
  953. });
  954. app.UseCors();
  955. ```
  956. # 12、为不同场景设计合适的缓存策略
  957. 缓存的场景
  958. - 计算结果缓存:反射对象缓存
  959. - 请求结果缓存:`DNS`缓存
  960. - 临时共享数据缓存:会话缓存
  961. - 热点内容缓存:商品详情页
  962. - 热点变更逻辑数据:秒杀库存数
  963. 缓存策略
  964. - 越接近最终的输出结果效果越好
  965. - 命中率越高越好
  966. 缓存位置
  967. - 浏览器中
  968. - 反向代理服务器(负载均衡)
  969. - 应用进程内存中
  970. - 分布式存储系统中
  971. 要点
  972. - `Key`的生成策略,表示数据范围业务含义
  973. - 缓存失效策略,过期时间机制,主动刷新机制
  974. - 缓存更新策略,表示更新缓存数据的时机
  975. 几个问题
  976. - 缓存失效,导致数据不一致
  977. - 缓存穿透,查询无数据时,导致缓存不生效,查询都落在数据库
  978. - 缓存击穿,缓存失效瞬间,大量请求访问到数据库
  979. - 缓存雪崩,大量缓存同一时间失效,导致数据库压力
  980. 内存缓存和分布式缓存的区别
  981. - 内存缓存可以存储任意对象
  982. - 分布式缓存的对象需要支持序列化
  983. - 分布式缓存远程请求可能失败,内存缓存不会