应用服务用于将领域逻辑暴露给展现层。展现层调用具有DTO参数的应用服务,使用领域对象来执行一些特定的业务逻辑并返回给展现层一个DTO。这样,展现层就完全独立于领域层了。在一个理想的分层应用中,展现层永远不直接和领域对象打交道。
在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); } }
这里是一些重点:
应用服务类应该实现应用服务接口(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
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
对于工作单元方法,ABP会在方法结束时自动保存所有的更改。假设我们有一个更新一个人的名字的应用服务方法:
public void UpdateName(UpdateNameInput input) { var person = _personRepository.Get(input.PersonId); person.Name = input.NewName; }
只需要这样,name字段就改变了。我们甚至都不要调用_personRepository.Update方法。ORM框架会跟踪工作单元内的实体的所有更改,并将更改反映给数据库。
所有的应用服务实例都是 Transient(每次使用时创建) 。ABP强烈建议使用依赖注入技术。当一个应用服务类需要注入时,该类的一个新实例会在依赖注入容器中自动创建。更多内容请查看 《依赖注入》 。