当业务逐渐变得复杂,涉及到的实体对象不再是一个时,通用的增删改查分页功能已经无法满足要求,就需要更高级的功能。ABP框架提供了领域服务、工作单元和其他相关功能来实现对复杂业务的处理。
1.领域服务的使用范围
ABP框架设计的主要思想之一就是领域驱动设计模式,虽然ABP使用有一段时间了,但我觉得自己学习到的也只是皮毛而已,真正核心的东西还没有触及到。以我个人粗陋的理解,当你的业务处理过程涉及到多个领域对象而且业务处理过程不可再分时,你就需要领域服务了。领域服务一般是在领域层的,可以被领域层或应用层的方法所调用,但不能直接被展现层所调用。此外领域服务的返回内容应该是领域对象(或者是Entity实体),不应该是数据传输对象(Dto)。
2.创建并实现领域服务
在之前货品管理的基础上,加上简单的库存管理功能。货品的出库入库在变更货品数量的同时,也要添加货品的出库入库记录。
首先要创建货品出入库记录的实体类。由于只是记录,暂不考虑修改和删除,继承的基类也改成了CreationAuditedEntity。
/// <summary>
/// 货品出入库记录实体类
/// </summary>
public class GoodsRecord: CreationAuditedEntity<string>
{
/// <summary>
/// 货品名称
/// </summary>
public string GoodsName { get; set; }
/// <summary>
/// 货品类型
/// </summary>
public string GoodsType { get; set; }
/// <summary>
/// 存放位置
/// </summary>
public string Location { get; set; }
/// <summary>
/// 操作类型
/// </summary>
public GoodsOperateType OperateType { get; set; }
/// <summary>
/// 货品数量
/// </summary>
public int GoodsNum { get; set; }
}
/// <summary>
/// 库存操作类型
/// </summary>
public enum GoodsOperateType
{
/// <summary>
/// 入库
/// </summary>
In,
/// <summary>
/// 出库
/// </summary>
Out,
/// <summary>
/// 盘点
/// </summary>
Check
}
其次,创建货品出入库记录的领域服务。由于出入库时需要创建出入库记录,所以出入库记录的方法是需要被调用的,理所应当的要为出入库记录创建领域服务。在ABP框架中有一个约定,所有的领域服务都应该继承并实现IDomainService接口。在领域层AbpDemo.Core创建货品出入库的领域服务类,继承并实现IDomainService接口。在ABP框架中,领域服务类的命名习惯一般时xxxManager。
/// <summary>
/// 出入库记录-领域服务接口
/// </summary>
public interface IGoodsRecordManager:IDomainService
{
/// <summary>
/// 入库
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<string> InRecord(GoodsRecord input);
/// <summary>
/// 出库
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<string> OutRecord(GoodsRecord input);
/// <summary>
/// 盘点
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<string> CheckRecord(GoodsRecord input);
}
/// <summary>
/// 出入库记录-领域服务
/// </summary>
public class GoodsRecordManager : DomainService, IGoodsRecordManager
{
private readonly IRepository<GoodsRecord, string> _recordRepository;//仓储
public GoodsRecordManager(IRepository<GoodsRecord, string> recordRepository)
{
_recordRepository = recordRepository;
}
/// <summary>
/// 盘点
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<string> CheckRecord(GoodsRecord input)
{
return await _recordRepository.InsertAndGetIdAsync(input);
}
/// <summary>
/// 入库
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<string> InRecord(GoodsRecord input)
{
return await _recordRepository.InsertAndGetIdAsync(input);
}
/// <summary>
/// 出库
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<string> OutRecord(GoodsRecord input)
{
return await _recordRepository.InsertAndGetIdAsync(input);
}
}
3.使用领域服务
首先在应用层AbpDemo.Application的货品管理应用服务类GoodsAppService中引入出入库记录的领域服务IGoodsRecordManager。
private readonly IGoodsRecordManager _goodsRecordManager;
public GoodsAppService(IRepository<Goods,string> repository,IGoodsRecordManager goodsRecordManager):base(repository)
{
_goodsRecordManager = goodsRecordManager;
}
在ABP框架中,相邻层之间方法调用大都是通过定义接口和依赖注入的方式进行的,在其他的场景中也会经常见到。
然后在具体的方法中就可以调用领域服务了。按照文章开始处的需求,在应用服务中添加入库、出库的方法。
/// <summary>
/// 货品管理-应用服务接口
/// </summary>
public interface IGoodsAppService: IAbpDemoAppServiceBase<Goods,DetailGoodsDto,string,CreateGoodsDto,UpdateGoodsDto, PagedGoodsDto>,IApplicationService
{
/// <summary>
/// 入库
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<DetailGoodsDto> In(InOutGoodsDto input);
/// <summary>
/// 出库
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<DetailGoodsDto> Out(InOutGoodsDto input);
}
/// <summary>
/// 货品管理-应用服务
/// </summary>
public class GoodsAppService: AbpDemoAppServiceBase<Goods,DetailGoodsDto,string,CreateGoodsDto,UpdateGoodsDto,PagedGoodsDto>,IGoodsAppService
{
private readonly IGoodsRecordManager _goodsRecordManager;
public GoodsAppService(IRepository<Goods,string> repository,IGoodsRecordManager goodsRecordManager):base(repository)
{
_goodsRecordManager = goodsRecordManager;
}
/// <summary>
/// 入库
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<DetailGoodsDto> In(InOutGoodsDto input)
{
Goods entity = Repository.FirstOrDefault(input.Id);
if (entity != null)
{
entity.GoodsNum = entity.GoodsNum + input.GoodsNum;
}
GoodsRecord record = input.MapTo<GoodsRecord>();
record.OperateType = GoodsOperateType.In;
string recordId = await _goodsRecordManager.InRecord(record);
entity =await Repository.UpdateAsync(entity);
DetailGoodsDto result = entity.MapTo<DetailGoodsDto>();
return await Task.FromResult(result);
}
/// <summary>
/// 出库
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<DetailGoodsDto> Out(InOutGoodsDto input)
{
Goods entity = Repository.FirstOrDefault(input.Id);
if (entity != null)
{
entity.GoodsNum = entity.GoodsNum - input.GoodsNum;
}
GoodsRecord record = input.MapTo<GoodsRecord>();
record.OperateType = GoodsOperateType.Out;
string recordId = await _goodsRecordManager.OutRecord(record);
entity = await Repository.UpdateAsync(entity);
DetailGoodsDto result = entity.MapTo<DetailGoodsDto>();
return await Task.FromResult(result);
}
}
启动项目,在swagger帮助界面可以查看到新增的接口并进行测试了。
4.工作单元的使用
在一个成熟的、体系化的开发框架中,一个功能或特性并不是单独存在的,在ABP框架中,领域服务常常会与工作单元、领域事件等伴随出现。工作单元的使用场景简单来讲就是将多次的数据库连接和事务放到一起。ABP框架中使用仓储模式进行数据库连接和操作,仓储的每次使用都包含了数据库连接打开、数据库操作、连接关闭的过程。而工作单元的作用就是将多次仓储操作放在一起时,在最开始打开数据库连接,所有操作完成后才关闭数据库连接,从性能和效率上有所提高。
工作单元一般时使用在领域层的,比较常见的是配合领域服务使用。这里尝试将上一步骤中货品管理的功能也移动到领域层,将货品管理和出入库记录的操作合并到一起。
public class GoodsManager : DomainService, IGoodsManager
{
private readonly IRepository<Goods, string> _goodsRepository;//货品仓促
private readonly IRepository<GoodsRecord, string> _recordRepository;//出入库记录仓储
public GoodsManager(IRepository<Goods, string> goodsRepository, IRepository<GoodsRecord, string> recordRepository)
{
_goodsRepository = goodsRepository;
_recordRepository = recordRepository;
}
[UnitOfWork]
public async Task<Goods> CheckGoods(GoodsRecord input)
{
Goods entity = _goodsRepository.FirstOrDefault(input.Id);
if (entity!=null)
{
entity.GoodsNum = entity.GoodsNum + input.GoodsNum;
}
input.Id = Guid.NewGuid().ToString();
input.OperateType = GoodsOperateType.Check;
GoodsRecord record = await _recordRepository.InsertAsync(input);
return await _goodsRepository.UpdateAsync(entity);
}
}
使用UnitOfWork特性对需要使用工作单元的方法进行标记,那么方法内部涉及到仓储的操作将在进入方法时打开数据库连接,离开方法时再关闭数据库连接。
对于工作单元我们只需要牢记它是原子性操作,也就是不可再分了,内部细节由ABP框架自动完成。另外,如果工作单元方法调用其它工作单元的方法,它们使用相同的连接和事务。第一个进入的方法管理连接和事务,其它的方法仅仅是使用它。