PocoEmit遥遥领先于AutoMapper之打通充血模型的任督二脉

一、充血模型和失血模型

1. 充血模型的优势

  • 充血模型更加OOP
  • 充血模型代码可读性更好

1.1 充血模型伪代码

csharp 复制代码
var messageDto = controller.ReadDto();
var message = messageDto.ToEntity();
message.Save();

1.2 失血模型伪代码

csharp 复制代码
var messageDto = controller.ReadDto();
var message = messageDto.ToEntity();
var messageService = controller.GetMessageService();
messageService.Save(message);

2. 充血模型爱你不容易

  • 大部分程序员都知道充血模型好,想实现却很难
  • 大部分业务逻辑都需要依赖外部服务
  • 充血模型需要用到外部服务,又不想依赖外部服务的具体实现
  • 很容易想到使用IOC的依赖注入来解决
  • 我们给DTO注入服务还行,因为IOC参与了controller过程
  • 当DTO发生转化时,新增的服务IOC还是有点力不从心
  • 这个任督二脉PocoEmit可以帮你打通

二、首先来个Case演示一下

  • Dto转化为实体
  • 但是实体有更多逻辑依赖外部服务,这些外部服务Dto不见得提供的了
  • 这就需要注入
  • PocoEmit支持构造函数参数注入和属性注入
  • IMapper对象是默认支持注入的服务

1. Entity比Dto多出来的Mapper可以注入

csharp 复制代码
class MessageDto
{
    public string Message { get; set; }
}
class MessageEntity
{
    public IMapper Mapper { get; set; }
    public string Message { get; set; }
}

2. 转化并注入的代码

csharp 复制代码
var mapper = Mapper.Create();
var dto = new MessageDto { Message = "Hello UseMapper" };
MessageEntity message = mapper.Convert<MessageDto, MessageEntity>(dto);
Assert.NotNull(message.Mapper);

三、再演示注入自定义的服务

1. UserDomain比Dto多出来的Repository可以注入

csharp 复制代码
public record UserDTO
{
    public int Id { get; set; }
    public string Name { get; set; }
}
class UserDomain(UserRepository repository, int id, string name)
{
    private readonly UserRepository _repository = repository;
    public UserRepository Repository
        => _repository;
    public int Id { get; } = id;
    public string Name { get; } = name;
    // ...
}
class UserRepository
{
    void Add(UserDomain user) { }
    void Update(UserDomain entity) { }
    void Remove(UserDomain entity) { }
    public static readonly UserRepository Instance = new();
}

2. 注册、转化并注入的代码

  • 通过UseDefault可以注入服务
csharp 复制代码
IMapper mapper = Mapper.Create()
    .UseDefault(UserRepository.Instance);
var dto = new UserDTO { Id = 1, Name = "Jxj" };
UserDomain user = mapper.Convert<UserDTO, UserDomain>(dto);
Assert.NotNull(user.Repository);

四、注入IOC容器的Case

  • 注入IOC容器需要安装nuget包PocoEmit.ServiceProvider

1. 包含IOC容器的实体

csharp 复制代码
class UserWithServiceProvider
{
    public int Id { get; set; }
    public string Name { get; set; }
    public IServiceProvider ServiceProvider { get; set; }
}

2. 注册、转化并注入的代码

  • UseSingleton是把容器作为唯一容器注入
  • UseScope是使用当前Scope的子容器
  • UseContext是在Mvc下,使用当前HttpContext的RequestServices子容器
csharp 复制代码
var services = new ServiceCollection();
var serviceProvider = services.BuildServiceProvider();
var mapper = Mapper.Create();
mapper.UseSingleton(serviceProvider);
var dto = new UserDTO { Id = 1, Name = "Jxj" };
UserWithServiceProvider user = mapper.Convert<UserDTO, UserWithServiceProvider>(dto);
Assert.NotNull(user.ServiceProvider);

五、当然还可以注入容器内的服务

1. UserDomain多出来的UserDomain需要注入

  • 这次我们用IOC来管理UserRepository
  • 这样才能更好的利用依赖注入
  • UserRepository可能还会依赖其他的
  • 手动维护对象可能会很麻烦,IOC容器擅长维护这些复杂关系
csharp 复制代码
    class UserDomain(UserRepository repository, int id, string name)
    {
        private readonly UserRepository _repository = repository;
        public UserRepository Repository
            => _repository;
        public int Id { get; } = id;
        public string Name { get; } = name;
        // ...
    }
    class UserRepository
    {
        void Add(UserDomain user) { }
        void Update(UserDomain entity) { }
        void Remove(UserDomain entity) { }
    }

2. 注册、转化并注入的代码

  • 通过UseScope注入IOC容器
  • 通过UseDefault告知这个类型从IOC容器中注入
csharp 复制代码
var services = new ServiceCollection()
    .AddScoped<UserRepository>();
