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

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

关键词:

前言

简单介绍一下实体模型的设计。

正文

前文提及了我们的应用分为:

  1. 共享层

  2. 基础设施层

  3. 领域层

  4. 应用层

今天来介绍领域模型层。

前文提及到领域模型在共享层有一个领域模型抽象类库。

里面有这些类:

先分别介绍一下这些类是做什么的。

IEntity 类,是我们实体的接口:

/// <summary>
/// 实体接口(包含多个主键的实体接口)
/// </summary>
public interface IEntity

	object[] GetKeys();


/// <summary>
/// 实体接口(包含唯一主键Id的实体接口)
/// </summary>
/// <typeparam name="TKey">主键ID类型</typeparam>
public interface IEntity<TKey> : IEntity

	TKey Id  get; 

实现抽象类Entity:

/// <summary>
/// 实体抽象类(包含多个主键的实体接口)
/// </summary>
public abstract class Entity : IEntity

	public abstract object[] GetKeys();

	public override string ToString()
	
		return $"[Entity:GetType().Name] Keys = string.Join(",", GetKeys())";
	

	#region 领域事件定义处理 DomainEvents

	/// <summary>
	/// 领域事件集合
	/// </summary>
	private List<IDomainEvent> _domainEvents;

	/// <summary>
	/// 获取当前实体对象领域事件集合(只读)
	/// </summary>
	public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents?.AsReadOnly();

	/// <summary>
	/// 添加领域事件至当前实体对象领域事件结合中
	/// </summary>
	/// <param name="eventItem"></param>
	public void AddDomainEvent(IDomainEvent eventItem)
	
		_domainEvents = _domainEvents ?? new List<IDomainEvent>();
		_domainEvents.Add(eventItem);
	

	/// <summary>
	/// 移除指定领域事件
	/// </summary>
	/// <param name="eventItem"></param>
	public void RemoveDomainEvent(IDomainEvent eventItem)
	
		_domainEvents?.Remove(eventItem);
	

	/// <summary>
	/// 清空所有领域事件
	/// </summary>
	public void ClearDomainEvents()
	
		_domainEvents?.Clear();
	

	#endregion


/// <summary>
/// 实体抽象类(包含唯一主键Id的实体接口)
/// </summary>
/// <typeparam name="TKey">主键ID类型</typeparam>
public abstract class Entity<TKey> : Entity, IEntity<TKey>

	int? _requestedHasCode;
	public virtual TKey Id  get; protected set; 
	public override object[] GetKeys()
	
		return new object[]  Id ;
	

	/// <summary>
	/// 对象是否想等
	/// </summary>
	/// <param name="obj"></param>
	/// <returns></returns>
	public override bool Equals(object obj)
	
		if (obj == null || !(obj is Entity<TKey>))
		
			return false;
		

		if (Object.ReferenceEquals(this, obj))
		
			return true;
		

		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 (!_requestedHasCode.HasValue)
			
				_requestedHasCode = this.Id.GetHashCode() ^ 31; // TODO
			
			return _requestedHasCode.Value;
		
		else
		
			return base.GetHashCode();
		
	

	/// <summary>
	/// 对象是否为全新创建的,未持久化的
	/// </summary>
	/// <returns></returns>
	public bool IsTransient()
	
		return EqualityComparer<TKey>.Default.Equals(Id, default);
	
	public override string ToString()
	
		return $"[Entity:GetType().Name] Id = Id";
	

	/// <summary>
	/// == 操作符重载
	/// </summary>
	/// <param name="left"></param>
	/// <param name="right"></param>
	/// <returns></returns>
	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);
		
	

	/// <summary>
	/// != 操作符重载
	/// </summary>
	/// <param name="left"></param>
	/// <param name="right"></param>
	/// <returns></returns>
	public static bool operator !=(Entity<TKey> left, Entity<TKey> right)
	
		return !(left == right);
	

聚合根接口IAggregateRoot.cs:

/// <summary>
/// 聚合根接口
/// 作用是我们在实现仓储层的时候,让我们的一个仓储对应一个聚合根
/// </summary>
public interface IAggregateRoot


领域事件IDomainEvent :

/// <summary>
/// 领域事件接口
/// 用来标记我们某一个对象是否是领域事件
/// </summary>
public interface IDomainEvent : INotification


领域事件的处理接口IDomainEventHandler:

/// <summary>
/// 领域事件处理器接口
/// </summary>
public interface IDomainEventHandler<TDomainEvent> : INotificationHandler<TDomainEvent> where TDomainEvent : IDomainEvent

	//这里我们使用了INotificationHandler的Handle方法来作为处理方法的定义,所以无需重新定义
	//Task Handle(TDomainEvent domainEvent, CancellationToken cancellationToken);

值对象 ValueObject:

/// <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);
	

那么来看一下领域模型的具体实现:

先来看一下Aggregate 的具体实现:

