• ABP理论学习之应用服务


    返回总目录


    本篇目录

    应用服务用于将领域逻辑暴露给展现层。展现层调用具有DTO参数的应用服务,使用领域对象来执行一些特定的业务逻辑并返回给展现层一个DTO。这样,展现层就完全独立于领域层了。在一个理想的分层应用中,展现层永远不直接和领域对象打交道。

    IApplicationService接口###

    在ABP中,应用服务应该实现 IApplicationService接口。建议为每个应用服务创建一个接口。这样一来,我们先要为一个应用定义一个接口,如下所示:

    public interface IPersonAppService : IApplicationService
    {
        void CreatePerson(CreatePersonInput input);
    }
    
    

    IPersonAppService只有一个方法。展现层用它来创建一个新的person。CreatePersonInput是如下所示的一个DTO对象:

    public class CreatePersonInput : IInputDto
    {
        [Required]
        public string Name { get; set; }
    
        public string EmailAddress { get; set; }
    }
    
    

    然后我可以实现IPersonAppService:

    public class PersonAppService : IPersonAppService
    {
        private readonly IRepository<Person> _personRepository;
    
        public PersonAppService(IRepository<Person> personRepository)
        {
            _personRepository = personRepository;
        }
    
        public void CreatePerson(CreatePersonInput input)
        {
            var person = _personRepository.FirstOrDefault(p => p.EmailAddress == input.EmailAddress);
            if (person != null)
            {
                throw new UserFriendlyException("There is already a person with given email address");
            }
    
            person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
            _personRepository.Insert(person);
        }
    }
    
    

    这里是一些重点:

    • PersonAppService使用IRepository执行数据库操作。这里使用了依赖注入,而且使用了 构造函数注入的模式。
    • PersonAppService实现了IApplicationService,它是通过ABP自动注册到依赖注入系统的,然后被其他的类注入并使用。
    • CreatePerson方法以 CreatePersonInput作为参数。它是一个输入DTO,会被ABP自动验证。

    ApplicationService类

    应用服务类应该实现应用服务接口(IApplicationService)。此外,还可以选择从ApplicationService基类派生。这样,IApplicationService也被继承实现了。而且,ApplicationService有一些基本功能,使得logging本土化更加简单。建议为你的继承了ApplicationService的应用服务创建一个特殊的基类。这样,你就可以为所有的应用服务添加一些通用功能。一个应用服务类的例子如下所示:

    public class TaskAppService : ApplicationService, ITaskAppService
    {
        public TaskAppService()
        {
            LocalizationSourceName = "SimpleTaskSystem";
        }
    
        public void CreateTask(CreateTaskInput input)
        {
            //记录日志 (Logger 定义在 ApplicationService 类中)
            Logger.Info("Creating a new task with description: " + input.Description);
    
            //获取本地化文本 (L 是LocalizationHelper.GetString(...)的简写, 定义在 ApplicationService类中)
            var text = L("SampleLocalizableTextKey");
    
            //TODO: Add new task to database...
        }
    }
    
    

    你可以定义一个基类,在该基类中的构造函数中定义LocalizationSourceName。这样,就不用为所有的服务类重复定义了。

    工作单元###

    在ABP中,应用服务方法默认是一个工作单元。

    数据库连接和事务管理

    假如说我们想要在一个必须是事务的应用服务方法中调用两个仓储方法。例如:

    public void CreatePerson(CreatePersonInput input)
    {
        var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };    
        _personRepository.Insert(person);
        _statisticsRepository.IncrementPeopleCount();
    }
    
    
    

    我们将一个person实体插入到People表中,然后总人数自增,并保存到另一个表的字段中。这个方法都是用不同的仓储实现的,但是共享了相同的连接和事务。

    在CreatePerson方法开始时,ABP会自动打开数据库连接并开始事务。在方法结束时,如果没有异常发生,会自动提交事务并关闭数据库连接。这样,在CreatePerson方法中的所有数据库操作都是事务的(原子的),如果有任何异常抛出,操作就会回滚。因此,在这个方法中的两个仓储共享相同的连接和事务。

    当调用仓储的GetAll()方法时,会返回一个IQueryable。数据库连接应该在调用该方法后打开。这是因为IQueryable和Linq会延迟执行。数据库真正查询是在调用 ToList()方法时发生的。看下面的例子:

    public SearchPeopleOutput SearchPeople(SearchPeopleInput input)
    {
        //获得 IQueryable<Person>
        var query = _personRepository.GetAll();
    
        //添加过滤
        if (!string.IsNullOrEmpty(input.SearchedName))
        {
            query = query.Where(person => person.Name.StartsWith(input.SearchedName));
        }
    
        if (input.IsActive.HasValue)
        {
            query = query.Where(person => person.IsActive == input.IsActive.Value);
        }
    
        //获取分页结果list
        var people = query.Skip(input.SkipCount).Take(input.MaxResultCount).ToList();
    
        return new SearchPeopleOutput {People = Mapper.Map<List<PersonDto>>(people)};
    }
    
    

    因为一个应用服务方法是一个工作单元,所以在执行这个方法期间数据库连接是打开的。如果在一个不是应用服务的类中调用了GetAll()方法,那么应该显式使用工作单元

    注意这里使用了AutoMapper类库将 List转成List。更多细节请看下一篇DTO博客。

    自动保存更改

    对于工作单元方法,ABP会在方法结束时自动保存所有的更改。假设我们有一个更新一个人的名字的应用服务方法:

    public void UpdateName(UpdateNameInput input)
    {
        var person = _personRepository.Get(input.PersonId);
        person.Name = input.NewName;
    }
    
    

    只需要这样,name字段就改变了。我们甚至都不要调用_personRepository.Update方法。ORM框架会跟踪工作单元内的实体的所有更改,并将更改反映给数据库。

    更多

    请查看《工作单元》

    应用服务的生命周期###

    所有的应用服务实例都是Transient(每次使用时创建)。ABP强烈建议使用依赖注入技术。当一个应用服务类需要注入时,该类的一个新实例会在依赖注入容器中自动创建。更多内容请查看《依赖注入》

  • 相关阅读:
    网络基础知识-TCP/IP协议各层详解
    MySQL及其图形界面navicat的安装
    Python 浅谈编程规范和软件开发目录规范的重要性
    python 浅析模块,包及其相关用法
    spring batch中MyBatisPagingItemReader分页使用介绍
    eclipse中git插件使用
    oracle中查找某用户执行某张表的操作操作记录
    redis集群主流架构方案分析
    消息队列常见的 5 个应用场景
    Kafka vs RocketMQ——单机系统可靠性
  • 原文地址:https://www.cnblogs.com/farb/p/ABPApplicationService.html
Copyright © 2020-2023  润新知