# 业务服务层

# 概述

一个模块的服务层,主要负责如下几个方面的工作:

API层提供各个实体的数据查询的IQueryable<T>类型的数据源

接收API层传入的IInputDto参数,完成实体的新增、更新、删除等业务操作

接收API层传入的参数,处理各种模块级别的综合业务

处理完业务之后,将数据通过数据仓储IRepository更新到数据库

1API层返回业务操作结果

# 代码布局

一个业务模块,是负责完成一系列功能的,这些功能相互之间具有密切的关联性,所以对于一个模块来说,业务服务是一个整体,不应把他们再按单个实体拆分开来。 YuebonCore的业务模块代码结构设计,也是根据这一原则来设计的。设计规则如下:

服务接口ISequenceService:一个模块的业务服务共享一个服务接口,接口中包含模块的综合业务服务,也包含模块的各个实体的查询数据集、新增、更新、删除等自有业务服务。

服务实现SequenceService:服务实现使用,负责各实体的仓储服务注入,辅助服务注入,模块综合业务实现。

src                                     # 源代码文件夹
└─Yuebon.Security.Core                  # 项目核心工程
     └─IServices                        # 业务单据编码模服务接口文件夹
     │    ├─ISequenceService.cs         # 业务单据服务接口类
     │    └─ISequenceRuleService.cs     # 单据编码规则服务接口类
     └─Services                         # 业务单据编码模块实体类文件夹
          ├─SequenceService.cs          # 业务单据服务接口实现类
          └─SequenceRuleService.cs      # 单据编码规则服务接口实现类

IService、BaseService是服务接口和实现基类,所有业务模块的服务类都继承该基类。

# 服务接口

所有业务的Service接口必须继承IService基础接口。主要用于定义业务自己的功能业务接口,不要与IService接口名称一致即可。

根据 <业务模块设计#服务层> 的需求分析,我们需要给业务单据Sequence实体定义新增、更新、删除、获取最新单据编码等服务。

接口定义如下:

/// <summary>
/// 定义单据编码服务接口
/// </summary>
public interface ISequenceService:IService<Sequence,SequenceOutputDto, string>
{
    /// <summary>
    /// 获取最新业务单据编码
    /// </summary>
    /// <param name="sequenceName">业务单据编码名称</param>
    /// <returns></returns>
    CommonResult GetSequenceNext(string sequenceName);
}

# 接口的实现

1、主要实现仓储接口中定义方法实现,同时对BaseService中的方法实现不能满足业务需要时可以重写相关的方法。

2、此处需根据业务情况依赖注入服务生命周期接口,方法同仓储Repository实现一样,不再赘述。

3、依赖服务注入方式分析

服务层的业务实现,通过向服务实现类注入 数据仓储IRepository<TEntity, TKey> 对象来获得向数据库存取数据的能力。根据 .NetCore 的依赖注入使用原则,常规的做法是在服务实现类的构造函数进行依赖服务的注入。形如:

/// <summary>
/// 单据编码服务接口实现
/// </summary>
public class SequenceService : BaseService<Sequence, SequenceOutputDto, string>, ISequenceService,ITransientDependency
{
    private readonly ISequenceRepository _repository;
    private readonly ISequenceRuleRepository _repositoryRule;
    private readonly ILogService _logService;
    /// <summary>
    /// 
    /// </summary>
    /// <param name="repository"></param>
    /// <param name="repositoryRule"></param>
    /// <param name="logService"></param>
    public SequenceService(ISequenceRepository repository, ISequenceRuleRepository repositoryRule, ILogService logService) : base(repository)
    {
        _repository = repository;
        _logService = logService;
        _repositoryRule = repositoryRule;
    }
}

构造函数注入带来的性能影响

每个仓储都使用构造函数注入的话,如果模块的业务比较复杂,涉及的实体比较多(比如十几个实体是很经常的事),就会造成每次实例化SequenceService类的实例的时候,都需要去实例化很多个依赖服务,而实际上**一次业务执行只执行服务中的某个方法,可能也就用到其中的一两个依赖服务,**这就造成了很多不必要的额外工作,也就是性能损耗。

依赖服务注入的性能优化

如果不考虑业务服务的可测试性(单元测试通常需要Mock依赖服务)的话,在业务代码中使用 App.GetService<T>()的方式来按需获取依赖服务的实例,是比较经济的方式。则服务实现变为如下所示:

/// <summary>
/// 单据编码服务接口实现
/// </summary>
public class SequenceService : BaseService<Sequence, SequenceOutputDto, string>, ISequenceService,ITransientDependency
{
    private readonly ISequenceRepository _repository=App.GetService<ISequenceRepository>();
    private readonly ISequenceRuleRepository _repositoryRule=App.GetService<ISequenceRuleRepository>();
    private readonly ILogService _logService=App.GetService<ILogService>();
    /// <summary>
    /// 
    /// </summary>
    public SequenceService() : base(repository)
    {
    }
}

4、本例需要重写分页方法

