重新整理 .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 仓库分发管理
结
下一节领域模型内在逻辑和外在行为。
重新整理 .net core 实践篇—————应用分层[二十四]的更多相关文章
- 重新整理 .net core 实践篇—————异常中间件[二十]
前言 简单介绍一下异常中间件的使用. 正文 if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } 这样写入中间件哈,那么在env环 ...
- 重新整理 .net core 实践篇—————Mediator实践[二十八]
前言 简单整理一下Mediator. 正文 Mediator 名字是中介者的意思. 那么它和中介者模式有什么关系呢?前面整理设计模式的时候,并没有去介绍具体的中介者模式的代码实现. 如下: https ...
- 重新整理 .net core 实践篇—————领域事件[二十九]
前文 前面整理了仓储层,工作单元模式,同时简单介绍了一下mediator. 那么就mediator在看下领域事件启到了什么作用吧. 正文 这里先注册一下MediatR服务: // 注册中间者:Medi ...
- 重新整理 .net core 实践篇—————静态中间件[二十一]
前言 简单整理一下静态中间件. 正文 我们使用静态文件调用: app.UseStaticFiles(); 那么这个默认会将我们根目录下的wwwroot作为静态目录. 这个就比较值得注意的,可能刚开始学 ...
- 重新整理 .net core 实践篇——— 权限源码阅读四十五]
前言 简单介绍一下权限源码阅读一下. 正文 一直有人对授权这个事情上争论不休,有的人认为在输入账户密码给后台这个时候进行了授权,因为认为发送了一个身份令牌,令牌里面可能有些用户角色信息,认为这就是授权 ...
- 重新整理 .net core 实践篇————cookie 安全问题[三十八]
前言 简单整理一下cookie的跨站攻击,这个其实现在不常见,因为很多公司都明确声明不再用cookie存储重要信息,不过对于老站点还是有的. 正文 攻击原理: 这种攻击要达到3个条件: 用户访问了我们 ...
- 重新整理 .net core 实践篇————重定向攻击[三十九]
前言 简单介绍一下重定向攻击. 正文 攻击思路: 看着上面挺复杂的,其实是一些很简单的步骤. 攻击者通过某些手段,让用户打开了一个好站点,打开的这个地址里面带有重定向信息,重定向信息就是自己伪造的站点 ...
- 重新整理 .net core 实践篇————配置应用[一]
前言 本来想整理到<<重新整理.net core 计1400篇>>里面去,但是后来一想,整理 .net core 实践篇 是偏于实践,故而分开. 因为是重新整理,那么就从配置开 ...
- 学习ASP.NET Core Blazor编程系列二十二——登录(1)
学习ASP.NET Core Blazor编程系列文章之目录 学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应 ...
- 学习ASP.NET Core Blazor编程系列二十五——登录(4)
学习ASP.NET Core Blazor编程系列文章之目录 学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应 ...
随机推荐
- C++ //类模板成员函数类外实现
1 #include <iostream> 2 #include <string> 3 #include<fstream> 4 using namespace st ...
- XAF Blazor TabbedMdi
开源项目地址:https://gitee.com/easyxaf/blazor-tabbed-mdi 前言 XAF在WinForm中采用了多文档界面(MDI),但在Blazor中却没有,在官网中也有人 ...
- 图数据库基准测试 LDBC SNB 系列讲解:Schema 和数据生成的机制
LDBC(Linked Data Benchmark Council)Social Network Benchmark,简称 LDBC SNB,是一种针对社交网络场景的评估图数据库性能的基准测试. L ...
- iVCam 可以当电脑的摄像头 同一个wifi
iVCam 可以当电脑的摄像头 同一个wifi
- Kotlin 快速遍历File及子目录筛选指定类型文件
原文: Kotlin 快速遍历File及子目录筛选指定类型文件 - Stars-One的杂货小窝 在做文件相关的app,经常会遇到筛选某个文件夹下的符合条件的文件对象,且要包含子文件夹,之前一直是自己 ...
- Kotlin学习快速入门(11)—— 枚举类的使用
原文地址:Kotlin学习快速入门(11)-- 枚举类的使用 - Stars-One的杂货小窝 由于有时候偶尔用到枚举类,所以简单记录一下,和Java的一起对比记录 下面以一个简单的四季设计一个枚举类 ...
- window.showModalDialog与opener及returnValue
首先来看看 window.showModalDialog 的参数 vReturnValue = window.showModalDialog(sURL [, vArguments] [, sFeatu ...
- HiSi 3516CV500 NNIE(Neural Network Inference Engine) 摸鱼记录(2) --- 模型生成及模型仿真(实例分析)
PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明 本文作为本人csdn blog的主站的备份.(Bl ...
- GaussDB(分布式)实例故障处理
本文分享自华为云社区<GaussDB(分布式)实例故障处理>,作者:subverter. 一.说明 GaussDB Kernel实例出现故障时,可以按照本节的办法进行实例快速修复. 1.执 ...
- 数据好合: Argilla 和 Hugging Face Spaces 携手赋能社区合力构建更好的数据集
最近,Argilla 和 Hugging Face 共同 推出 了 Data is Better Together 计划,旨在凝聚社区力量协力构建一个对提示进行排名的偏好数据集.仅用几天,我们就吸引了 ...