teleware.foundation.core - wengys/Teleware.Foundation GitHub Wiki
主要技术相关组件接口定义以及常用辅助类
运行环境涉及Hosting命名空间下相关类/接口
本命名空间主要定义运行环境相关接口
运行环境指的是当前程序运行时所在的(开发、准备发布、发布等)环境。不同环境下程序有不同的配置(甚至不同的行为)。此概念请参考Asp.net core Environments说明
主要类/接口:
-
IEnvironment
提供当前执行环境相关信息
-
EnvironmentName
常用环境名
-
EnvironmentExtensions
IEnvironment相关扩展方法
IEnvironment有以下两个实现:
-
Teleware.Foundation.Hosting.AspNetCore
基于Asp.net core的环境(对Asp.net core下IHostingEnvironment的简单包装)
-
Teleware.Foundation.Hosting.Application
Console、Asp.net用实现(模仿Asp.net core下IHostingEnvironment实现,也通过环境变量
ASPNETCORE_ENVIRONMENT
配置)
下面以一个虚拟的FooService为例子,演示环境的使用方式
获取当前程序目录(控制台下为exe所在目录,Asp.net下为bin目录):
class FooService
{
private readonly IEnvironment _environment;
public FooService(IEnvironment environment)
{
// 构造函数注入IEnvironment实例
_environment = environment;
}
public Recipe LoadRecipe()
{
// 假设当前目录下有一份神奇菜谱recipe.txt, _environment.ContentRootPath 返回当前目录
var recipePath = Path.Combine(_environment.ContentRootPath, "recipe.txt");
var recipeContent = File.ReadAllText(recipePath);
return Recipe.FromContent(recipeContent);
}
}
通过当前程序环境切换功能(IsDevelopment为IEnvironment上的扩展方法)
class FooService
{
private readonly IEnvironment _environment;
private readonly ILogger<FooService> _logger;
public FooService(IEnvironment environment, ILogger<FooService> logger)
{
// 构造函数注入IEnvironment实例
_environment = environment;
_logger = logger;
}
/// <summary>
/// 记录用户隐私(调试用)
/// </summary>
/// <param name="ue"></param>
public void LogUserPrivacyInfo(UserEvent ue)
{
if (_environment.IsDevelopment()) // 没有人希望Production环境下还把用户的隐私记录下来
{
_logger.Debug(0, ue.ToString());
}
}
}
数据访问主要涉及Data和Domain命名空间下相关类/接口
主要类/接口:
-
IUnitOfWork
表示一个工作单元
-
IRepository
表示一个仓储
-
IRepositoryQuery
表示一个用于只读直接查询仓储中子对象的仓储访问器
-
ICRUDRepository
表示最常用的,具有CRUD功能的仓储
-
IUnitOfWorkCoordinator
表示一个工作单元协调器,用于协调多个工作单元共同工作
-
UnitOFWorkCoordinator
IUnitOfWorkCoordinator的默认实现
需要注意的是,目前没有计划实现2PC,而是简单按顺序提交各个工作单元
本命名空间主要定义领域对象相关类/接口
主要类/接口:
-
领域模型相关
- IEntity 表示一个实体
- IValueType 表示一个值类型
- IAggregateRoot 表示一个聚合根
- AbstractRootEntity 聚合根基类
- AbstractEntity 实体基类
-
领域事件相关
- IDomainEvent 描述一个领域事件
- IDomainEventHandler 描述一个领域事件处理器
- DomainEventPostBox 领域事件邮箱
- DomainEventMessenger 领域事件递送者
- EntityCreatedEvent、EntityRemovedEvent、EntityUpdatedEvent 默认的实体新增删除修改事件
-
其他
- IIdGenerator 表示一个Id生成器,为实体Id的生成提供了公共接口
领域事件的介绍,参见此处
业务中,领域事件可用于以下场景(举例,不限于此场景):
- 对具体实体操作的审计
- 在保存项目对象时,向平台推送项目名、项目编号等信息
默认的ICRUDRepository支持对聚合根基本CUD事件的投递,聚合根内的事件 需要由用户手工投递 。如下面这个DEMO,在删除Sub时,就投递了两个领域事件:
class ARootEntity : AbstractRootEntity
{
public ARootEntity()
{
Subs = new List<ASubEntity>();
}
public ICollection<ASubEntity> Subs { get; set; }
public void RemoveSub(ASubEntity sub)
{
this.Subs.Remove(sub);
this.GetEventPostBox().Post(new ASubRemovedEvent(this, sub)); // 投递自定义的删除事件
this.GetEventPostBox().Post(new EntityRemovedEvent(sub)); // 投递默认删除事件
}
}
class ASubEntity : AbstractEntity
{
}
class ASubRemovedEvent : AbstractDomainEvent
{
public ASubEntity RemovedSub { get; }
public ASubRemovedEvent(ARootEntity root,ASubEntity sub):base(root)
{
RemovedSub = sub;
}
}
若要捕获领域事件,则需要先定义领域事件处理器,并注册。参考以下代码:
class ASubRemovedEventHandler : IDomainEventHandler
{
public Type[] EventTypes { get; } = { typeof(ASubRemovedEvent) }; // 表示只处理ASubRemovedEvent时间
public Task HandleAsync(IDomainEvent @event)
{
//Handling...
}
}
// 以下代码在autofac中注册此处理器
containerBuilder.RegisterType<ASubRemovedEventHandler>()
.As<IDomainEventHandler>()
.InstancePerLifetimeScope();
完成注册后,工作单元将保证在提交前所有的领域事件都由相应的处理器处理
针对EF6后端的Repository实现,删除聚合根内的领域对象时(如上例中RemoveSub
方法),将执行delete操作(而不是默认的将外键update为null)
异常主要涉及Exceptions命名空间下相关类型/辅助方法
检查相关主要涉及Assertion命名空间下一系列的扩展方法
本命名空间主要定义异常相关类型/辅助方法
主要类/接口:
- BusinessException 业务异常基类,表示一个业务上的异常
- ClientNoticeableException 客户端业务异常基类,约定此类异常的
Message
信息客户可见 - HttpClientNoticeableException Http客户端业务异常基类,约定异常的
Message
信息客户可见,且Response的状态码将被设置为此异常的StatusCode
,
本命名空间主要包含断言相关辅助类
针对外部传入的值(尤其用户输入),断言越早越好,否则错误扩散后将难以排查
过去,我们这么检查NullReferenceException
Foo a = ReadFooInput();
// ...
if( a == null ) {
throw new AssertingException("Foo is null");
}
var bar = a.Bar;
// ...
现在,我们可以这么写
using Teleware.Foundation.Assertion
var bar = ReadFooInput()
.ShouldNotDefault("Foo is null")
.Bar;
所有扩展方法均为Should*
格式,其他相关辅助方法,参见代码/智能提示
杂七杂八的一堆东西
本命名空间定义缓存相关接口
主要接口:
-
ICacheProvider
本地缓存接口
未来可能增加远程缓存支持
本命名空间定义集合相关扩展方法以及辅助类
主要类/接口:
-
GeneralEqualityComparer
IEqualityComparer的默认实现
-
EnumerableExtensions
IEnumerable扩展
例子:
集合根据特定属性去重:
List<Foo> foos = GetFoos();
var uniqueFoos = foos.Distinct(new GeneralEqualityComparer<Foo, string>(f => f.Name)); // 根据Name属性去重
集合分组:
List<Foo> foos = GetFoos();
IEnumerable<IEnumerable<Foo>> fooPartitions = foos.PartitionBy(10); // 10个一组
本命名空间定义调试相关接口
主要类/接口:
-
ILogger<TClass>
-
ILoggerFactory
主要用于无法通过类型得到loggerName的环境
-
ElapseTimeEvaluator
Stopwatch的封装
例子:
打印执行时间
using (ElapseTimeEvaluator.EvaluateUsedTime((time)=>_logger.Debug(0,$"耗时: {time}")))
{
// 需要性能调优等的代码
}
本命名空间定义常用配置实体类
主要类:
-
DatabaseOptions
数据库连接配置
约定:所有的配置实体均放置在*.Options命名空间下,类名以Options结尾
本命名空间定义对象序列化相关接口
主要接口:
-
IObjectSerializer
定义一个对象序列化器
例子:
序列化/反序列化对象
IObjectSerializer serializer = ... // 默认由autofac解析具体实现
Foo foo = GetFoo();
var serializer =lt.Resolve<IObjectSerializer>(); // 序列化为byte数据
var bytes = serializer.SerializeObject(foo);
// var stream = serializer.SerializeObjectToStream(foo); // 也可以直接序列化为流
var deserialized = serializer.DeserializeObject<Foo>(bytes);
本命名空间定义Task相关扩展方法/辅助类
主要类/接口:
-
AsyncTaskWrapper
将基于回调的异步调用包装为基于Task的异步任务
-
TaskExtensions
Task操作相关扩展
例子:
将一系列异步
Task<int>[] tasks = GetTaskAsyncs();
Task<int[]> oneBigAllResultsTask = tasks.ToTask();
int[] values = await oneBigAllResultsTask; // 不用一个个任务await过去了
本命名空间中存放杂七杂八中的杂七杂八的玩意
主要类/接口:
-
MathExtensions
数学相关扩展,主要有:
-
ToFixed
四舍五入小数到特定位数
-
-
Objects
对象相关帮助类,主要有:
-
IsDefault
检查对象是否为默认值(类是否为null,结构体是否为空)
-
-
Paths
路径相关帮助类,主要有:
-
GetAbsoluteFilePath
根据相对路径获取绝对路径
-
-
Strings
字符串相关帮助类,主要有:
-
TruncateLeft
从左向右截断字符串 -
TruncateRight
从右向左截断字符串
-
-
Units
单位相关帮助类,主要有:
-
ToHectare
平方米转公顷 -
ToMu
平方米转亩 -
MuToSquareMeter
亩转平方米转
-
-
ObjectConverter
类型转换器,统一类型转换入口,解决AutoMapper之类的类型转换配置散落在项目各个角落问题
例子:
转换对象Foo为Bar
Foo singleFoo = GetFoo(); IEnumerable<Foo> foos = GetFoos(); IQueryable<Foo> fooQuery = QueryFoo(); var bar = converter.Convert(singleFoo).To<Bar>(); // 单个对象 var bars = converter.Convert(foos).To<Bar>(); // IEnumerable var barQuery = converter.Convert(fooQuery).To<Bar>(); // IQueryable
转换对象Foo为Bar
Foo singleFoo = GetFoo(); Bar existingBar = GetBar(); converter.Convert(singleFoo).To<Bar>(existingBar); // 将singleFoo的值填充到existingBar中
-
AbstractObjectConverterConfigFatory
类型转换配置工厂,方便实现类型转换配置