teleware.foundation.core - wengys/Teleware.Foundation GitHub Wiki

Teleware.Foundation.Core

主要技术相关组件接口定义以及常用辅助类

运行环境相关

运行环境涉及Hosting命名空间下相关类/接口

Hosting

本命名空间主要定义运行环境相关接口

运行环境指的是当前程序运行时所在的(开发、准备发布、发布等)环境。不同环境下程序有不同的配置(甚至不同的行为)。此概念请参考Asp.net core Environments说明

主要类/接口:

  1. IEnvironment

    提供当前执行环境相关信息

  2. EnvironmentName

    常用环境名

  3. EnvironmentExtensions

    IEnvironment相关扩展方法

IEnvironment有以下两个实现:

  1. Teleware.Foundation.Hosting.AspNetCore

    基于Asp.net core的环境(对Asp.net core下IHostingEnvironment的简单包装)

  2. 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命名空间下相关类/接口

Data

本命名空间主要定义仓储模式工作单元模式相关接口

主要类/接口:

  1. IUnitOfWork

    表示一个工作单元

  2. IRepository

    表示一个仓储

  3. IRepositoryQuery

    表示一个用于只读直接查询仓储中子对象的仓储访问器

  4. ICRUDRepository

    表示最常用的,具有CRUD功能的仓储

  5. IUnitOfWorkCoordinator

    表示一个工作单元协调器,用于协调多个工作单元共同工作

  6. UnitOFWorkCoordinator

    IUnitOfWorkCoordinator的默认实现

    需要注意的是,目前没有计划实现2PC,而是简单按顺序提交各个工作单元

Domain

本命名空间主要定义领域对象相关类/接口

主要类/接口:

  • 领域模型相关

    1. IEntity 表示一个实体
    2. IValueType 表示一个值类型
    3. IAggregateRoot 表示一个聚合根
    4. AbstractRootEntity 聚合根基类
    5. AbstractEntity 实体基类
  • 领域事件相关

    1. IDomainEvent 描述一个领域事件
    2. IDomainEventHandler 描述一个领域事件处理器
    3. DomainEventPostBox 领域事件邮箱
    4. DomainEventMessenger 领域事件递送者
    5. EntityCreatedEvent、EntityRemovedEvent、EntityUpdatedEvent 默认的实体新增删除修改事件
  • 其他

    1. IIdGenerator 表示一个Id生成器,为实体Id的生成提供了公共接口

领域事件

领域事件的介绍,参见此处

业务中,领域事件可用于以下场景(举例,不限于此场景):

  1. 对具体实体操作的审计
  2. 在保存项目对象时,向平台推送项目名、项目编号等信息

默认的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命名空间下一系列的扩展方法

Exceptions

本命名空间主要定义异常相关类型/辅助方法

主要类/接口:

  1. BusinessException 业务异常基类,表示一个业务上的异常
  2. ClientNoticeableException 客户端业务异常基类,约定此类异常的Message信息客户可见
  3. HttpClientNoticeableException Http客户端业务异常基类,约定异常的Message信息客户可见,且Response的状态码将被设置为此异常的StatusCode

Assertion

本命名空间主要包含断言相关辅助类

针对外部传入的值(尤其用户输入),断言越早越好,否则错误扩散后将难以排查

过去,我们这么检查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*格式,其他相关辅助方法,参见代码/智能提示

其他

杂七杂八的一堆东西

Caching

本命名空间定义缓存相关接口

主要接口:

  1. ICacheProvider

    本地缓存接口

未来可能增加远程缓存支持

Collections

本命名空间定义集合相关扩展方法以及辅助类

主要类/接口:

  1. GeneralEqualityComparer

    IEqualityComparer的默认实现

  2. 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个一组

Diagnostics

本命名空间定义调试相关接口

主要类/接口:

  1. ILogger<TClass>

  2. ILoggerFactory

    主要用于无法通过类型得到loggerName的环境

  3. ElapseTimeEvaluator

    Stopwatch的封装

例子:

打印执行时间

using (ElapseTimeEvaluator.EvaluateUsedTime((time)=>_logger.Debug(0,$"耗时: {time}")))
{
    // 需要性能调优等的代码
}

Options

本命名空间定义常用配置实体类

主要类:

  1. DatabaseOptions

    数据库连接配置

约定:所有的配置实体均放置在*.Options命名空间下,类名以Options结尾

Serialization

本命名空间定义对象序列化相关接口

主要接口:

  1. 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);

Threading.Tasks

本命名空间定义Task相关扩展方法/辅助类

主要类/接口:

  1. AsyncTaskWrapper

    将基于回调的异步调用包装为基于Task的异步任务

  2. TaskExtensions

    Task操作相关扩展

例子:

将一系列异步

Task<int>[] tasks = GetTaskAsyncs();
Task<int[]> oneBigAllResultsTask = tasks.ToTask();
int[] values = await oneBigAllResultsTask; // 不用一个个任务await过去了

Util

本命名空间中存放杂七杂八中的杂七杂八的玩意

主要类/接口:

  1. MathExtensions

    数学相关扩展,主要有:

    • ToFixed 四舍五入小数到特定位数
  2. Objects

    对象相关帮助类,主要有:

    • IsDefault 检查对象是否为默认值(类是否为null,结构体是否为空)
  3. Paths

    路径相关帮助类,主要有:

    • GetAbsoluteFilePath 根据相对路径获取绝对路径
  4. Strings

    字符串相关帮助类,主要有:

    • TruncateLeft 从左向右截断字符串
    • TruncateRight 从右向左截断字符串
  5. Units

    单位相关帮助类,主要有:

    • ToHectare 平方米转公顷
    • ToMu 平方米转亩
    • MuToSquareMeter 亩转平方米转
  6. 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中
  7. AbstractObjectConverterConfigFatory

    类型转换配置工厂,方便实现类型转换配置

⚠️ **GitHub.com Fallback** ⚠️