点这里进入ABP系列文章总目录html
基于DDD的现代ASP.NET开发框架--ABP系列之1五、ABP应用层——应用服务(Application services)
git
ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称。github
ABP的官方网站:http://www.aspnetboilerplate.com数据库
ABP在Github上的开源项目:https://github.com/aspnetboilerplate架构
本文由东莞-天道提供翻译app
应用服务用于将领域(业务)逻辑暴露给展示层。展示层经过传入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接口:ui
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); } }
如下是几个重要提示:
应用服务(Application Services)须要实现IApplicationService接口。固然,你能够选择将你的应用服务(Application Services)继承自ApplicationService基类,这样你的应用服务也就天然而然的实现IApplicationService接口了。ApplicationService基类提供了方便的日志记录和本地化功能。在此建议你针对你的应用程序建立一个应用服务基类继承自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,但你能够在基类中定义它,这样你就不须要在每一个具体的应用服务中定义它。查看日志记录(logging)和本地化(localization)文档能够获取更多的相关信息。
在ABP中,一个应用服务方法默认是一个工做单元。
在应用服务方法中,若是咱们须要调用两个仓储方法,那么这些方法必须为一个事务。举个例子:
public void CreatePerson(CreatePersonInput input) { var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress }; _personRepository.Insert(person); _statisticsRepository.IncrementPeopleCount(); }
咱们向Person表插入一个数据,接着在其余表中修改了Person计数字段的值。这两个操做实现于不一样的仓储中,可是它们使用了相同的数据链接和事务。这是怎么实现的呢?
对于UOW模式,当事务启动而且开始执行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); } //获取分页 var people = query.Skip(input.SkipCount).Take(input.MaxResultCount).ToList(); return new SearchPeopleOutput {People = Mapper.Map<List<PersonDto>>(people)}; }
因为一个应用服务(Application Services)方法就是一个工做单元,因此数据库链接在方法执行期间都是开启的。若是你在非应用服务(Application Services)中调用GetAll(),你须要显式的使用工做单元模式。如:在Controller的Action方法中要使用GetAll()或调用多个有对数据库操做的AppService方法时, 应该将Action方法使用virtual修饰,并在Action的上面经过[UnitOfWork]进行显示开启工做单元模式。
注意我使用了AutoMapper库将List转换成List。能够查看DTO文档获取相关细节。
译者-天道注:这里要说一下,就是uow和非uow模式的区别,两种模式对于数据库链接的打开和关闭是不一样的。对于控制器的方法,ABP默认是非 uow模式,此时若是调用方法会报错,提示数据库未链接。解决的办法是在方法加上virtual。
对于工做单元方法(应用服务(Application Services)方法),在方法结束时ABP将会自动保存全部数据修改。假设咱们须要一个应用服务(Application Services)方法来更新一个Person的Name:
public void UpdateName(UpdateNameInput input) { var person = _personRepository.Get(input.PersonId); person.Name = input.NewName; }
就是这样,Name被成功修改!咱们甚至不须要调用_personRepository.Update方法。ORM框架在工做单元中会跟踪全部实体修改并将修改更新到数据库中。
全部应用服务(Application Services)实例的生命周期都是暂时的(Transient)。这意味着在每次使用都会建立新的应用服务(Application Services)实例。ABP坚定地使用依赖注入技术。当一个应用服务(Application Services)类型须要被注入时,该应用服务(Application Services)类型的新实例将会被依赖注入容器自动建立。查看依赖注入(Dependency Injection)文档获取更多信息。
但愿更多国内的架构师能关注到ABP这个项目,也许这其中有能帮助到您的地方,也许有您的参与,这个项目能够发展得更好。
欢迎加QQ群:
ABP架构设计交流群:134710707 ABP架构设计交流2群: 579765441