项目分层以及依赖关系
领域模型层
基础设施层
应用层
共享层
1、共享层
XXX.Core:基础项目
基础类型比如说异常或者帮助类等。不应该依赖任何项目。
XXX.Domain.Abstractions:领域抽象层
在领域模型中可以定义一些entity基类 接口 或者领域事件的接口等等。不应该依赖任何项目。
XXX.Infrastructure.Core:基础设施核心层
可以对我们的领域仓库还有我们EFContext定义一些基础共享的代码。
依赖沃恩的领域模型抽象层,实现了我们的仓储
2、领域模型层
目的:专注于业务的设计,不依赖仓储等记住设置层。就是我们理解的实体层
XXX.Domain
里面会有不同的领域聚合,领域事件。
领域对象需要继承领域抽象层,并实现一个聚合根的接口 表明他是一个聚合根。
领域事件需要实现领域事件的接口
3、基础设施层
目的:他的仓库负责领域模型的存取,不负责任何业务代码
XXX.Infrastructure
就是我们实现的仓库(比如说 订单仓库等等)和一些共享代码的实现。
数据库访问的实现:
事务处理的实现:
4、应用层
可以分为两个
XXX.API:接口
XXX.BackgroundTasks:后台任务
推荐:
使用CQRS模式
WEBAPI是面向前端交互的接口,避免依赖领域模型。
领域模型设计
分为领域模型的抽象层和领域模型的实现
一、领域模型抽象层
1、聚合根接口,实现他的表明是一个聚合根。
public interface IAggregateRoot { }
2、领域对象的接口
public interface IEntity { object[] GetKeys(); } public interface IEntity<TKey> : IEntity { TKey Id { get; } }
3、领域对象的实现。里面定义了一些领域对象的公用方法。
public abstract class Entity : IEntity { public abstract object[] GetKeys(); public override string ToString() { return $"[Entity: {GetType().Name}] Keys = {string.Join(",", GetKeys())}"; } #region 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(); } #endregion } public abstract class Entity<TKey> : Entity, IEntity<TKey> { int? _requestedHashCode; public virtual TKey Id { get; protected set; } public override object[] GetKeys() { return new object[] { Id }; } public override bool Equals(object obj) { if (obj == null || !(obj is Entity<TKey>)) return false; if (Object.ReferenceEquals(this, obj)) return true; if (this.GetType() != obj.GetType()) return false; Entity<TKey> item = (Entity<TKey>)obj; if (item.IsTransient() || this.IsTransient()) return false; else return item.Id.Equals(this.Id); } public override int GetHashCode() { if (!IsTransient()) { if (!_requestedHashCode.HasValue) _requestedHashCode = this.Id.GetHashCode() ^ 31; return _requestedHashCode.Value; } else return base.GetHashCode(); } //表示对象是否为全新创建的,未持久化的 public bool IsTransient() { return EqualityComparer<TKey>.Default.Equals(Id, default); } public override string ToString() { return $"[Entity: {GetType().Name}] Id = {Id}"; } public static bool operator ==(Entity<TKey> left, Entity<TKey> right) { if (Object.Equals(left, null)) return (Object.Equals(right, null)) ? true : false; else return left.Equals(right); } public static bool operator !=(Entity<TKey> left, Entity<TKey> right) { return !(left == right); } }
4、领域事件接口
public interface IDomainEvent : INotification { }
在领域模型中领域事件是一个属性
#region 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(); } #endregion
领域事件之应该在领域模型中调用处理,因为领域事件应该跟着领域业务变动触发。
5、领域事件处理接口,借助MediatR
public interface IDomainEventHandler<TDomainEvent> : INotificationHandler<TDomainEvent> where TDomainEvent : IDomainEvent { //这里我们使用了INotificationHandler的Handle方法来作为处理方法的定义 //Task Handle(TDomainEvent domainEvent, CancellationToken cancellationToken); }
一般我们可以在领事件处理中向写队列通知,或者向EventBus写消息等等。
6、值对象
public abstract class ValueObject { protected static bool EqualOperator(ValueObject left, ValueObject right) { if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null)) { return false; } return ReferenceEquals(left, null) || left.Equals(right); } protected static bool NotEqualOperator(ValueObject left, ValueObject right) { return !(EqualOperator(left, right)); } protected abstract IEnumerable<object> GetAtomicValues(); public override bool Equals(object obj) { if (obj == null || obj.GetType() != GetType()) { return false; } ValueObject other = (ValueObject)obj; IEnumerator<object> thisValues = GetAtomicValues().GetEnumerator(); IEnumerator<object> otherValues = other.GetAtomicValues().GetEnumerator(); while (thisValues.MoveNext() && otherValues.MoveNext()) { if (ReferenceEquals(thisValues.Current, null) ^ ReferenceEquals(otherValues.Current, null)) { return false; } if (thisValues.Current != null && !thisValues.Current.Equals(otherValues.Current)) { return false; } } return !thisValues.MoveNext() && !otherValues.MoveNext(); } public override int GetHashCode() { return GetAtomicValues() .Select(x => x != null ? x.GetHashCode() : 0) .Aggregate((x, y) => x ^ y); } }
protected abstract IEnumerable<object> GetAtomicValues(); 获取原子值的方法。
二、领域模型实现层
public class Order : Entity<long>, IAggregateRoot { /// <summary> /// private set作用 领域模型的属性只能自己操作。符合开放封闭原则 /// </summary> public string UserId { get; private set; } public string UserName { get; private set; } public Address Address { get; private set; } public int ItemCount { get; private set; } protected Order() { } 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)); } /// <summary> /// 修改只能在领域模型内部操作 /// </summary> /// <param name="address"></param> public void ChangeAddress(Address address) { this.Address = address; //this.AddDomainEvent(new OrderAddressChangedDomainEvent(this)); } }
public class Address : ValueObject { public string Street { get; private set; } public string City { get; private set; } public string ZipCode { get; private set; } public Address() { } public Address(string street, string city, string zipcode) { Street = street; City = city; ZipCode = zipcode; } protected override IEnumerable<object> GetAtomicValues() { // Using a yield return statement to return each element one at a time yield return Street; yield return City; yield return ZipCode; } }
Order模型
1、属性
/// <summary>
/// private set作用 领域模型的属性只能自己操作。符合开放封闭原则
/// </summary>
public string UserId { get; private set; }
2、构造函数,创建对象
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)); }
3、属性修改
属性的修改,只能在模型内部,用具有意义的方法修改
/// <summary> /// 修改只能在领域模型内部操作 /// </summary> /// <param name="address"></param> public void ChangeAddress(Address address) { this.Address = address; //this.AddDomainEvent(new OrderAddressChangedDomainEvent(this)); }
4、聚合根
public class Order : Entity<long>, IAggregateRoot
Address模型
1、值类型
public class Address : ValueObject
必须实现GetAtomicValues获取元素方法
protected override IEnumerable<object> GetAtomicValues() { // Using a yield return statement to return each element one at a time yield return Street; yield return City; yield return ZipCode; }
三、注意事项
1、领域模型字段修改设为私有
2、使用构造函数表示对象的创建
3、使用具有业务含义的方法来操作模型字段
4、领域模型负责对自己数据大的处理
5、领域服务或者命令处理者(上层处理者) 负责调用领域模型业务动作(这样可以区分领域模型内在逻辑 和 外在逻辑)
仓储设置
一、工作单元
使用同一个上线文
实体状态跟踪
保证事务一致性
二、工作单元接口
public interface IUnitOfWork : IDisposable { Task<int> SaveChangesAsync(CancellationToken cancellationToken = default); Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default); }
第一个返回影响条数
第二个返回bool表示是否成功
三、事务接口
public interface ITransaction { IDbContextTransaction GetCurrentTransaction(); bool HasActiveTransaction { get; } Task<IDbContextTransaction> BeginTransactionAsync(); Task CommitTransactionAsync(IDbContextTransaction transaction); void RollbackTransaction(); }
GetCurrentTransaction
获取当前事务
HasActiveTransaction
判断事物事物开始
BeginTransactionAsync
开始事务
CommitTransactionAsync
提交事务
RollbackTransaction
事务回滚
四、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; } #region IUnitOfWork public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default) { var result = await base.SaveChangesAsync(cancellationToken); await _mediator.DispatchDomainEventsAsync(this); return true; } #endregion #region ITransaction private IDbContextTransaction _currentTransaction; public IDbContextTransaction GetCurrentTransaction() => _currentTransaction; public bool HasActiveTransaction => _currentTransaction != null; public Task<IDbContextTransaction> BeginTransactionAsync() { if (_currentTransaction != null) return null; _currentTransaction = Database.BeginTransaction(_capBus, autoCommit: false); return Task.FromResult(_currentTransaction); } public async Task CommitTransactionAsync(IDbContextTransaction transaction) { if (transaction == null) throw new ArgumentNullException(nameof(transaction)); if (transaction != _currentTransaction) throw new InvalidOperationException($"Transaction {transaction.TransactionId} is not current"); try { await SaveChangesAsync(); transaction.Commit(); } catch { RollbackTransaction(); throw; } finally { if (_currentTransaction != null) { _currentTransaction.Dispose(); _currentTransaction = null; } } } public void RollbackTransaction() { try { _currentTransaction?.Rollback(); } finally { if (_currentTransaction != null) { _currentTransaction.Dispose(); _currentTransaction = null; } } } #endregion }
继承DbContext, IUnitOfWork, ITransaction
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(); 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 interface IRepository<TEntity> where TEntity : Entity, IAggregateRoot { IUnitOfWork UnitOfWork { get; } TEntity Add(TEntity entity); Task<TEntity> AddAsync(TEntity entity, CancellationToken cancellationToken = default); TEntity Update(TEntity entity); Task<TEntity> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default); bool Remove(Entity entity); Task<bool> RemoveAsync(Entity entity); } public interface IRepository<TEntity, TKey> : IRepository<TEntity> where TEntity : Entity<TKey>, IAggregateRoot { bool Delete(TKey id); Task<bool> DeleteAsync(TKey id, CancellationToken cancellationToken = default); TEntity Get(TKey id); Task<TEntity> GetAsync(TKey id, CancellationToken cancellationToken = default); }
public abstract class Repository<TEntity, TDbContext> : IRepository<TEntity> where TEntity : Entity, IAggregateRoot where TDbContext : EFContext { protected virtual TDbContext DbContext { get; set; } public Repository(TDbContext context) { this.DbContext = context; } public virtual IUnitOfWork UnitOfWork => DbContext; public virtual TEntity Add(TEntity entity) { return DbContext.Add(entity).Entity; } public virtual Task<TEntity> AddAsync(TEntity entity, CancellationToken cancellationToken = default) { return Task.FromResult(Add(entity)); } public virtual TEntity Update(TEntity entity) { return DbContext.Update(entity).Entity; } public virtual Task<TEntity> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) { return Task.FromResult(Update(entity)); } public virtual bool Remove(Entity entity) { DbContext.Remove(entity); return true; } public virtual Task<bool> RemoveAsync(Entity entity) { return Task.FromResult(Remove(entity)); } } public abstract class Repository<TEntity, TKey, TDbContext> : Repository<TEntity, TDbContext>, IRepository<TEntity, TKey> where TEntity : Entity<TKey>, IAggregateRoot where TDbContext : EFContext { public Repository(TDbContext context) : base(context) { } public virtual bool Delete(TKey id) { var entity = DbContext.Find<TEntity>(id); if (entity == null) { return false; } DbContext.Remove(entity); return true; } public virtual async Task<bool> DeleteAsync(TKey id, CancellationToken cancellationToken = default) { var entity = await DbContext.FindAsync<TEntity>(id, cancellationToken); if (entity == null) { return false; } DbContext.Remove(entity); return true; } public virtual TEntity Get(TKey id) { return DbContext.Find<TEntity>(id); } public virtual async Task<TEntity> GetAsync(TKey id, CancellationToken cancellationToken = default) { return await DbContext.FindAsync<TEntity>(id, cancellationToken); } }
MediatR和CAP和NserviceBus的知识自行学习
接口设计
1、接口控制器作用
定义输入输出
身份认证
授权
不因该处理我们的领域模型,仓储
2、分离
IMediator _mediator; public OrderController(IMediator mediator) { _mediator = mediator; } [HttpPost] public async Task<long> CreateOrder([FromBody]CreateOrderCommand cmd) { return await _mediator.Send(cmd, HttpContext.RequestAborted); } [HttpGet] public async Task<List<string>> QueryOrder([FromQuery]MyOrderQuery myOrderQuery) { return await _mediator.Send(myOrderQuery); }
用mediatr分离控制器和一些业务逻辑
尽量使用一部
集成事件 EventBus
集成事件工作原理:
目的实现我们系统的集成,系统里面多个微服务之间相互传递事件
集成事件的实现:
1、如上图发布订阅模式
在mediatr的处理者中,发布消息,其他服务订阅,(消息一般发布队列中)。 现成的发布订阅组件 cap nervicebus等等,也可以自己封装(写对列 消费)
2、观察者模式。(由观察者将事件发给关注的人)
RabbitMQ实现集成事件的发布订阅
cap+rabbitmq
nservicebus+rabbitmq
自己封装事件组件+rabbitmq
总结:
集成事件是跨服务的领域事件
集成事件一般由领域事件驱动触发。(业务触发领域事件 领域事件 发送集成事件)
不通过事务来处理集成事件(实现最终一致性)
仅在必要的情况下定义和使用集成事件