1. 애플리케이션 서비스 계층 설명
애플리케이션 서비스는 도메인(비즈니스) 로직을 프레젠테이션 계층에 노출하는 데 사용됩니다. 프리젠테이션 계층은 DTO(데이터 전송 객체) 매개변수를 전달하여 애플리케이션 서비스를 호출하고, 애플리케이션 서비스는 도메인 객체를 통해 해당 비즈니스 로직을 실행하고 DTO를 프리젠테이션 계층으로 반환합니다. 따라서 프리젠테이션 계층과 도메인 계층은 완전히 분리됩니다.
애플리케이션 서비스를 생성할 때 다음 사항에 유의해야 합니다.
ABP에서 애플리케이션 서비스는 IApplicationService 인터페이스를 구현해야 합니다. 가장 좋은 방법은 각 애플리케이션 서비스 인터페이스에 해당하는 IApplicationService 인터페이스를 생성하는 것입니다. . (이 인터페이스를 상속함으로써 ABP는 자동으로 종속성 주입을 돕습니다.)
ABP는 IApplicationService에 대한 기본 구현 ApplicationService를 제공합니다. 이 기본 클래스는 편리한 로깅 및 지역화 기능을 제공합니다. 애플리케이션 서비스를 구현할 때 ApplicationService에서 상속하고 정의된 인터페이스를 구현하면 됩니다.
ABP에서는 기본적으로 애플리케이션 서비스 방식이 작업 단위입니다. ABP는 UOW 모드에 대한 데이터베이스 연결 및 트랜잭션 관리를 자동으로 수행하고 데이터 수정 사항을 자동으로 저장합니다.
2. ITaskAppService 인터페이스 정의
1. 먼저 정의된 인터페이스
public interface ITaskAppService : IApplicationService { GetTasksOutput GetTasks(GetTasksInput input); void UpdateTask(UpdateTaskInput input); int CreateTask(CreateTaskInput input); Task<TaskDto> GetTaskByIdAsync(int taskId); TaskDto GetTaskById(int taskId); void DeleteTask(int taskId); IList<TaskDto> GetAllTasks(); }
를 살펴보고 해당 메서드의 매개변수와 반환 값을 살펴보세요. 직접 사용됩니다. 왜 이런가요? 프레젠테이션 계층과 애플리케이션 서비스 계층은 DTO(Data Transfer Object)를 통해 데이터를 전송하기 때문입니다.
2. 왜 dto를 통해 데이터를 전송해야 하나요?
요약하면 데이터 전송에 DTO를 사용하면 다음과 같은 이점이 있습니다.
데이터 숨기기
직렬화 및 지연 로딩 문제
ABP는 검증을 지원하기 위해 DTO에 대한 계약 클래스를 제공합니다
Dto를 통해 매개변수 또는 반환 값 변경 확장
자세한 내용은
ABP Framework - Data Transfer Object
3, Dto 사양(유연한 응용 프로그램)
ABP에서는 입력/출력 이름 지정을 권장합니다. 매개 변수는 MethodNameInput 및 MethodNameOutput
이며 각 애플리케이션 서비스 메서드에 대해 별도의 입력 및 출력 DTO를 정의합니다(각 메서드의 입력 및 출력에 대해 dto가 정의된 경우 다음을 수행해야 하는 거대한 dto 클래스가 있습니다). 일반적으로 공개 dto를 정의하여 공유됩니다.
메소드가 하나의 매개변수만 허용/반환하더라도 DTO 클래스를 만드는 것이 가장 좋습니다.
일반적으로 다음에서 사용됩니다. 해당 엔터티의 응용 Dto 클래스를 관리하기 위해 서비스 폴더 아래에 새로운 Dtos 폴더를 생성합니다.
3. 애플리케이션 서비스 인터페이스에 필요한 DTO를 정의합니다
1. 먼저 TaskDto의 정의를 살펴보겠습니다.
namespace LearningMpaAbp.Tasks.Dtos{ /// <summary> /// A DTO class that can be used in various application service methods when needed to send/receive Task objects. /// </summary> public class TaskDto : EntityDto { public long? AssignedPersonId { get; set; } public string AssignedPersonName { get; set; } public string Title { get; set; } public string Description { get; set; } public DateTime CreationTime { get; set; } public TaskState State { get; set; } //This method is just used by the Console Application to list tasks public override string ToString() { return string.Format( "[Task Id={0}, Description={1}, CreationTime={2}, AssignedPersonName={3}, State={4}]", Id, Description, CreationTime, AssignedPersonId, (TaskState)State ); } } }
TaskDto는 EntityDto에서 직접 상속됩니다. 일반 엔터티는 Id 속성만 정의하는 간단한 클래스입니다. TaskDto를 직접 정의하는 목적은 여러 응용 프로그램 서비스 메서드 간에 공유되는 것입니다.
2. GetTasksOutput의 정의를 살펴보겠습니다.
가 TaskDto를 직접 공유합니다.
public class GetTasksOutput { public List<TaskDto> Tasks { get; set; } }
3. UpdateTaskInput이 ICustomValidate 인터페이스를 구현하여 사용자 지정 확인을 구현하는 CreateTaskInput 및 UpdateTaskInput
public class CreateTaskInput { public int? AssignedPersonId { get; set; } [Required] public string Description { get; set; } [Required] public string Title { get; set; } public TaskState State { get; set; } public override string ToString() { return string.Format("[CreateTaskInput > AssignedPersonId = {0}, Description = {1}]", AssignedPersonId, Description); } }
/// <summary> /// This DTO class is used to send needed data to <see cref="ITaskAppService.UpdateTask"/> method. /// /// Implements <see cref="ICustomValidate"/> for additional custom validation. /// </summary> public class UpdateTaskInput : ICustomValidate { [Range(1, Int32.MaxValue)] //Data annotation attributes work as expected. public int Id { get; set; } public int? AssignedPersonId { get; set; } public TaskState? State { get; set; } [Required] public string Title { get; set; } [Required] public string Description { get; set; } //Custom validation method. It's called by ABP after data annotation validations. public void AddValidationErrors(CustomValidationContext context) { if (AssignedPersonId == null && State == null) { context.Results.Add(new ValidationResult("Both of AssignedPersonId and State can not be null in order to update a Task!", new[] { "AssignedPersonId", "State" })); } } public override string ToString() { return string.Format("[UpdateTaskInput > TaskId = {0}, AssignedPersonId = {1}, State = {2}]", Id, AssignedPersonId, State); } }
를 살펴보겠습니다. DTO 검증을 이해하려면 ABP Framework - Verification Data Transfer Object
##4를 참조하세요. 마지막으로 필터링을 위한 두 가지 속성이 포함된 GetTasksInput
의 정의를 살펴보겠습니다.
public class GetTasksInput { public TaskState? State { get; set; } public int? AssignedPersonId { get; set; } }
DTO를 정의한 후, 프리젠테이션 레이어와 애플리케이션 서비스 레이어에서 데이터를 전송하기 위해 DTO를 사용하고 있는데, 결국 이러한 DTO를 엔터티로 변환하여 직접적으로 처리해야 한다는 생각이 드시나요? 데이터베이스를 다루세요. 각 DTO를 해당 엔터티로 수동으로 변환해야 하는 경우 작업 부하를 과소평가할 수 없습니다.
당신은 똑똑하지만 이 작업량을 줄일 수 있는 방법이 있는지 확실히 궁금할 것입니다.
四、使用AutoMapper自动映射DTO与实体
1,简要介绍AutoMapper
开始之前,如果对AutoMapper不是很了解,建议看下这篇文章AutoMapper小结。
AutoMapper的使用步骤,简单总结下:
创建映射规则(Mapper.CreateMap
类型映射转换(Mapper.Map
在Abp中有两种方式创建映射规则:
特性数据注解方式:
AutoMapFrom、AutoMapTo 特性创建单向映射
AutoMap 特性创建双向映射
代码创建映射规则:
Mapper.CreateMap
2,为Task实体相关的Dto定义映射规则
2.1,为CreateTasksInput、UpdateTaskInput定义映射规则
其中CreateTasksInput、UpdateTaskInput中的属性名与Task实体的属性命名一致,且只需要从Dto映射到实体,不需要反向映射。所以通过AutoMapTo创建单向映射即可。
[AutoMapTo(typeof(Task))] //定义单向映射 public class CreateTaskInput { ... } [AutoMapTo(typeof(Task))] //定义单向映射 public class UpdateTaskInput { ... }
2.2,为TaskDto定义映射规则
TaskDto与Task实体的属性中,有一个属性名不匹配。TaskDto中的AssignedPersonName属性对应的是Task实体中的AssignedPerson.FullName属性。针对这一属性映射,AutoMapper没有这么智能需要我们告诉它怎么做;
var taskDtoMapper = mapperConfig.CreateMap
taskDtoMapper.ForMember(dto => dto.AssignedPersonName, map => map.MapFrom(m => m.AssignedPerson.FullName));
为TaskDto与Task创建完自定义映射规则后,我们需要思考,这段代码该放在什么地方呢?
四、创建统一入口注册AutoMapper映射规则
如果在映射规则既有通过特性方式又有通过代码方式创建,这时就会容易混乱不便维护。
为了解决这个问题,统一采用代码创建映射规则的方式。并通过IOC容器注册所有的映射规则类,再循环调用注册方法。
1,定义抽象接口IDtoMapping
应用服务层根目录创建IDtoMapping接口,定义CreateMapping方法由映射规则类实现。
namespace LearningMpaAbp{ /// <summary> /// 实现该接口以进行映射规则创建 /// </summary> internal interface IDtoMapping { void CreateMapping(IMapperConfigurationExpression mapperConfig); } }
2,为Task实体相关Dto创建映射类
namespace LearningMpaAbp.Tasks{ public class TaskDtoMapping : IDtoMapping { public void CreateMapping(IMapperConfigurationExpression mapperConfig) { //定义单向映射 mapperConfig.CreateMap<CreateTaskInput, Task>(); mapperConfig.CreateMap<UpdateTaskInput, Task>(); mapperConfig.CreateMap<TaskDto, UpdateTaskInput>(); //自定义映射 var taskDtoMapper = mapperConfig.CreateMap<Task, TaskDto>(); taskDtoMapper.ForMember(dto => dto.AssignedPersonName, map => map.MapFrom(m => m.AssignedPerson.FullName)); } } }
3,注册IDtoMapping依赖
在应用服务的模块中对IDtoMapping进行依赖注册,并解析以进行映射规则创建。
namespace LearningMpaAbp{ [DependsOn(typeof(LearningMpaAbpCoreModule), typeof(AbpAutoMapperModule))] public class LearningMpaAbpApplicationModule : AbpModule { public override void PreInitialize() { Configuration.Modules.AbpAutoMapper().Configurators.Add(mapper => { //Add your custom AutoMapper mappings here... }); } public override void Initialize() { IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly()); //注册IDtoMapping IocManager.IocContainer.Register( Classes.FromAssembly(Assembly.GetExecutingAssembly()) .IncludeNonPublicTypes() .BasedOn<IDtoMapping>() .WithService.Self() .WithService.DefaultInterfaces() .LifestyleTransient() ); //解析依赖,并进行映射规则创建 Configuration.Modules.AbpAutoMapper().Configurators.Add(mapper => { var mappers = IocManager.IocContainer.ResolveAll<IDtoMapping>(); foreach (var dtomap in mappers) dtomap.CreateMapping(mapper); }); } } }
通过这种方式,我们只需要实现IDtoMappting进行映射规则定义。创建映射规则的动作就交给模块吧。
五、万事俱备,实现ITaskAppService
认真读完以上内容,那么到这一步,就很简单了,业务只是简单的增删该查,实现起来就很简单了。可以自己尝试自行实现,再参考代码:
namespace LearningMpaAbp.Tasks { /// <summary> /// Implements <see cref="ITaskAppService"/> to perform task related application functionality. /// /// Inherits from <see cref="ApplicationService"/>. /// <see cref="ApplicationService"/> contains some basic functionality common for application services (such as logging and localization). /// </summary> public class TaskAppService : LearningMpaAbpAppServiceBase, ITaskAppService { //These members set in constructor using constructor injection. private readonly IRepository<Task> _taskRepository; private readonly IRepository<Person> _personRepository; /// <summary> ///In constructor, we can get needed classes/interfaces. ///They are sent here by dependency injection system automatically. /// </summary> public TaskAppService(IRepository<Task> taskRepository, IRepository<Person> personRepository) { _taskRepository = taskRepository; _personRepository = personRepository; } public GetTasksOutput GetTasks(GetTasksInput input) { var query = _taskRepository.GetAll(); if (input.AssignedPersonId.HasValue) { query = query.Where(t => t.AssignedPersonId == input.AssignedPersonId.Value); } if (input.State.HasValue) { query = query.Where(t => t.State == input.State.Value); } //Used AutoMapper to automatically convert List<Task> to List<TaskDto>. return new GetTasksOutput { Tasks = Mapper.Map<List<TaskDto>>(query.ToList()) }; } public async Task<TaskDto> GetTaskByIdAsync(int taskId) { //Called specific GetAllWithPeople method of task repository. var task = await _taskRepository.GetAsync(taskId); //Used AutoMapper to automatically convert List<Task> to List<TaskDto>. return task.MapTo<TaskDto>(); } public TaskDto GetTaskById(int taskId) { var task = _taskRepository.Get(taskId); return task.MapTo<TaskDto>(); } public void UpdateTask(UpdateTaskInput input) { //We can use Logger, it's defined in ApplicationService base class. Logger.Info("Updating a task for input: " + input); //Retrieving a task entity with given id using standard Get method of repositories. var task = _taskRepository.Get(input.Id); //Updating changed properties of the retrieved task entity. if (input.State.HasValue) { task.State = input.State.Value; } if (input.AssignedPersonId.HasValue) { task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value); } //We even do not call Update method of the repository. //Because an application service method is a 'unit of work' scope as default. //ABP automatically saves all changes when a 'unit of work' scope ends (without any exception). } public int CreateTask(CreateTaskInput input) { //We can use Logger, it's defined in ApplicationService class. Logger.Info("Creating a task for input: " + input); //Creating a new Task entity with given input's properties var task = new Task { Description = input.Description, Title = input.Title, State = input.State, CreationTime = Clock.Now }; if (input.AssignedPersonId.HasValue) { task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value); } //Saving entity with standard Insert method of repositories. return _taskRepository.InsertAndGetId(task); } public void DeleteTask(int taskId) { var task = _taskRepository.Get(taskId); if (task != null) { _taskRepository.Delete(task); } } } }
到此,此章节就告一段落。为了加深印象,请自行回答如下问题:
什么是应用服务层?
如何定义应用服务接口?
DTO란 무엇이며 DTO를 어떻게 정의하나요?
DTO를 엔터티와 자동으로 매핑하는 방법은 무엇입니까?
통합 매핑 규칙을 만드는 방법은 무엇입니까?
위는 ABP 입문 시리즈(5) - 애플리케이션 서비스 만들기의 내용입니다. 더 많은 관련 내용은 PHP 중국어 홈페이지(www.php.cn)를 참고해주세요!