重新整理 .net core 实践篇—————应用分层[二十四]
前言
简单整理一下分层。
正文
应用程序分层,分为:
1.领域模型层
2.基础设施层
3.应用层
4.共享层
共享层
共享层一般包括下面几个类库。
- 有一个Core 的类库,比如说BLog.Core.
这个类库用来,主要用来承载一些基础简单的类型,比如说一下帮助类。
- 共享层的抽象层。 比如说有一个Blog.Domain.Abstractions(Domain就是领域模型) 这样一个抽象层。
这个抽象成用来在领域模型中定义一些基类或者接口或者领域事件的接口、领域事件处理的接口还有entity的接口和entity的基类。
领域模型中定义一些基类或者接口或者领域事件的接口,比如说:
/// <summary>
/// 领域事件接口
/// 用来标记我们某一个对象是否是领域事件
/// </summary>
public interface IDomainEvent : INotification
{
}
/// <summary>
/// 领域事件处理器接口
/// </summary>
public interface IDomainEventHandler<TDomainEvent> : INotificationHandler<TDomainEvent> where TDomainEvent : IDomainEvent
{
//这里我们使用了INotificationHandler的Handle方法来作为处理方法的定义,所以无需重新定义
//Task Handle(TDomainEvent domainEvent, CancellationToken cancellationToken);
}
/// <summary>
/// 值对象
/// TODO 领域驱动中比较关键的类
/// </summary>
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 NotEqualQperator(ValueObject left, ValueObject right)
{
return !(EqualOperator(left, right));
}
/// <summary>
/// 获取值对象原子值(字段值)
/// 将我们值对象的字段输出出来,作为唯一标识来判断我们两个对象是否想等
/// </summary>
/// <returns></returns>
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);
}
}
这种驱动领域的一些处理抽象。
- 基础设施的核心层,比如说可以取名:Blog.Infrastructure.core
是指我们可以对仓储还有EFContext定义一些共享代码。
这个共享层,一般会单独打包成一个dll,然后会放在公司的nuget管理里面。
比如说:泛型仓储接口
/// <summary>
/// 泛型仓储接口
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
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);
}
/// <summary>
/// 泛型仓储接口
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <typeparam name="TKey">主键Id类型</typeparam>
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);
}
EF上下文:
/// <summary>
/// EF上下文
/// 注:在处理事务的逻辑部分,需要嵌入CAP的代码,构造函数参数 ICapPublisher
/// </summary>
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
/// <summary>
/// 保存实体变更
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
{
var result = await base.SaveChangesAsync(cancellationToken);
// 执行发送领域事件
await _mediator.DispatchDomainEventsAsync(this);
return true;
}
///// <summary>
///// IUniOfWork中该方法的定义与DbContext中的SaveChangesAsync一致,所以此处无需再进行实现
///// </summary>
///// <param name="cancellationToken"></param>
///// <returns></returns>
//public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
//{
// return base.SaveChangesAsync();
//}
#endregion
#region ITransaction
/// <summary>
/// 当前事务
/// </summary>
private IDbContextTransaction _currentTransaction;
/// <summary>
/// 公开方法,返回当前私有事务对象
/// </summary>
/// <returns></returns>
public IDbContextTransaction GetCurrentTransaction() => _currentTransaction;
/// <summary>
/// 当前事务是否开启
/// </summary>
public bool HasActiveTransaction => _currentTransaction == null;
/// <summary>
/// 开启事务
/// </summary>
/// <returns></returns>
public Task<IDbContextTransaction> BeginTransactionAsync()
{
if (_currentTransaction != null)
{
return null;
}
// 该扩展方法是由CAP组件提供
// 创建事务时,也要把 ICapPublisher 传入
// 核心作用是将我们要发送事件逻辑与我们业务的存储都放在同一个事务内部,从而保证事件与业务逻辑的存取都是一致的
_currentTransaction = Database.BeginTransaction(_capBus, autoCommit: false);
return Task.FromResult(_currentTransaction);
}
/// <summary>
/// 提交事务
/// </summary>
/// <param name="transaction"></param>
/// <returns></returns>
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
{
// 提交事务之前,安全起见还是要 SaveChanges 一下,保存变更到数据库
await SaveChangesAsync();
transaction.Commit();
}
catch (Exception ex)
{
RollbackTransaction();
throw;
}
finally
{
if (_currentTransaction!=null)
{
_currentTransaction.Dispose();
_currentTransaction = null;
}
}
}
/// <summary>
/// 回滚事务
/// </summary>
public void RollbackTransaction()
{
try
{
_currentTransaction?.Rollback();
}
finally
{
if (_currentTransaction!=null)
{
_currentTransaction.Dispose();
_currentTransaction = null;
}
}
}
#endregion
}
Domain 层(领域模型)
里面就是一些领域事件,领域结构。
基础设施层
一般用来定义了仓储层的实现
应用层
就是我们的api接口层了还有就是我们的一些job任务类库。
这里的Application如果是大一点的项目其实是会独立出去的。
可以叫做:Blog.Application这样子。里面可以存放一些服务或者领域模型的命令和查询等。
尽量让应用层只做和客户端的交互,如果需要其他服务,在其他类库中调用即可。
梳理
上面看这些可能有点乱,这里来梳理一下。
什么是共享层。 以前呢,我们会有一个common 类库,专门用来存放共享类的,比如一些helper类了。
但是呢,人们发现一个问题,因为common类库呢,会引用很多包,就有点乱了。
第二个呢,common包里面的helper越来越多,越来越难管理,找起来也麻烦。
然后很多东西就独立出来了,比如说基础设施类库,这个类库用来做一些基础设施的。
那么什么是基础设施呢?就是说如果这个项目没有什么是跑不起来的,就比如说这个项目要和数据库打交道,那么数据库就是基础设施了。
然后呢,领域驱动又属于一个独立的东西,那么又可以分为一个类库了。
总的来说是对我们的一个common类库进行梳理。
然后来说一下基础设施层和领域驱动层,这些就是该项目的实现,需要什么功能那么写什么样的接口。
应用层就是和客户端打交道的层。
应用层不是说只是一个应用api,比如说blog.api这样的。
它可以独立出去很多类库,比如说blog.aplication(一些服务或者一些具体业务)或者blog.BackgroundTasks(一些后台服务)让blog.api只专注于和客户端打交道的。
总结
-
领域模型专注于业务的设计,不依赖仓储等基础设施层。
-
基础设施的仓储层仅负责领域模型的取出和存储
-
使用CQRS(查询与命令分开)模型设计引用层。
-
Web Api 是面向前端交互的接口,避免依赖领域模型
-
将共享代码设计为共享包,使用私有Nuget 仓库分发管理
结
下一节领域模型内在逻辑和外在行为。