/// <summary>
/// 单据编码服务接口实现
/// </summary>
public class SequenceService : BaseService<Sequence, SequenceOutputDto, string>, ISequenceService,ITransientDependency
{
    private readonly ISequenceRepository _repository;
    private readonly ISequenceRuleRepository _repositoryRule;
    private readonly ILogService _logService;
    /// <summary>
    /// 
    /// </summary>
    /// <param name="repository"></param>
    /// <param name="repositoryRule"></param>
    /// <param name="logService"></param>
    public SequenceService(ISequenceRepository repository, ISequenceRuleRepository repositoryRule, ILogService logService) : base(repository)
    {
        _repository = repository;
        _logService = logService;
        _repositoryRule = repositoryRule;
    }

    /// <summary>
    /// 获取最新业务单据编码
    /// </summary>
    /// <param name="sequenceName">业务单据编码名称</param>
    /// <returns></returns>
    public async Task<CommonResult> GetSequenceNextTask(string sequenceName)
    {

        CommonResult result = new CommonResult();
        //生成编号   
        string sequenceNewNo = "";
        #region 获取序号生成器属性
        if (string.IsNullOrWhiteSpace(sequenceName))
        {
            result.ErrMsg = "参数错误:业务编码编号";
            return result;
        }
        //获取序号生成器属性
        Sequence sequence = _repository.GetWhere("SequenceName='" + sequenceName + "'");
        if (sequence != null)
        {
            IEnumerable<SequenceRule> list = _repositoryRule.GetListWhere("SequenceName='" + sequenceName + "' order by RuleOrder asc");
            if (list.Count() > 0)
            {
                int delimiterNum = 0;
                foreach (SequenceRule item in list)
                {
                    delimiterNum++;

                    switch (item.RuleType)
                    {
                        case "const"://常量方式
                            sequenceNewNo += item.RuleValue;
                            break;
                        case "shortdate"://短日期 年2位月2位日期2位
                            sequenceNewNo += DateTime.Now.ToString("yyyyMMdd").Substring(2);
                            break;
                        case "date"://日期,年4位
                            sequenceNewNo += DateTime.Now.ToString("yyyyMMdd");
                            break;
                        case "ydate"://年月,年4位月2位
                            sequenceNewNo += DateTime.Now.ToString("yyyyMMdd").Substring(0,6);
                            break;
                        case "timestamp"://日期时间精确到毫秒
                            sequenceNewNo += DateTime.Now.ToString("yyyyMMddHHmmssffff");
                            break;
                        case "number"://计数,流水号
                            int num = CurrentReset(sequence, item);
                            //计数拼接
                            sequenceNewNo += NumberingSeqRule(item, num).ToString();
                            //更新当前序号
                            sequence.CurrentNo =num;
                            break;
                        case "guid"://Guid
                            sequenceNewNo += GuidUtils.NewGuidFormatN();
                            break;
                        case "random"://随机数
                            Random random = new Random();
                            string strMax = "9".ToString().PadLeft(item.RuleValue.Length - 1, '9');
                            string strRandom = random.Next(item.RuleValue.ToInt(), strMax.ToInt()).ToString(); //生成随机编号 
                            sequenceNewNo += strRandom;
                            break;
                    }
                    if (!string.IsNullOrEmpty(sequence.SequenceDelimiter)&& delimiterNum!= list.Count())
                    {
                        sequenceNewNo += sequence.SequenceDelimiter;
                    }

                }
                //当前编号
                sequence.CurrentCode = sequenceNewNo;
                sequence.CurrentReset = DateTime.Now.ToString("yyyyMMdd");
                await _repository.UpdateAsync(sequence, sequence.Id);
                result.ResData = sequenceNewNo;
                result.Success = true;
            }
            else
            {
                result.Success = false;
                result.ErrMsg = "未查询到业务编码对应的编码规则配置, 请检查编码规则配置";
                return result;
            }
        }
        else
        {
            result.Success = false;
            result.ErrMsg = "请定义" + sequenceName + "的单据编码!";
            return result;
        }
        #endregion
        return result;
    }


    /// <summary>
    /// 根据条件查询数据库,并返回对象集合(用于分页数据显示)
    /// </summary>
    /// <param name="search">查询的条件</param>
    /// <returns>指定对象的集合</returns>
    public override async Task<PageResult<SequenceOutputDto>> FindWithPagerAsync(SearchInputDto<Sequence> search)
    {
        bool order = search.Order == "asc" ? false : true;
        string where = GetDataPrivilege();
        if (!string.IsNullOrEmpty(search.Keywords))
        {
            where += string.Format(" and SequenceName like '%{0}%' ", search.Keywords);
        };
        PagerInfo pagerInfo = new PagerInfo
        {
            CurrenetPageIndex = search.CurrenetPageIndex,
            PageSize = search.PageSize
        };
        List<Sequence> list = await repository.FindWithPagerAsync(where, pagerInfo, search.Sort, order);
        PageResult<SequenceOutputDto> pageResult = new PageResult<SequenceOutputDto>
        {
            CurrentPage = pagerInfo.CurrenetPageIndex,
            Items = list.MapTo<SequenceOutputDto>(),
            ItemsPerPage = pagerInfo.PageSize,
            TotalItems = pagerInfo.RecordCount
        };
        return pageResult;
    }
}

如遇到问题到Issues(opens new window) 反馈