应用服务

应用服务实现应用程序的用例, 将领域层逻辑公开给表示层.

假设你有一个Book实体(聚合根), 如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Book : AggregateRoot<Guid>
{
public const int MaxNameLength = 128;

public virtual string Name { get; protected set; }

public virtual BookType Type { get; set; }

public virtual float? Price { get; set; }

protected Book()
{

}

public Book(Guid id, [NotNull] string name, BookType type, float? price = 0)
{
Id = id;
Name = CheckName(name);
Type = type;
Price = price;
}

public virtual void ChangeName([NotNull] string name)
{
Name = CheckName(name);
}

private static string CheckName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException($"name can not be empty or white space!");
}

if (name.Length > MaxNameLength)
{
throw new ArgumentException($"name can not be longer than {MaxNameLength} chars!");
}

return name;
}
}

IBookAppService接口

在ABP中应用程序服务应该实现IApplicationService接口. 推荐每个应用程序服务创建一个接口:

1
2
3
4
public interface IBookAppService : IApplicationService
{
Task CreateAsync(CreateBookDto input);
}

我们将实现Create方法作为示例. CreateBookDto定义如下:

1
2
3
4
5
6
7
8
9
10
public class CreateBookDto
{
[Required]
[StringLength(Book.MaxNameLength)]
public string Name { get; set; }

public BookType Type { get; set; }

public float? Price { get; set; }
}

BookAppService(实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class BookAppService : ApplicationService, IBookAppService
{
private readonly IRepository<Book, Guid> _bookRepository;

public BookAppService(IRepository<Book, Guid> bookRepository)
{
_bookRepository = bookRepository;
}

public async Task CreateAsync(CreateBookDto input)
{
var book = new Book(
GuidGenerator.Create(),
input.Name,
input.Type,
input.Price
);

await _bookRepository.InsertAsync(book);
}
}
  • BookAppService继承了基类ApplicationService· 这不是必需的, 但是ApplicationService提供了应用服务常见的需求(比如本示例服务中使用的GuidGenerator). 如果不继承它, 我们需要在服务中手动注入IGuidGenerator
  • BookAppService按照预期实现了IBookAppService
  • BookAppService 注入了 IRepository<Book, Guid>(请参见仓储)在CreateAsync方法内部使用仓储将新实体插入数据库.
  • CreateAsync使用Book实体的构造函数从给定的Input值创建新的Book对象

数据传输对象

应用服务使用并返回DTO而不是实体. ABP不会强制执行此规则. 但是将实体暴露给表示层(或远程客户端)存在重大问题, 所以不建议返回实体.

CRUD应用服务

如果需要创建具有Create,Update,Delete和Get方法的简单CRUD应用服务,则可以使用ABP的基类轻松构建服务. 你可以继承CrudAppService.

创建继承ICrudAppService接口的IBookAppService接口.

1
2
3
4
5
6
7
8
9
public interface IBookAppService : 
ICrudAppService< //Defines CRUD methods
BookDto, //Used to show books
Guid, //Primary key of the book entity
PagedAndSortedResultRequestDto, //Used for paging/sorting on getting a list of books
CreateUpdateBookDto, //Used to create a new book
CreateUpdateBookDto> //Used to update a book
{
}

ICrudAppService 有泛型参数来获取实体的主键类型和CRUD操作的DTO类型(它不获取实体类型,因为实体类型未向客户端公开使用此接口).

最后BookAppService实现非常简单:

1
2
3
4
5
6
7
8
9
10
public class BookAppService : 
CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto,
CreateUpdateBookDto, CreateUpdateBookDto>,
IBookAppService
{
public BookAppService(IRepository<Book, Guid> repository)
: base(repository)
{
}
}

CrudAppService实现了ICrudAppService接口中声明的所有方法. 然后,你可以添加自己的自定义方法或重写和自定义实现.

AbstractKeyCrudAppService

CrudAppService 要求你的实体拥有一个Id属性做为主键. 如果你使用的是复合主键,那么你无法使用它.

AbstractKeyCrudAppService 实现了相同的 ICrudAppService 接口,但它没有要求实体有主键.

假设你有实体 District,它的CityId 和 Name 做为复合主键,使用 AbstractKeyCrudAppService 时需要你自己实现 DeleteByIdAsync 和 GetEntityByIdAsync 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class DistrictAppService
: AbstractKeyCrudAppService<District, DistrictDto, DistrictKey>
{
public DistrictAppService(IRepository<District> repository)
: base(repository)
{
}

protected async override Task DeleteByIdAsync(DistrictKey id)
{
await Repository.DeleteAsync(d => d.CityId == id.CityId && d.Name == id.Name);
}

protected async override Task<District> GetEntityByIdAsync(DistrictKey id)
{
return await AsyncQueryableExecuter.FirstOrDefaultAsync(
Repository.Where(d => d.CityId == id.CityId && d.Name == id.Name)
);
}
}

这个实现需要你创建一个类做为复合键:

1
2
3
4
5
6
public class DistrictKey
{
public Guid CityId { get; set; }

public string Name { get; set; }
}

生命周期

应用服务的生命周期是transient的,它们会自动注册到依赖注入系统.

总结

  • 将实体对象作为方法参数,而不是其id值。如果接受其id值,则需要从领域服务内的数据库中检索实体。这种方法使应用程序代码在同一请求(用例)的不同位置多次加载同一实体,这是低效的,并导致错误。