26 | 工程结构概览:定义应用分层及依赖关系
从这一节开始进入微服务实战部分
这一节主要讲解工程的结构和应用的分层
在应用的分层这里定义了四个层次:
1、领域模型层
2、基础设施层
3、应用层
4、共享层
可以通过代码来看一下
源码链接:
https://github.com/witskeeper/geektime/tree/master/microservices
共享层一共建立三个工程:
1、GeekTime.Core:主要承载基础的简单的类型,比如说异常或者一些帮助类
2、GeekTime.Domain.Abstractions:抽象层,领域的抽象是指在领域模型可以定义一些基类或者接口,领域事件接口,领域事件处理接口,还有 Entity 的接口和 Entity 的基类
3、GeekTime.Infrastructure.Core:基础设施的核心层,是指对仓储,还有 EFContext 定义一些共享代码
这些包实际上在不同的项目里面都可以共享,所以建议的做法是把这些代码都通过私有的 NuGet 的仓库来存储,然后其他的工程可以使用 NuGet 包来直接引用即可
领域模型层就是定义领域模型的地方,这里面会有不同的聚合,还有领域事件,不同的聚合下面就是领域模型
基础设施层是仓储层和一些共享代码的实现,这里只定义了仓储层的实现,包括 EF 的 DomainContext,还有 Order 的仓储层,User 的仓储层,还定义了领域模型与数据库之间的映射关系,就是在 EntityConfigurations 这目录下面去定义
应用层分两个,一个工程是 API 层,是用来承载 Web API 或者 Web 应用的,另外一个是后台任务,这个就是用来执行一些特殊的 Job,作为 Job 的宿主运行的,它可以是一个控制台的应用程序
在 Web 层,Web API 层,也分了几个关键目录 Application,Controllers,Extensions,Infrastructure
基础设施层会放一些身份认证缓存之类的与基础设施交互相关的一些代码
扩展层主要是将服务注册进容器的代码和中间件配置的代码,也就是两扩展方法,一个是对 ServiceCollection 的扩展,一个是对 ApplicationBuilder 的扩展
控制器层主要用来定义 Web API,这一层就是定义前后端交互的接口
应用层使用了 CQRS 的设计模式,就是命令与查询职责分离,把命令放在一个目录,把查询放在一个目录,同样的这里还有两个事件处理的目录,一个是领域模型,领域事件的处理,一个是集成事件的处理
再看一下各层之间的依赖关系
Shared 层实际上是不依赖任何层次的,它存储了共享的代码,被各个工程共享
GeekTime.Core,GeekTime.Domain.Abstractions 是不依赖任何工程的,而 GeekTime.Infrastructure.Core 依赖了 GeekTime.Domain.Abstractions,实现了仓储,比如说仓储会依赖 IAggregateRoot 接口
public interface IRepository<TEntity> where TEntity : Entity, IAggregateRoot
领域模型需要继承模型的基类,并且实现一个聚合根的接口,表示它是一个聚合根
public class Order : Entity<long>, IAggregateRoot
领域事件需要实现一个领域事件的接口
public class OrderCreatedDomainEvent : IDomainEvent
基础设施层是一个独立的程序集,实现了仓储的部分,定义了一个 Order 的仓储
public interface IOrderRepository : IRepository<Order, long>
还定义了 Order 仓储的实现
public class OrderRepository : Repository<Order, long, DomainContext>, IOrderRepository
{public OrderRepository(DomainContext context) : base(context){}
}
这里可以看到仓储实际上依赖了基础设施层共享代码里面的仓储的定义 IRepository,这样就可以复用仓储层的代码,这样定义 OrderRepository 就会比较简单,可以复用 Repository 的一些实现
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);}
}
已经实现了一些基本的方法,增删改查的方法
数据库访问的实现,继承了自己定义的 EFContext,EFContext 作为共享代码在各个工程里面复用
public class DomainContext : EFContext
另外一个比较特殊的是事务处理的对象,这个对象是用来管理整个应用程序的请求上下文中的事务,这样就可以避免手动地去处理事务,简化代码
public class DomainContextTransactionBehavior<TRequest, TResponse> : TransactionBehavior<DomainContext, TRequest, TResponse>
{public DomainContextTransactionBehavior(DomainContext dbContext, ICapPublisher capBus, ILogger<DomainContextTransactionBehavior<TRequest, TResponse>> logger) : base(dbContext, capBus, logger){}
}
应用层依赖了基础设施层,基础设施层又依赖了领域层
应用层实际上是把各层组装在一起的这一层,它是应用程序的一个宿主,协调各层之间的关系,以及组装代码都是在这里实现的
总结一下
领域模型层专注于业务的设计,它不依赖于其他各层,它是相对独立的
基础设施的仓储层仅仅负责领域模型的存取,它不负责任何的业务逻辑代码的承载
推荐使用 CQRS 的模式来设计应用程序,使应用程序的代码结构更加的合理,在团队和项目膨胀的情况下,工程的可维护性不至于急剧的下降
Web API 是面向前端交互的接口,避免依赖领域模型
共享代码建议设计为共享包,使用私有的 NuGet 仓库来分发和管理