重新整理.netcore实践篇—————应用分层[二十四](代码片段)

你永远想象不到,一个光鲜亮丽的Application,有多么 你永远想象不到,一个光鲜亮丽的Application,有多么肮脏的Code     2022-11-30     167

关键词:

前言

简单整理一下分层。

正文

应用程序分层,分为:

1.领域模型层

2.基础设施层

3.应用层

4.共享层

共享层

共享层一般包括下面几个类库。

  1. 有一个Core 的类库,比如说BLog.Core.

这个类库用来,主要用来承载一些基础简单的类型,比如说一下帮助类。

  1. 共享层的抽象层。 比如说有一个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);
	

这种驱动领域的一些处理抽象。

  1. 基础设施的核心层,比如说可以取名: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只专注于和客户端打交道的。

总结

  1. 领域模型专注于业务的设计,不依赖仓储等基础设施层。

  2. 基础设施的仓储层仅负责领域模型的取出和存储

  3. 使用CQRS(查询与命令分开)模型设计引用层。

  4. Web Api 是面向前端交互的接口,避免依赖领域模型

  5. 将共享代码设计为共享包,使用私有Nuget 仓库分发管理

下一节领域模型内在逻辑和外在行为。

重新整理.netcore实践篇—————entity的定义[二十五](代码片段)

前言简单介绍一下实体模型的设计。正文前文提及了我们的应用分为:共享层基础设施层领域层应用层今天来介绍领域模型层。前文提及到领域模型在共享层有一个领域模型抽象类库。里面有这些类:先分别介绍一下这些类是做... 查看详情

重新整理.netcore实践篇—————中间件[十九](代码片段)

前言简单介绍一下.netcore的中间件。正文官方文档已经给出了中间件的概念图:和其密切相关的是下面这两个东西:IApplicationBuilder和RequestDelegate(HttpContextcontext)IApplicationBuilder:publicinterfaceIApplicationBuilderIServiceProviderApplicationService 查看详情

重新整理.netcore实践篇—————工作单元模式[二十六](代码片段)

前言简单整理一下工作单元模式。正文工作单元模式有3个特性,也算是其功能:使用同一上下文跟踪实体的状态保障事务一致性工作单元模式主要关注事务,所以重点在事务上。在共享层的基础建设类库中加入:///<summary>///工... 查看详情

重新整理.netcore实践篇—————静态中间件[二十一](代码片段)

前言简单整理一下静态中间件。正文我们使用静态文件调用:app.UseStaticFiles();那么这个默认会将我们根目录下的wwwroot作为静态目录。这个就比较值得注意的,可能刚开始学.netcore的小伙伴,会直接把脚本写在更目录script这样是访... 查看详情

重新整理.netcore实践篇—————仓储层的具体实现[二十七](代码片段)

前言简单整理一下仓储层。正文在共享层的基础建设类库中:///<summary>///泛型仓储接口///</summary>///<typeparamname="TEntity">实体类型</typeparam>publicinterfaceIRepository<TEntity>whereTEntity:Entity,IAggregate 查看详情

重新整理.netcore实践篇—————路由和终结点[二十三](代码片段)

前言简单整理一下路由和终节点。正文路由方式主要有两种:1.路由模板方式2.RouteAttribute方式路由约束:1.类型约束2.范围约束3.正则表达式4.是否必选5.自定义IRootConstaintURL生成1.LinKGenerator2.IUrlHelper先搭建一个swagger:services.AddSwaggerGen(... 查看详情

重新整理.netcore实践篇—————微服务的桥梁eventbus[三十一](代码片段)

前言简单介绍一下EventBus.正文EventBus也就是集成事件,用于服务与服务之间的通信。比如说我们的订单处理事件,当订单处理完毕后,我们如果通过api马上去调用后续接口。比如说订单完成给用户通知的话,如果是大量订单,即... 查看详情

重新整理.netcore实践篇—————日志系统之结构化[十八](代码片段)

前言什么是结构化呢?结构化,就是将原本没有规律的东西进行有规律话。就比如我们学习数据结构,需要学习排序然后又要学习查询,说白了这就是一套,没有排序,谈如何查询是没有意义的,因为查询算法就是根据某种规律... 查看详情

重新整理.netcore实践篇—linux上排查问题实用工具[外篇]

前言介绍下面几个工具:Lldbcreatedumpdotnet-dumpdotnet-gcdumpdotnet-symbolProcdump该文的前置篇为:https://www.cnblogs.com/aoximin/p/16839812.html献给初学者,这篇就只介绍下看下日志和lldb,毕竟东西太多了。正文我以官网的例子作为演示:... 查看详情

javaweb应用的代码分层最佳实践

JavaWeb应用的代码分层最佳实践代码分层,对于任何一个JavaWeb开发来说应该都不陌生。一个好的层次划分不仅可以能使代码结构更加清楚,还可以使项目分工更加明确,可读性大大提升,更加有利于后期的维护和升级。从另外一... 查看详情

istio实践-路由控制及多应用部署(netcore&springboot)

...接上一篇istio应用部署及服务间调用,本文介绍通过构建.netcore与springboot简单服务应用,实现服务间调用及相关路由控制等1、.netcore代码介绍及应用部署新建.netcorewebapi服务项目,添加简单服务调用方法(getStrByServiceName与getS 查看详情

.netcore实践篇————网关

参考技术A简单整理一下网关。在介绍网关之前,介绍一下BFF,BFF全称是BackendForFrontend,它负责认证授权,服务聚合,目标是为前端提供服务。说的通透一点,就是有没有见过这种服务。上述就是buff通过代理其他服务来让前端访... 查看详情

asp.netcore中间件应用实践中你不知道的那些事(代码片段)

...工作原理的同学,可以点击查看以下两篇解读文章:Asp.NetCoreEndPoint终结点路由工作原理解读ASP.NETCORE管道模型及中间件使用解读1.1中间件(Middleware)的作用我们知道,任何的一个web框架都是把http请求封装成一个管道,每一次的请... 查看详情

net开源项目整理

参考技术A整理一些平时收藏和应用的开源代码,方便自己学习和查阅1.应用nopcommerce,开源电商网站,开发环境asp.netmvc(未支持.netcore),使用技术(autofac,ef,页面插件等)https://github.com/nopSolutions/nopCommerceOrchardCMS,内容管理网站http... 查看详情

.netcore杂记

...解了.netcore设计理念和设计思想(纯属跟人理解)。再此整理了之前写的一些学习笔记,后续也会把新的学习新的加上。1..netcore跨平台实践2.asp.netcore使用EF7CodeFirst创建数据库,同时使用命令创建数据库 查看详情

n 分层架构中的实体框架 - 要遵循的最佳实践?

....microsoft.com/en-us/magazine/cc700340.aspx但想知道在asp.net、n分层应用程序中使用实体框架(.net框 查看详情

ddd实践_如何使用ddd设计代码模型

...目录结构三、各层目录结构四、注意事项五、领域对象的整理六、从领域模型到微服务的设计七、领域层的领域对象八、应用层的领域对象九、领域对象与微服务代码对象的映射DDD并没有给出标准的代码模型,因此一千人就... 查看详情

asp.netcore在centos上的最小化部署实践(代码片段)

原文:ASP.NETCore在CentOS上的最小化部署实践引言    本文从Linux小白的视角,在CentOS7.x服务器上搭建一个Nginx-PoweredAspNetCoreWeb准生产应用。在开始之前,我们还是重温一下部署原理,正如你所常见的.NetCore部署图:在Lin... 查看详情