var serviceProvider = services.BuildServiceProvider();
var mapper = Mapper.Create();
mapper.UseScope(serviceProvider)
     .UseDefault<UserRepository>();
var dto = new UserDTO { Id = 1, Name = "Jxj" };
UserDomain user = mapper.Convert<UserDTO, UserDomain>(dto);
Assert.NotNull(user.Repository);

六、支持IOC容器的特性

  • 支持FromKeyedServices
  • 支持FromServices

1. FromKeyedServices标记注入点和服务键

csharp 复制代码
class UserDomain1([FromKeyedServices("User1")]UserRepository repository, int id, string name)
    : UserDomain(repository, id, name)
{
}
class UserDomain2([FromKeyedServices("User2")] UserRepository repository, int id, string name)
    : UserDomain(repository, id, name)
{
}
class UserDomain(UserRepository repository, int id, string name)
{
    private readonly UserRepository _repository = repository;
    public UserRepository Repository
        => _repository;
    public int Id { get; } = id;
    public string Name { get; } = name;
    // ...
}
class UserRepository(string tableName)
{
    private readonly string _tableName = tableName;
    public string TableName 
        => _tableName;
    void Add(UserDomain user) { }
    void Update(UserDomain entity) { }
    void Remove(UserDomain entity) { }
}

2. 注册、转化并注入的代码

  • 由于识别出FromKeyedServices,就不需要UseDefault
  • 这样简洁由优雅
csharp 复制代码
string table1 = "User1";
string table2 = "User2";
var services = new ServiceCollection()
    .AddKeyedScoped(table1, (_, _) => new UserRepository(table1))
    .AddKeyedScoped(table2, (_, _) => new UserRepository(table2));
var serviceProvider = services.BuildServiceProvider();
var mapper = Mapper.Create();
mapper.UseScope(serviceProvider);
var dto = new UserDTO { Id = 1, Name = "Jxj" };
UserDomain user = mapper.Convert<UserDTO, UserDomain1>(dto);
Assert.NotNull(user.Repository);
UserDomain user2 = mapper.Convert<UserDTO, UserDomain2>(dto);
Assert.NotNull(user2.Repository);

七、竞品类似的功能

1. AutoMapper不支持

  • AutoMapper的NullSubstitute用来指定源属性为null时的默认值
  • 用AutoMapper实现类似功能需要复杂的自定义IValueResolver来实现
  • PocoEmit在源无法匹配或源字段为null都可能触发依赖注入

2. EF有类似功能

八、总结

1. OOM映射需要依赖注入

  • DTO、实体、领域模型如果有业务逻辑就需要依赖外部服务
  • 支持按类型注入,也支持按指定的参数或属性注入
  • 支持FromKeyedServices和FromServices
  • 需要外部服务就需要依赖注入

2. IOC容器使用需要注意

  • 简单作业单容器,使用UseSingleton即可
  • 多线程需要使用UseScope
  • Mvc(含WebApi)逻辑处理使用UseContext
  • UseContext需要引用nuget包PocoEmit.Mvc
  • 如果是Mvc异步处理或Quartz类似作业不要用UseContext
  • 就怕异步中获取到了HttpContext,但执行中途被释放了,后面就可能异常了

另外源码托管地址: https://github.com/donetsoftwork/MyEmit ,欢迎大家直接查看源码。

gitee同步更新:https://gitee.com/donetsoftwork/MyEmit

如果大家喜欢请动动您发财的小手手帮忙点一下Star,谢谢!!!

相关推荐
想不明白的过度思考者2 天前
Spring IoC 与 DI 深度剖析:从“控制反转”到 Bean 的集中管理
java·spring·ioc·di
彷徨的蜗牛3 天前
六边形架构使用场景 - 第二章 - DDD领域模型
架构·领域模型·ddd·六边形架构
L.EscaRC13 天前
Spring IOC核心原理与运用
java·spring·ioc
程序员三明治15 天前
【Spring进阶】Spring IOC实现原理是什么?容器创建和对象创建的时机是什么?
java·后端·spring·ioc·bean生命周期
記億揺晃着的那天17 天前
从单体到微服务:如何拆分
java·微服务·ddd·devops·系统拆分
液态不合群20 天前
DDD驱动低代码开发:从业务流程到领域模型的全链路设计
前端·低代码·架构·ddd
小七mod20 天前
【Spring】Spring Boot自动配置的案例
java·spring boot·spring·自动配置·源码·ioc·aop
Kay_Liang25 天前
Spring IOC核心原理与实战技巧
java·开发语言·spring boot·spring·ioc·依赖注入·控制反转
安冬的码畜日常1 个月前
【JUnit实战3_27】第十六章:用 JUnit 测试 Spring 应用:通过实战案例深入理解 IoC 原理
spring·观察者模式·设计模式·单元测试·ioc·依赖注入·junit5
canonical-entropy1 个月前
范式重构:可逆计算如何颠覆DDD的经典模式
低代码·重构·ddd·领域驱动设计·可逆计算·nop平台