/// <summary>
/// 订单实体
/// </summary>
public class Order : Entity<long>, IAggregateRoot

	// 实体内字段的 set 方法都是 private 的
	// 实体类型相关的数据操作,都应该是由我们实体来负责,而不是被外部的对象去操作
	// 这样的好处是让我们的领域模型符合封闭开放的原则

	public string UserId  get; private set; 
	public string UserName  get; private set; 
	public Address Address  get; private set; 
	public int ItemCount  get; set; 

	protected Order()
	
	

	public Order(string userId, string userName, int itemCount, Address address)
	
		this.UserId = userId;
		this.UserName = userName;
		this.ItemCount = itemCount;
		this.Address = address;

		// 构造新的Order对象的时候,添加一个创建Order领域事件
		this.AddDomainEvent(new OrderCreatedDomainEvent(this));
	

	/// <summary>
	/// 修改收货地址
	/// </summary>
	/// <param name="address"></param>
	public void ChangeAddress(Address address)
	
		this.Address = address;

		// 同样的,在修改地址操作时,也该定义一个类似的修改地址领域事件
		//this.AddDomainEvent(new OrderAddressChangedDomainEvent(this));
	

这里面实现了Entity,同时实现了IAggregateRoot,这个接口里面没有任何东西,表示这是定义接口,表示将这个Order 定义为一个聚合根。

看一下Address:

/// <summary>
/// 地址实体
/// 定义为值对象
/// </summary>
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)
	
		this.Street = street;
		this.City = city;
		this.ZipCode = zipCode;
	

	/// <summary>
	/// 重载获取原子值的方法
	/// 这里特殊的是,我们使用了 yield return 的方式
	/// </summary>
	/// <returns></returns>
	protected override IEnumerable<object> GetAtomicValues()
	
		yield return Street;
		yield return City;
		yield return ZipCode;
	

将这个Address 定义为值对象。

梳理

这里如果不了解领域设计,会有点蒙。

那么这里只需要知道这里Order 定义为了aggregateRoot,也就是聚合根。

把Address 定义为了值对象即可。随着后面系列的他们之间的调用会越来越清晰的。

总结

  1. 将领域模型字段的修改设置为私有的。

  2. 使用构造函数表示对象的创建

  3. 使用具有业务的动作来操作模型字段

  4. 领域模型复制堆自己数据的处理

  5. 领域模型负责对自己数据的处理

  6. 领域服务或命令处理者扶着调用领域模型业务动作

下一节 领域模型之工作单元模式

重新整理.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实践篇—————应用分层[二十四](代码片段)

前言简单整理一下分层。正文应用程序分层,分为:1.领域模型层2.基础设施层3.应用层4.共享层共享层共享层一般包括下面几个类库。有一个Core的类库,比如说BLog.Core.这个类库用来,主要用来承载一些基础简单的类型,比如说一... 查看详情

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

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

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

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

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

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

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

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

.netcore杂记

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

.netcore实践篇————网关

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

asp.netcore分布式项目实战整理identityserver4mvc授权consent功能实现(代码片段)

...前言由于之前的博客都是基于其他的博客进行开发,现在重新整理一下方便以后后期使用与学习新建IdentityServer4服务端服务端也就是提供服务,如Q 查看详情

.netcore跨平台实践

本人采用Ubuntu14.04来实现.netcore的跨平台实践。首先安装Ubuntu14.04系统。安装细节问百度。1..netcoreconsole程序的跨平台首先新建一个console程序在一个空目录下用dotnet命令行创建 修改project.json文件在命令上中dotnetrestore(还原包)在... 查看详情

.netcore多租户框架整理

一saaskitAsp.NetCoremulti-tenantapplicationSampleusing#SaaSKithttps://github.com/saaskit/saaskithttp://benfoster.io/blog/tagged/saaskitsaaskitsampleshttps://github.com/janaks09/NetCoreSaaS/tree/0e1bdab 查看详情

netcore国际化最佳实践(代码片段)

NetCore国际化最佳实践ASP.NETCore中提供了一些本地化服务和中间件,可将网站本地化为不同的语言文化。ASP.NETCore中我们可以使用Microsoft.AspNetCore.Localization库来实现本地化。但是默认只支持使用资源文件方式做多语言存储,... 查看详情

asp.netcore中json序列化处理整理

一、Asp.NetCore中的Json序列化处理使用的是Newtonsoft.Json,更多参考:C#Newtonsoft.JsonJsonSerializerSettings配置序列化操作,C#Json序列化工具--Newtonsoft.Json简介和使用1.Newtonsoft.Json仅依赖.NetStandard所以支持.NetFramework也支持.NetCore2.更多说明/**1. 查看详情

.netcore依赖注入实践总结(代码片段)

知识点回顾依赖包。 Microsoft.Extensions.DependencyInjection.Abstractions核心对象和方法。IServiceCollection。注入对象的容器。注意它只存储对象的元数据,并不保存实例对象。IServiceProvider。注入对象的提供者。它负责提供具体的对象实... 查看详情

asp.netcore依赖注入最佳实践——提示与技巧(代码片段)

在这篇文章,我将分享一些在ASP.NETCore程序中使用依赖注入的个人经验和建议。这些原则背后的动机如下:高效地设计服务和它们的依赖。预防多线程问题。预防内存泄漏。预防潜在的BUG。这篇文章假设你已经基本熟悉依赖注入... 查看详情