|
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486 |
- ---
-
- typora-root-url: ./
- ---
-
-
-
- # 1、什么是领域驱动
-
- 领域驱动,Domain Driven Design。
-
-
-
- 一张桌子有4条腿。程序员可能的第一反应是通过`Table`类和`Leg`类描述两者之间的关系。一个人戴着一顶帽子,程序员可能的第一反应是一个`Person`类有一个`Hat`属性。
-
-
-
- 这似乎是顺其自然的、合乎事理的。但是,大家是否会觉得这是一种`Code Driven Implementation`?的确,领域驱动最终会落实到代码层面,**但领域驱动的意义在于:在写代码之前,我们先理解领域,理解业务概念、约束、行为、规则等等**。
-
-
-
- 领域驱动似乎传递着*问题第一代码第二*、*市场第一代码第二*、*业务第一代码第二*的理念。程序员不再是拿着锤子到处找钉子的人,而是让自己首先成为一个领域专家,或者成为一个领域专家的倾听者。程序员不仅对代码负责,还对利益相关方负责,对业务负责。程序员关注的焦点不仅在解决问题本身,而是是否真的解决了问题。
-
-
-
- 当我们谈领域驱动,其实与编程语言、代码、数据库、微服务都无关,而是一种更高层面的设计。
-
- # 2、分层结构
-
- ## 2.1 领域抽象层
-
- > 聚合根标记接口
-
- ```
- public interface IAggregateRoot{}
- ```
-
- 什么是聚合根?理解聚它首先要理解聚合`Aggregate`。假设有一个银行转账场景,程序员A和领域专家B正在进行对话:
-
-
-
- A:我们目前想解决的问题是什么?
-
- B:做一个客户转账的功能。
-
- A:能具体说一下吗?
-
- B: 客户X给客户Y转账100元,X的账户上少了100元,Y的账户上增加100元。我们希望所有的转账记录都可以被追溯。
-
-
-
- 接着,A和B在白板上进行了一次事件风暴`Event Storming`。首先,在列出了影响系统状态的事件。
-
- ![](/ddd1.png)
-
- 事件作用在哪里?谁触发事件?触发什么事件?
-
- ![](/ddd2.png)
-
- 显然,事件围绕账户而进行。在`DDD`中适用聚合`Aggregate`这个概念对相关性事件进行逻辑划分。
-
-
-
- 有两种做法。**一种是把聚合看作名词**,比如在这里定义`Account`类。
-
- ```
- public class Account : IAggregateRoot{}
- ```
-
- 以上的`Account`就是聚合,可以被持久化到数据库,我们目前的架构采用的就是这种方式。
-
-
-
- **另一种做法是把聚合看作动词**。其中有两个关键的视角:组成和规则。
-
-
-
- **聚合组成:**
-
- - 转账的唯一性:每次转账都有其唯一编号。创建`TransferNumber`用来生成唯一编号
-
- - 出账: 从一个账户中扣除转账金额。创建`Debit`类用来描述出账
-
- - 入账: 把转账金额存入某个账户。创建`Credit`类用来描述入账
-
- - 账户:出账和入账都用到了账户。创建`AccountNumber`类用来描述账户
-
-
-
- **聚合规则:**
-
- - 出账:转账的金额必须大于零,出账账户不能为空
- - 入账:入账的金额必须大于零,入账账户不能为空
-
- ```
- //每次转账唯一编号的封装
- public class TranferNumber
- {
- public string Value {get; private set;}
- public Guid EntityId {get; private set;}
-
- public TranferNumber(string value, Guid entityId)
- {
- Value = value;
- EntityId = entityId;
- }
- }
-
- //出账
- public class Debit
- {
- public decimal Value {get; private set; }
- public AccountNumber Account {get; private set; }
-
- public Debit(decimal value, AccountNumber account)
- {
- Value = value;
- Account = account;
- value.Must(v => v >=0);//business rule
- account.MustNotBeNull();//ensure value object is valid
- }
- }
-
- //入账
- public class Credit
- {
- public decimal Value {get; private set; }
- public AccountNumber Account {get; private set; }
-
- public Credit(decimal value, AccountNumber account)
- {
- value.Must(v => v >=0);//business rule
- account.MustNotBeNull();//ensure value object is valid
- Value = value;
- Account = account;
- }
- }
-
-
- //账号的封装
- public class AccountNumber : IEquatable<AccountNumber>
- {
- public string Number {get; private set;}
- public AccountNumber(string number)
- {
- Number = number;
- }
-
- public bool Equals(AccountNumber other) => other !=null && Number == other.Number;
- }
-
- public class TransferedRegistered
- {
- public Guid EntityId {get; set;}
- public string TransferNumber {get; set;}
- public string DebitAccountNo {get;set;}
- public string CreditAccountNo {get;set;}
- public DateTimeOffset CreatedOn {get;set;} = DateTimeOffset.Now;
- }
-
- //Aggregate Root
- public static class TransferAccount
- {
- public static TransferRegistered Create(TransferNumber number, Debit debit, Credit credit)
- {
- number.MustNotBeNull();
- debit.MustNotBeNull();
- credit.MustNotBeNull();
-
- debit.Account.MustNotBeNull(credit.Account);
-
- var ev = new TransferedRegistered();
- ev.EntityId = number.EntityId;
- ev.TransferNumber = number.Value;
- en.Amount = debit.Value;
- ev.DebitAccountNo = debit.Account.Number;
- ev.CreditAccountNo = credit.Account.Number;
- return ev;
- }
- }
- ```
-
- 以上`TransferAccount`就是聚合,聚合无需持久化到数据库,状态的改变需要持久化到数据库。
-
-
-
- > 领域接口和抽象实现
-
- ```
- public interface IEntity{}
-
- public interface IEntity<TKey> : IEntity{}
-
- public abstract class Entity : IEntity
- {
- //保存所有的事件,针对领域的事件在领域内部调用
- private List<IDomainEvent> _domainEvents;
- public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents?.AsReadOnly();
-
- public void AddDomainEvent(IDomainEvent eventItem)
- {
- _domainEvents = _domainEvents ?? new List<IDomainEvent>();
- _domainEvents.Add(eventItem);
- }
-
- public void RemoveDomainEvent(IDomainEvent eventItem)
- {
- _domainEvents?.Remove(eventItem);
- }
-
- public void ClearDomainEvents()
- {
- _domainEvents?.Clear();
- }
- }
- ```
-
-
-
- > 领域事件接口和领域事件处理接口
-
- ```
- using MediatR;
- public interface IDomainEvent : INotification{}
-
- pubic interface IDomainEventHandler<TDomainEvent> : INotificationHandler<TDomainEvent> where TDomainEvent : IDomainEvent{}
- ```
-
-
-
- > 值对象: 如果两个值对象的值都一样,这两个值对象可以相互替换
-
- ```
- public abstract class ValueObject{}
- ```
-
-
-
- ## 2.2 基础设施抽象层
-
-
-
- > 工作者单元接口
-
- ```
- public interface IUnitOfWork : IDisposable
- {
- Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
- Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default);
- }
- ```
-
-
-
- > 事务接口
-
- ```
- public interface ITransaction
- {
- IDbContextTransaction GetCurrentTransaction();
- bool HasActiveTransaction { get; }
- Task<IDbContextTransaction> BeginTransactionAsync();
- Task CommitTransactionAsync(IDbContextTransaction transaction);
- void RollbackTransaction();
- }
- ```
-
-
-
- > 自定义上下文
-
- ```
- public class EFContext : DbContext, IUnitOfWork, ITransaction
- {
- protected IMediator _mediator;
- ICapPublisher _capBus;
-
- public EFContext(DbContextOptions options, IMediator mediator, ICapPublisher capBus) : base(options)
- {
- _mediator = mediator;
- _capBus = capBus;
- }
-
- public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
- {
- var result = await base.SaveChangesAsync(cancellationToken);
- await _mediator.DispatchDomainEventsAsync(this);
- return true;
- }
-
- ...
- }
- ```
-
-
-
- > 仓储接口
-
- ```
- public interface IRepository<TEntity> where TEntity : Entity, IAggregateRoot{}
-
- public interface IRepository<TEntity, TKey> : IRepository<TEntity> where TEntity : Entity<TKey>, IAggregateRoot{}
- ```
-
-
-
- > 仓储抽象实现
-
- ```
- public abstract class Repository<TEntity, TDbContext> : IRepository<TEntity> where TEntity : Entity, IAggregateRoot where TDbContext : EFContext
- {
-
- }
- ```
-
- > 管道行为
-
- 在管道内提交事务
-
- ```
- //在MedatR的IPipelineBehavior基础上注入上下文
- public class TransactionBehavior<TDbContext, TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TDbContext : EFContext
- {
- ILogger _logger;
- TDbContext _dbContext;
- public TransactionBehavior(TDbContext dbContext, ILogger logger)
- {
- _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
- _logger = logger ?? throw new ArgumentNullException(nameof(logger));
- }
-
-
- public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
- {
- var response = default(TResponse);
- var typeName = request.GetGenericTypeName();//object类型的TRequest的扩展方法,获取泛型类型名称
-
- try
- {
- if (_dbContext.HasActiveTransaction)
- {
- return await next();
- }
-
- var strategy = _dbContext.Database.CreateExecutionStrategy();
-
- await strategy.ExecuteAsync(async () =>
- {
- Guid transactionId;
- using (var transaction = await _dbContext.BeginTransactionAsync())
- using (_logger.BeginScope("TransactionContext:{TransactionId}", transaction.TransactionId))
- {
- _logger.LogInformation("----- 开始事务 {TransactionId} ({@Command})", transaction.TransactionId, typeName, request);
-
- response = await next();
-
- _logger.LogInformation("----- 提交事务 {TransactionId} {CommandName}", transaction.TransactionId, typeName);
-
-
- await _dbContext.CommitTransactionAsync(transaction);
-
- transactionId = transaction.TransactionId;
- }
- });
-
- return response;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "处理事务出错 {CommandName} ({@Command})", typeName, request);
-
- throw;
- }
- }
- }
-
-
- ```
-
- 扩展方法,获取泛型类型名称:
-
- ```
- public static class GenericTypeExtensions
- {
- public static string GetGenericTypeName(this Type type)
- {
- var typeName = string.Empty;
-
- if (type.IsGenericType)
- {
- var genericTypes = string.Join(",", type.GetGenericArguments().Select(t => t.Name).ToArray());
- typeName = $"{type.Name.Remove(type.Name.IndexOf('`'))}<{genericTypes}>";
- }
- else
- {
- typeName = type.Name;
- }
-
- return typeName;
- }
-
- public static string GetGenericTypeName(this object @object)
- {
- return @object.GetType().GetGenericTypeName();
- }
- }
- ```
-
-
-
- ## 2.3 共用层
-
- - 异常处理
-
- 异常处理接口:`IknownException`
-
- ```
- public interface IKnownException
- {
- string Message { get; }
- int ErrorCode { get; }
- object[] ErrorData { get; }
- }
- ```
-
- 异常处理接口实现:`KnownException`
-
- ```
- public class KnownException : IKnownException
- {
- public string Message { get; private set; }
- public int ErrorCode { get; private set; }
- public object[] ErrorData { get; private set; }
-
- public readonly static IKnownException Unknown = new KnownException { Message = "未知错误", ErrorCode = 9999 };
-
- public static IKnownException FromKnownException(IKnownException exception)
- {
- return new KnownException { Message = exception.Message, ErrorCode = exception.ErrorCode, ErrorData = exception.ErrorData };
- }
- }
- ```
-
-
-
- ## 2.4 领域层
-
-
-
- > 引用
-
- 引用领域抽象层。
-
- > 聚合
-
- ```
- public class Order : Entity<long>, IAggregateRoot
- {
- public string UserId {get; private set;}
- public string UserName {get; private set;}
- public int ItemCount {get; private set;}
- public Address Address {get; private set;}
-
- public Order (string userId, string userName, int itemCount, Address address)
- {
- this.UserName = userName;
- this.UserId = userId;
- this.ItemCount = itemCount;
-
- this.AddDomainEvent(new OrderCreatedDomainEvent(this));
- }
-
- public void ChangeAddress(Address address)
- {
- this.Address = address;
-
- //TODO:这里也可以添加事件
- }
- }
- ```
-
- > 领域事件
-
- ```
- public class OrderCreatedDomainEvent : IDomainEvent
- {
- public Order Order {get; private set;}
-
- public OrderCreatedDomainEvent(Order order)
- {
- this.Order = order;
- }
- }
- ```
-
- ## 2.5 基础设施层
-
-
-
- > 引用
-
- - 基础设施抽象层
- - 领域层
-
- > 上下文
-
- ```
- public class OrderingContext : EFContext
- {
- public OrderingContext(DbContextOptions options, IMediator mediator, ICapPublisher capBus) : base(options, mediator, capBus){}
-
- public DbSet<Order> Orders { get; set; }
-
- protected override void OnModelCreating(ModelBuilder modelBuilder){
- modelBuilder.ApplyConfiguration(new OrderEntityTypeConfiguration());
- base.OnModelCreating(modelBuilder);
- }
- }
- ```
-
- > 领域约束
-
- ```
- class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
- {
- public void Configure(EntityTypeBuilder<Order> builder)
- {
- builder.HasKey(p => p.Id);
- builder.ToTable("order");
- builder.Property(p => p.UserId).HasMaxLength(20);
- builder.Property(p => p.UserName).HasMaxLength(30);
- builder.OwnsOne(o => o.Address, a =>
- {
- a.WithOwner();
- a.Property(p => p.City).HasMaxLength(20);
- a.Property(p => p.Street).HasMaxLength(50);
- a.Property(p => p.ZipCode).HasMaxLength(10);
- });
- }
- }
- ```
-
- > 管道
-
- ```
- public class OrderingContextTransactionBehavior<TRequest, TResponse> : TransactionBehavior<OrderingContext, TRequest, TResponse>
- {
- public OrderingContextTransactionBehavior(OrderingContext dbContext, ILogger<OrderingContextTransactionBehavior<TRequest, TResponse>> logger) : base(dbContext, logger)
- {
- }
- }
- ```
-
- ## 2.6 应用层和接口层
-
- > 引用
-
- - 基础设施层
-
- > `IServiceCollection`的扩展
-
- > 领域事件的处理链路
-
- - 在接口层或应用层定义`IRequest`
-
- ```
- public class CreateOrderCommand : IRequest<long>
- {
- public CreateOrderCommand(int itemCount)
- {
- ItemCount = itemCount;
- }
-
- public long ItemCount {get; private set;}
- }
- ```
-
- - 在接口层把`IRequest`传入
-
- ```
- [HttpPost]
- public async Task<long> CreateOrder([FromBody]CreateorderCommand cmd)
- {
- return await _medator.Send(cmd, HttpContext.RequestAborted);
- }
- ```
-
- - 在接口层或应用层的`IRequestHandler`对`IRequest`进行处理
-
- ```
- public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, long>
- {
- IOrderRepository _orderRepository;
- ICapPublisher _capPublisher;
- public CreateOrderCommandHandler(IOrderRepository orderRepository, ICapPublisher capPublisher)
- {
- _orderRepository = orderRepository;
- _capPublisher = capPublisher;
- }
-
-
- public async Task<long> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
- {
-
- var address = new Address("wen san lu", "hangzhou", "310000");
- var order = new Order("xiaohong1999", "xiaohong", 25, address);
-
- _orderRepository.Add(order);
- await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
- return order.Id;
- }
- }
- ```
-
- - 领域层的领域在创建`Order`的同时添加了领域事件
-
- ```
- public class Order : Entity<long>, IAggregateRoot
- {
- public Order(string userId, string userName, int itemCount, Address address)
- {
- this.UserId = userId;
- this.UserName = userName;
- this.Address = address;
- this.ItemCount = itemCount;
-
- this.AddDomainEvent(new OrderCreatedDomainEvent(this));//添加领域事件
- }
- }
-
- ```
-
- - 领域抽象层的`Entity`抽象基类定义了对事件的处理
-
- ```
- public abstract class Entity : IEntity
- {
- private List<IDomainEvent> _domainEvents;
- public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents?.AsReadOnly();
-
- public void AddDomainEvent(IDomainEvent eventItem)
- {
- _domainEvents = _domainEvents ?? new List<IDomainEvent>();
- _domainEvents.Add(eventItem);
- }
-
- public void RemoveDomainEvent(IDomainEvent eventItem)
- {
- _domainEvents?.Remove(eventItem);
- }
-
- public void ClearDomainEvents()
- {
- _domainEvents?.Clear();
- }
- }
- ```
-
- - 基础设施抽象层的`EFContext`在把数据持久化到数据库的同时会发布领域事件
-
- ```
- public class EFContext : DbContext, IUnitOfWork, ITransaction
- {
- public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
- {
- var result = await base.SaveChangesAsync(cancellationToken);
- await _mediator.DispatchDomainEventsAsync(this);//把事件发布出去
- return true;
- }
- }
- ```
-
- - 基础设施抽象层定义了对`MedatR`的扩展方法,本质上是把上下文所有领域的事件发布出去。
-
- ```
- public static async Task DispatchDomainEventsAsync(this IMediator mediator, DbContext ctx)
- {
- var domainEntities = ctx.ChangeTracker
- .Entries<Entity>()
- .Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any());
-
- var domainEvents = domainEntities
- .SelectMany(x => x.Entity.DomainEvents)
- .ToList();
-
- domainEntities.ToList()
- .ForEach(entity => entity.Entity.ClearDomainEvents());
-
- foreach (var domainEvent in domainEvents)
- await mediator.Publish(domainEvent);
- }
- ```
-
- - 应用层和接口中定义对领域事件的处理
-
- ```
- public class OrderCreatedDomainEventHandler : IDomainEventHandler<OrderCreatedDomainEvent>
- {
- ICapPublisher _capPublisher;
- public OrderCreatedDomainEventHandler(ICapPublisher capPublisher)
- {
- _capPublisher = capPublisher;
- }
-
- public async Task Handle(OrderCreatedDomainEvent notification, CancellationToken cancellationToken)
- {
- await _capPublisher.PublishAsync("OrderCreated", new OrderCreatedIntegrationEvent(notification.Order.Id));
- }
- }
- ```
-
- 这样,领域事件就被发布到`Event Bus`上,与外界产生了交互,很有可能与外界的另外一个微服务产生交互。
-
-
-
- # 3、集成事件
-
- ![ddd3](/ddd3.png)
-
- > 在各个微服务的数据库中一般有2张表,一张用来记录发布事件,另一张用来记录订阅事件。这些事件会随着领域事件的发布持久化到数据库,CAP可以做到业务逻辑和发布订阅事件的一致性。
-
- ![](/ddd4.png)
-
- 在应用层或接口层,定义一个集成事件。
-
- ```
- public class OrderCreatedIntegrationEvent
- {
- public OrderCreatedIntegrationEvent(long orderId) => OrderId = orderId;
- public long OrderId { get; }
- }
-
- public class OrderPaymentSucceededIntegrationEvent
- {
- public OrderPaymentSucceededIntegrationEvent(long orderId) => OrderId = orderId;
- public long OrderId { get; }
- }
- ```
-
- 在应用层或接口层,定义`IDomainEventHandler<IDomainEvent>`发布集成事件。
-
- ```
- public class OrderCreatedDomainEventHandler : IDomainEventHandler<OrderCreatedDomainEvent>
- {
- //中国开源社区,用于消息的发布和订阅,可以发布到RabbitMQ或者Kafa等
- ICapPublisher _capPublisher;
- public OrderCreatedDomainEventHandler(ICapPublisher capPublisher)
- {
- _capPublisher = capPublisher;
- }
-
- public async Task Handle(OrderCreatedDomainEvent notification, CancellationToken cancellationToken)
- {
- await _capPublisher.PublishAsync("OrderCreated", new OrderCreatedIntegrationEvent(notification.Order.Id));
- }
- }
- ```
-
- 在应用层或接口层,定义订阅接口。
-
- ```
- public interface ISubscriberService
- {
- void OrderPaymentSucceeded(OrderPaymentSucceededIntegrationEvent @event);
- }
- ```
-
- 订阅接口实现。
-
- ```
- public class SubscriberService : ISubscriberService, ICapSubscribe
- {
- IMediator _mediator;
- public SubscriberService(IMediator mediator)
- {
- _mediator = mediator;
- }
-
-
- [CapSubscribe("OrderPaymentSucceeded")]
- public void OrderPaymentSucceeded(OrderPaymentSucceededIntegrationEvent @event)
- {
- //Do SomeThing
- }
-
- [CapSubscribe("OrderCreated")]
- public void OrderCreated(OrderCreatedIntegrationEvent @event)
- {
-
- //Do SomeThing
- }
- }
- ```
-
-
-
- # 4、使用RabbitMQ实现EventBus
-
-
-
- 安装`RabbitMQ`。
-
-
-
- CAP的事件如何与业务逻辑放在同一个事务呢?在基础设施抽象层的`EFContext`中定义如下:
-
- ```
- public class EFContext : DbContext, IUnitOfWork, ITransaction
- {
- protected IMediator _mediator;
- ICapPublisher _capBus;
-
- public EFContext(DbContextOptions options, IMediator mediator, ICapPublisher capBus) : base(options)
- {
- _mediator = mediator;
- _capBus = capBus;
- }
-
- ......
- public Task<IDbContextTransaction> BeginTransactionAsync()
- {
- if (_currentTransaction != null) return null;
- _currentTransaction = Database.BeginTransaction(_capBus, autoCommit: false);
- return Task.FromResult(_currentTransaction);
- }
- ......
- }
- ```
-
- 这样保证了CAP事件的提交回滚与业务事务的提交回滚保持一致。
-
-
-
- 在应用层或接口层,注册CAP使用`RabbitMQ`。
-
- ```
- public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration)
- {
- services.AddTransient<ISubscriberService, SubscriberService>();//有关CAP的接口和实现
- services.AddCap(options =>
- {
- options.UseEntityFramework<OrderingContext>();
-
- options.UseRabbitMQ(options =>
- {
- configuration.GetSection("RabbitMQ").Bind(options);
- });
- //options.UseDashboard();
- });
-
- return services;
- }
- ```
-
-
-
- 在`appsettings.json`中:
-
- ```
- "Mysql": "server=localhost;port=3306;user id=root;password=123456;database=geektime;charset=utf8mb4;ConnectionReset=false;",
- "RabbitMQ": {
- "HostName": "localhost",
- "UserName": "guest",
- "Password": "guest",
- "VirtualHost": "/",//将RabbitMQ空间划分为不同的空间,每个空间可以被认为是一个租户。相同的VirtualHost可以被认为是一个集群
- "ExchangeName": "queue"
- }
- ```
-
-
-
- 在`Startup.cs`中注册:
-
- ```
- services.AddEventBus(Configuration);
- ```
-
-
-
- # 5、`gRPC`内部服务间的通讯利器
-
- `gRPC`是一个远程调用框架,由`Google`公司发起并开源。通过`gRPC`让我们可以像调用本地类一样调用远程的服务。提供几乎所有语言的实现。基于HTTP/2,开放协议,受到广泛支持,易于实现和集成。默认使用`Protocol Buffers`序列化,性能较于`RESTful Json`好很多。工具链成熟,代码生成便捷,开箱即用。支持双向流式的请求和响应,对批处理、低延时场景友好。`gRPC`使用自制证书,使用非加密的`HTTP2`。
-
-
-
- `.NET`提供基于`HttpClient`的原生框架实现。提供原生的`ASP.NET Core`集成库。提供完整的代码生成工具。`Visual Studio`和`Visual Studio Code`提供`proto`文件的智能提示。可以通过命令行工具创建服务。
-
-
-
- 服务端引用包:
-
- - `Grpc.AspNetCore`
-
-
-
- 客户端核心包:
-
- - `Google.Protobuf`
- - `Grpc.Net.Client`
- - `Grpc.Net.ClientFactory`
- - `Grpc.Tools`:提供命令行工具使用的包,用来基于`protocol`文件生成服务代码。`.proto`文件定义包、库名,定义服务`service`,定义输入输出模型`message`。然后借助`Grpc.Tools`生成服务端和客户端代码。
-
-
-
- `gRPC`异常处理引用包:
-
- - `Grpc.Core.RpcException`
- - `Grpc.Core.Interceptors.Interceptor`
-
-
-
- > 服务端
-
- `order.proto`
-
- ```
- syntax = "proto3";//协议版本
- option csharp_namespace="GrpcServices";//命名空间
- package GrpcServices;
-
- service OrderGrpc {//服务
- rpc CreateOrder(CreateOrderCommand) returns (CreateOrderResult);
- }
-
- message CreateOrderCommand { //输入,字段的顺序决定了序列化顺序
- string buyerId = 1;
- int32 productId = 2;
- double unitPrice = 3;
- double discount = 4;
- int32 units = 5;
- }
-
- message CreateOrderResult {//响应
- int32 orderId = 1;
- }
- ```
-
- 会自动生成`Order`和`OrderGrpc`两个类。
-
-
-
- `OrderService.cs`
-
- ```
- public class OrderService : OrderGrpc.OrderGrpcBase
- {
- public override Task<CreateOrderResult> CreateOrder(CreateOrderCommand request, ServerCallContext context)
- {
- return Task.FromResult(new CreateOrderResult {OrderId=24;});
- }
- }
- ```
-
-
-
- `Startup.cs`
-
- ```
- sevices.AddGrpc(options => {
- options.EnaleDetailErrors = false;
- options.Intercoptions.Add<ExceptionInterceptor>();
- });
-
- app.UseEndpoints(endpoints => {
- endpoints.MapGrpcService<OrderService>();
- });
- ```
-
-
-
- `appSettings.json`
-
- ```
- "Kestrel":{
- "Endpoints":{
- "Http":{
- "Url":"http://+:5000"
- },
- "Https":{
- "Url":"http://+:5001"
- },
- "Http2":{
- "Url":"http://+:5002",
- "Protocols":"Http2"
- }
- }
- }
- ```
-
-
-
- > 客户端
-
- 项目文件中引用服务端的`order.proto`,这样可以自动生成客户端代码。
-
- ```
- <ItemGroup>
- <Protobuf Include="order.proto" GrpService="Client" />
- </ItemGroup>
-
- ```
-
-
-
- `Startup.cs`
-
- ```
- AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport",true);//允许使用不加密的HTTP/2协议
-
- services.AddGrpcClient<OrderGrpc.OrderGrpcClient>(options => {
- //options.Address= new Uri("https://localhost:5000");//服务端地址
- options.Address= new Uri("https://localhost:5002");//不配置证书使用Grpc
- }).ConfigurePrimaryHttpMessageHandler(provider => {
- var handler = new SocketHttpHandler();
- handler.SslOptions.RemoteCeretificateValidationCallback = (a, b, c, d) => true;
- return handler;
- });
-
- app.UseEndpoints(endpoints => {
- endpoints.MapGet("/", async context => {
- OrderGrpcClient service = context.RequestServices.GetService<OrderGrpcClient>();
- try
- {
- var r = service.CreateOrder(new CreateOrderCommand {BuyerId=""});
-
- }
- catch(Exception ex)
- {
-
- }
- });
- });
- ```
-
-
-
- # 6、`Polly`失败重试和熔断机制
-
-
-
- 组件包:
-
- - `Polly`
- - `Polly.Extensions.Http`
- - `Microsoft.Extensions.Http.Polly`
-
-
-
- `Polly`的能力
-
- - 失败重试
- - 服务熔断:服务不可用时快速响应一个熔断结果,避免持续请求不可用的服务导致跪掉
- - 超时处理:当超时发生,比如说可以返回一个缓存结果
- - 舱壁隔离:为服务定义最大的流量和队列,避免请求过服务被压崩
- - 缓存策略
- - 失败降级:当服务不可用时,响应一个更友好的结果而不是报错
- - 组合策略
-
-
-
- `Polly`的使用步骤
-
- - 定义要处理的异常类型和返回值
- - 定义要处理的动作(重试、熔断、降级响应)
- - 使用定义的策略来执行代码
-
-
-
- 适合失败重试的场景
-
- - 服务失败时短暂的,可自愈的
-
- - 服务时幂等的,重复调用不会有副作用
-
- - 网络闪断
-
- - 部分服务节点异常
-
-
-
- 最佳实践
-
- - 设置失败重试次数
- - 设置带有步长策略的失败等待间隔:否则会持续不断地重试,类似`DDOS`攻击
- - 设置降级响应:当失败重试次数达到上限,为服务提供一个降级响应,更好的响应结果
- - 设置短路器:当重试一定次数,可能服务还是不可用
-
-
-
- # 7、网关与`BFF`
-
- `BFF`是指`Backend For Frontend`,负责认证授权,负责服务聚合,目标是为前端提供服务。网关和`BFF`的职责可以是重叠的。
-
- ![](/ddd5.png)
-
- ![](/ddd6.png)
-
- ![ddd7](/ddd7.png)
-
- 打造网关
-
- - 添加`Ocelot`
- - 添加配置文件`ocelot.json`
- - 添加配置读取代码
- - 注册`Ocelot`服务
- - 注册`Ocelot`中间件
-
-
-
- 网关和负载均衡的区别
-
- | 方面 | 网关 | 负载均衡 |
- | ---------------- | ------ | ------------------ |
- | OSI模型 | 第7层 | 第4层 |
- | 基于url的路由 | 可以 | 不可以 |
- | 基于cookie的路由 | 可以 | 不可以 |
- | web防火墙 | 可以 | 不可以 |
- | 地域性 | 任意ip | 云服务商下的某个ip |
- | | | |
-
-
-
- 项目介绍
-
- - `Ordering.API`
-
- ```
- "applicationUrl":"https://localhost.5001;http://localhost:5000"
- ```
-
- ```
- public class TestController : ControllerBase
- {
- [HttpGet]
- public IActionResult Abc()
- {
- return Content("Ordering.API");
- }
- }
- ```
-
-
-
-
-
- - `Mobile.ApiAggregator`
-
- ```
- "applicationUrl":"https://localhost.5005;http://localhost:5004"
- ```
-
- ```
- public class TestController : ControllerBase
- {
- [HttpGet]
- public IActionResult Abc()
- {
- return Content("Mobile.ApiAggregator");
- }
- }
- ```
-
-
-
- - `Mobile.Gateway`网关项目
-
- `appSettings.json`
-
- ```
- "Apollo": {
- "AppId": "geektime-mobile-gateway",
- "Env": "DEV",
- "MetaServer": "http://192.168.67.76:8080",
- "ConfigServer": [ "http://192.168.67.76:8080" ]
- },
- "AllowedHosts": "*",
- "ReRoutes": [
- {
- "DownstreamPathTemplate": "/api/{everything}",
- "DownstreamScheme": "http",
- "DownstreamHostAndPorts": [
- {
- "Host": "localhost",
- "Port": 5004
- }
- ],
- "UpstreamPathTemplate": "/mobileAgg/api/{everything}",
- "UpstreamHttpMethod": []
- },
- {
- "DownstreamPathTemplate": "/api/{everything}",
- "DownstreamScheme": "http",
- "DownstreamHostAndPorts": [
- {
- "Host": "localhost",
- "Port": 5000
- }
- ],
- "UpstreamPathTemplate": "/mobile/api/{everything}",
- "UpstreamHttpMethod": []
- }
-
- ],
- "GlobalConfiguration": {
- "RequestIdKey": "OcRequestId",
- "AdministrationPath": "/administration"
- },
- "SecurityKey": "aabbccddffskldjfklajskdlfjlas234234234"
- ```
-
-
-
- `Startup.cs`
-
- ```
- services.AddOcelot(Configuration);
- app.UseOcelot().Wait();
- ```
-
-
-
- `TestController.cs`
-
- ```
- public IActionResult Abc()
- {
- return Content("GeekTime.Mobile.Gateway");
- }
- ```
-
-
-
- # 8、防跨站请求伪造
-
-
-
- ![ddd8](/ddd8.png)
-
-
-
- 如何防御
-
- - 不适用`Cookie`存储或传输身份信息
- - 适用`AntiforgeryToken`机制
- - 避免适用`GET`作为业务操作的请求方法
-
-
-
- 两种选择
-
- - `ValidateAntiForgeryToken`
- - `AutoValidateAntiforgeryToken`
-
-
-
- 举例
-
- ```
- [ValidateAntiForgeryToken]
- public IActionResult CreateOrder(string itemId, int count)
- {
- _logger.LogInformation("创建了订单itemId:{itemId},count:{count}", itemId, count);
- return Content("Order Created");
- }
- ```
-
-
-
- # 9、防开放重定向攻击
-
-
-
- ![ddd9](/ddd9.png)
-
-
-
- 防范错误
-
- - 使用`LoalRedirect`处理重定向:适合重定向仅限于本站的情况
- - 验证重定向的目标域名是否合法
-
-
-
- 举例
-
- ```
- public async Task<IActionResult> Login([FromServices]IAntiforgery antiforgery, string name, string password, string returnUrl)
- {
- HttpContext.Response.Cookies.Append("CSRF-TOKEN", antiforgery.GetTokens(HttpContext).RequestToken, new Microsoft.AspNetCore.Http.CookieOptions { HttpOnly = false });
- var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);//一定要声明AuthenticationScheme
- identity.AddClaim(new Claim("Name", "小王"));
- await this.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
- if (string.IsNullOrEmpty(returnUrl))
- {
- return Content("登录成功");
- }
- try
- {
- var uri = new Uri(returnUrl);
- ///uri.Host 根据配置表数据库表数据来验证
- return Redirect(returnUrl);
- }
- catch
- {
- return Redirect("/");
- }
- //return Redirect(returnUrl);
- }
- ```
-
-
-
- # 10、防跨站脚本
-
-
-
- ![ddd10](/ddd10.png)
-
-
-
- 防范措施
-
- - 对用户内容进行验证,拒绝恶意脚本
- - 对用户提交的内容进行编码`UrlEncoder`,`JavaScriptEncoder`
- - 慎用`HtmlString`和`HtmlHelper.Raw`
- - 身份信息`Cookie`设置为`HttpOnly`
- - 避免使用`Path`传递带有不受信的字符,使用`Query`进行传递
-
-
-
- 举例
-
- ```
- services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
- .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
- {
- options.LoginPath = "/home/login";
- options.Cookie.HttpOnly = true;//这里的设置生效
- });
- ```
-
-
-
- # 11、跨域请求
-
-
-
- 同源
-
- - 方案相同(`HTTP/HTTPS`)
- - 主机或域名相同
- - 端口相同
-
-
-
- `CORS`是什么
-
- - 浏览器允许跨域发起请求
- - 是浏览器的行为协议
- - 并不会让服务器拒绝其它路径发起的`HTTP`请求
- - 开启时需要考虑是否存在被恶意网站攻击的情形
-
-
-
- `CORS`请求头
-
- - `Origin`请求源
- - `Access-Control-Reqyest-Method`
- - `Access-Control-Request-Headers`
-
-
-
- `CORS`响应头
-
- - `Access-Control-Allow-Origin`
- - `Access-Control-Allow-Credentials`
- - `Access-Control-Expose-Headers`
- - `Access-Control-Max-Age`
- - `Access-Control-Allow-Methods`
- - `Access-Control-Allow-Headers`
-
-
-
- 默认支持的`Expose Headers`
-
- - `Cache-Control`
- - `Content-Language`
- - `Content-Type`
- - `Expires`
- - `Last-Modified`
- - `Pragma`
-
-
-
- 举例
-
- ```
-
- services.AddCors(options =>
- {
- options.AddPolicy("api", builder =>
- {
- builder.WithOrigins("https://localhost:5001").AllowAnyHeader().AllowCredentials().WithExposedHeaders("abc");
-
- builder.SetIsOriginAllowed(orgin => true).AllowCredentials().AllowAnyHeader();
- });
- });
-
-
- app.UseCors();
-
- ```
-
- # 12、为不同场景设计合适的缓存策略
-
-
-
- 缓存的场景
-
- - 计算结果缓存:反射对象缓存
- - 请求结果缓存:`DNS`缓存
- - 临时共享数据缓存:会话缓存
- - 热点内容缓存:商品详情页
- - 热点变更逻辑数据:秒杀库存数
-
-
-
- 缓存策略
-
- - 越接近最终的输出结果效果越好
- - 命中率越高越好
-
-
-
- 缓存位置
-
- - 浏览器中
- - 反向代理服务器(负载均衡)
- - 应用进程内存中
- - 分布式存储系统中
-
-
-
- 要点
-
- - `Key`的生成策略,表示数据范围业务含义
- - 缓存失效策略,过期时间机制,主动刷新机制
- - 缓存更新策略,表示更新缓存数据的时机
-
-
-
- 几个问题
-
- - 缓存失效,导致数据不一致
- - 缓存穿透,查询无数据时,导致缓存不生效,查询都落在数据库
- - 缓存击穿,缓存失效瞬间,大量请求访问到数据库
- - 缓存雪崩,大量缓存同一时间失效,导致数据库压力
-
-
-
- 内存缓存和分布式缓存的区别
-
- - 内存缓存可以存储任意对象
- - 分布式缓存的对象需要支持序列化
- - 分布式缓存远程请求可能失败,内存缓存不会
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|