mirror of https://github.com/abpframework/abp
commit
0e41609b1b
@ -1,3 +1,128 @@
|
||||
# ABP Documentation
|
||||
# 领域服务
|
||||
|
||||
待添加
|
||||
## 介绍
|
||||
|
||||
在 [领域驱动设计](Domain-Driven-Design.md) (DDD) 解决方案中,核心业务逻辑通常在聚合 ([实体](Entities.md)) 和领域服务中实现. 在以下情况下特别需要创建领域服务
|
||||
|
||||
* 你实现了依赖于某些服务(如存储库或其他外部服务)的核心域逻辑.
|
||||
* 你需要实现的逻辑与多个聚合/实体相关,因此它不适合任何聚合.
|
||||
|
||||
## ABP 领域服务基础设施
|
||||
|
||||
领域服务是简单的无状态类. 虽然你不必从任何服务或接口派生,但 ABP 框架提供了一些有用的基类和约定.
|
||||
|
||||
### DomainService 和 IDomainService
|
||||
|
||||
从 `DomainService` 基类派生领域服务或直接实现 `IDomainService` 接口.
|
||||
|
||||
**示例: 创建从 `DomainService` 基类派生的领域服务.**
|
||||
|
||||
````csharp
|
||||
using Volo.Abp.Domain.Services;
|
||||
namespace MyProject.Issues
|
||||
{
|
||||
public class IssueManager : DomainService
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
当你这样做时:
|
||||
|
||||
* ABP 框架自动将类注册为瞬态生命周期到依赖注入系统.
|
||||
* 你可以直接使用一些常用服务作为基础属性,而无需手动注入 (例如 [ILogger](Logging.md) and [IGuidGenerator](Guid-Generation.md)).
|
||||
|
||||
> 建议使用 `Manager` 或 `Service` 后缀命名领域服务. 我们通常使用如上面示例中的 `Manager` 后缀.
|
||||
**示例: 实现将问题分配给用户的领域逻辑**
|
||||
|
||||
````csharp
|
||||
public class IssueManager : DomainService
|
||||
{
|
||||
private readonly IRepository<Issue, Guid> _issueRepository;
|
||||
public IssueManager(IRepository<Issue, Guid> issueRepository)
|
||||
{
|
||||
_issueRepository = issueRepository;
|
||||
}
|
||||
|
||||
public async Task AssignAsync(Issue issue, AppUser user)
|
||||
{
|
||||
var currentIssueCount = await _issueRepository
|
||||
.CountAsync(i => i.AssignedUserId == user.Id);
|
||||
|
||||
//Implementing a core business validation
|
||||
if (currentIssueCount >= 3)
|
||||
{
|
||||
throw new IssueAssignmentException(user.UserName);
|
||||
}
|
||||
issue.AssignedUserId = user.Id;
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
问题是定义如下所示的 [聚合根](Entities.md):
|
||||
|
||||
````csharp
|
||||
public class Issue : AggregateRoot<Guid>
|
||||
{
|
||||
public Guid? AssignedUserId { get; internal set; }
|
||||
|
||||
//...
|
||||
}
|
||||
````
|
||||
|
||||
* 使用 `internal` 的 set 确保外层调用者不能直接在调用 set ,并强制始终使用 `IssueManager` 为 `User` 分配 `Issue`.
|
||||
|
||||
### 使用领域服务
|
||||
|
||||
领域服务通常用于 [应用程序服务](Application-Services.md).
|
||||
|
||||
**示例: 使用 `IssueManager` 将问题分配给用户**
|
||||
|
||||
````csharp
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MyProject.Users;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
namespace MyProject.Issues
|
||||
{
|
||||
public class IssueAppService : ApplicationService, IIssueAppService
|
||||
{
|
||||
private readonly IssueManager _issueManager;
|
||||
private readonly IRepository<AppUser, Guid> _userRepository;
|
||||
private readonly IRepository<Issue, Guid> _issueRepository;
|
||||
public IssueAppService(
|
||||
IssueManager issueManager,
|
||||
IRepository<AppUser, Guid> userRepository,
|
||||
IRepository<Issue, Guid> issueRepository)
|
||||
{
|
||||
_issueManager = issueManager;
|
||||
_userRepository = userRepository;
|
||||
_issueRepository = issueRepository;
|
||||
}
|
||||
public async Task AssignAsync(Guid id, Guid userId)
|
||||
{
|
||||
var issue = await _issueRepository.GetAsync(id);
|
||||
var user = await _userRepository.GetAsync(userId);
|
||||
await _issueManager.AssignAsync(issue, user);
|
||||
await _issueRepository.UpdateAsync(issue);
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
由于 `IssueAppService` 在应用层, 它不能直接将问题分配给用户.因此,它使用 `IssueManager`.
|
||||
|
||||
## 应用程序服务与领域服务
|
||||
|
||||
虽然应用服务和领域服务都实现了业务规则,但存在根本的逻辑和形式差异;
|
||||
虽然 [应用服务](Application-Services.md) 和领域服务都实现了业务规则,但存在根本的逻辑和形式差异:
|
||||
|
||||
* 应用程序服务实现应用程序的 **用例** (典型 Web 应用程序中的用户交互), 而领域服务实现 **核心的、用例独立的领域逻辑**.
|
||||
* 应用程序服务获取/返回 [数据传输对象](Data-Transfer-Objects.md), 领域服务方法通常获取和返回 **领域对象** ([实体](Entities.md), [值对象](Value-Objects.md)).
|
||||
* 领域服务通常由应用程序服务或其他领域服务使用,而应用程序服务由表示层或客户端应用程序使用.
|
||||
|
||||
## 生命周期
|
||||
|
||||
领域服务的生命周期是 [瞬态](https://docs.abp.io/en/abp/latest/Dependency-Injection) 的,它们会自动注册到依赖注入服务.
|
||||
|
@ -1,3 +1,257 @@
|
||||
## 规约
|
||||
|
||||
TODO..
|
||||
规约模式用于为实体和其他业务对象定义 **命名、可复用、可组合和可测试的过滤器** .
|
||||
|
||||
> 规约是领域层的一部分.
|
||||
|
||||
## 安装
|
||||
|
||||
> 这个包 **已经安装** 在启动模板中.所以,大多数时候你不需要手动去安装.
|
||||
|
||||
添加 [Volo.Abp.Specifications](https://abp.io/package-detail/Volo.Abp.Specifications) 包到你的项目. 如果当前文件夹是你的项目的根目录(`.csproj`)时,你可以在命令行终端中使用 [ABP CLI](CLI.md) *add package* 命令:
|
||||
|
||||
````bash
|
||||
abp add-package Volo.Abp.Specifications
|
||||
````
|
||||
|
||||
## 定义规约
|
||||
|
||||
假设你定义了如下的顾客实体:
|
||||
|
||||
````csharp
|
||||
using System;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
|
||||
namespace MyProject
|
||||
{
|
||||
public class Customer : AggregateRoot<Guid>
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public byte Age { get; set; }
|
||||
|
||||
public long Balance { get; set; }
|
||||
|
||||
public string Location { get; set; }
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
你可以创建一个由 `Specification<Customer>` 派生的新规约类.
|
||||
|
||||
**例如:规定选择一个18岁以上的顾客**
|
||||
|
||||
````csharp
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using Volo.Abp.Specifications;
|
||||
|
||||
namespace MyProject
|
||||
{
|
||||
public class Age18PlusCustomerSpecification : Specification<Customer>
|
||||
{
|
||||
public override Expression<Func<Customer, bool>> ToExpression()
|
||||
{
|
||||
return c => c.Age >= 18;
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
你只需通过定义一个lambda[表达式](https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/lambda-expressions)来定义规约.
|
||||
|
||||
> 你也可以直接实现`ISpecification<T>`接口,但是基类`Specification<T>`做了大量简化.
|
||||
|
||||
## 使用规约
|
||||
|
||||
这里有两种常见的规约用例.
|
||||
|
||||
### IsSatisfiedBy
|
||||
|
||||
`IsSatisfiedBy` 方法可以用于检查单个对象是否满足规约.
|
||||
|
||||
**例如:如果顾客不满足年龄规定,则抛出异常**
|
||||
|
||||
````csharp
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace MyProject
|
||||
{
|
||||
public class CustomerService : ITransientDependency
|
||||
{
|
||||
public async Task BuyAlcohol(Customer customer)
|
||||
{
|
||||
if (!new Age18PlusCustomerSpecification().IsSatisfiedBy(customer))
|
||||
{
|
||||
throw new Exception(
|
||||
"这位顾客不满足年龄规定!"
|
||||
);
|
||||
}
|
||||
|
||||
//TODO...
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
### ToExpression & Repositories
|
||||
|
||||
`ToExpression()` 方法可用于将规约转化为表达式.通过这种方式,你可以使用规约在**数据库查询时过滤实体**.
|
||||
|
||||
````csharp
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Domain.Services;
|
||||
|
||||
namespace MyProject
|
||||
{
|
||||
public class CustomerManager : DomainService, ITransientDependency
|
||||
{
|
||||
private readonly IRepository<Customer, Guid> _customerRepository;
|
||||
|
||||
public CustomerManager(IRepository<Customer, Guid> customerRepository)
|
||||
{
|
||||
_customerRepository = customerRepository;
|
||||
}
|
||||
|
||||
public async Task<List<Customer>> GetCustomersCanBuyAlcohol()
|
||||
{
|
||||
var queryable = await _customerRepository.GetQueryableAsync();
|
||||
var query = queryable.Where(
|
||||
new Age18PlusCustomerSpecification().ToExpression()
|
||||
);
|
||||
|
||||
return await AsyncExecuter.ToListAsync(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
> 规约被正确地转换为SQL/数据库查询语句,并且在DBMS端高效执行.虽然它与规约无关,但如果你想了解有关 `AsyncExecuter` 的更多信息,请参阅[仓储](Repositories.md)文档.
|
||||
|
||||
实际上,没有必要使用 `ToExpression()` 方法,因为规约会自动转换为表达式.这也会起作用:
|
||||
|
||||
````csharp
|
||||
var queryable = await _customerRepository.GetQueryableAsync();
|
||||
var query = queryable.Where(
|
||||
new Age18PlusCustomerSpecification()
|
||||
);
|
||||
````
|
||||
|
||||
## 编写规约
|
||||
|
||||
规约有一个强大的功能是,它们可以与`And`、`Or`、`Not`以及`AndNot`扩展方法组合使用.
|
||||
|
||||
假设你有另一个规约,定义如下:
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using Volo.Abp.Specifications;
|
||||
|
||||
namespace MyProject
|
||||
{
|
||||
public class PremiumCustomerSpecification : Specification<Customer>
|
||||
{
|
||||
public override Expression<Func<Customer, bool>> ToExpression()
|
||||
{
|
||||
return (customer) => (customer.Balance >= 100000);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
你可以将 `PremiumCustomerSpecification` 和 `Age18PlusCustomerSpecification` 结合起来,查询优质成人顾客的数量,如下所示:
|
||||
|
||||
````csharp
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Domain.Services;
|
||||
using Volo.Abp.Specifications;
|
||||
|
||||
namespace MyProject
|
||||
{
|
||||
public class CustomerManager : DomainService, ITransientDependency
|
||||
{
|
||||
private readonly IRepository<Customer, Guid> _customerRepository;
|
||||
|
||||
public CustomerManager(IRepository<Customer, Guid> customerRepository)
|
||||
{
|
||||
_customerRepository = customerRepository;
|
||||
}
|
||||
|
||||
public async Task<int> GetAdultPremiumCustomerCountAsync()
|
||||
{
|
||||
return await _customerRepository.CountAsync(
|
||||
new Age18PlusCustomerSpecification()
|
||||
.And(new PremiumCustomerSpecification()).ToExpression()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
如果你想让这个组合成为一个可复用的规约,你可以创建这样一个组合的规约类,它派生自`AndSpecification`:
|
||||
|
||||
````csharp
|
||||
using Volo.Abp.Specifications;
|
||||
|
||||
namespace MyProject
|
||||
{
|
||||
public class AdultPremiumCustomerSpecification : AndSpecification<Customer>
|
||||
{
|
||||
public AdultPremiumCustomerSpecification()
|
||||
: base(new Age18PlusCustomerSpecification(),
|
||||
new PremiumCustomerSpecification())
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
现在,你就可以向下面一样重新编写 `GetAdultPremiumCustomerCountAsync` 方法:
|
||||
|
||||
````csharp
|
||||
public async Task<int> GetAdultPremiumCustomerCountAsync()
|
||||
{
|
||||
return await _customerRepository.CountAsync(
|
||||
new AdultPremiumCustomerSpecification()
|
||||
);
|
||||
}
|
||||
````
|
||||
|
||||
> 你可以从这些例子中看到规约的强大之处.如果你之后想要更改 `PremiumCustomerSpecification` ,比如将余额从 `100.000` 修改为 `200.000` ,所有查询语句和合并的规约都将受到本次更改的影响.这是减少代码重复的好方法!
|
||||
|
||||
## 讨论
|
||||
|
||||
虽然规约模式通常与C#的lambda表达式相比较,算是一种更老的方式.一些开发人员可能认为不再需要它,我们可以直接将表达式传入到仓储或领域服务中,如下所示:
|
||||
|
||||
````csharp
|
||||
var count = await _customerRepository.CountAsync(c => c.Balance > 100000 && c.Age => 18);
|
||||
````
|
||||
|
||||
自从ABP的[仓储](Repositories.md)支持表达式,这是一个完全有效的用法.你不必在应用程序中定义或使用任何规约,可以直接使用表达式.
|
||||
|
||||
所以,规约的意义是什么?为什么或者应该在什么时候考虑去使用它?
|
||||
|
||||
### 何时使用?
|
||||
|
||||
使用规约的一些好处:
|
||||
|
||||
- **可复用**:假设你在代码库的许多地方都需要用到优质顾客过滤器.如果使用表达式而不创建规约,那么如果以后更改“优质顾客”的定义会发生什么?假设你想将最低余额从100000美元更改为250000美元,并添加另一个条件,成为顾客超过3年.如果使用了规约,只需修改一个类.如果在任何其他地方重复(复制/粘贴)相同的表达式,则需要更改所有的表达式.
|
||||
- **可组合**:可以组合多个规约来创建新规约.这是另一种可复用性.
|
||||
- **命名**:`PremiumCustomerSpecification` 更好地解释了为什么使用规约,而不是复杂的表达式.因此,如果在你的业务中使用了一个有意义的表达式,请考虑使用规约.
|
||||
- **可测试**:规约是一个单独(且易于)测试的对象.
|
||||
|
||||
### 什么时侯不要使用?
|
||||
|
||||
- **没有业务含义的表达式**:不要对与业务无关的表达式和操作使用规约.
|
||||
- **报表**:如果只是创建报表,不要创建规约,而是直接使用 `IQueryable` 和LINQ表达式.你甚至可以使用普通SQL、视图或其他工具生成报表.DDD不关心报表,因此从性能角度来看,查询底层数据存储的方式可能很重要.
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,46 @@
|
||||
$(function () {
|
||||
var $myNav = $('#blog-post-sticky-index');
|
||||
var $scrollToTopBtn = $('.scroll-top-btn');
|
||||
|
||||
window.Toc.helpers.createNavList = function () {
|
||||
return $('<ul class="nav nav-pills flex-column"></ul>');
|
||||
};
|
||||
|
||||
window.Toc.helpers.createChildNavList = function ($parent) {
|
||||
var $childList = this.createNavList();
|
||||
$parent.append($childList);
|
||||
return $childList;
|
||||
};
|
||||
|
||||
window.Toc.helpers.generateNavEl = function (anchor, text) {
|
||||
var $a = $('<a class="nav-link"></a>');
|
||||
$a.attr('href', '#' + anchor);
|
||||
$a.text(text);
|
||||
var $li = $('<li class="nav-item"></li>');
|
||||
$li.append($a);
|
||||
return $li;
|
||||
};
|
||||
|
||||
Toc.init($myNav);
|
||||
|
||||
$('body').scrollspy({
|
||||
target: $myNav,
|
||||
});
|
||||
|
||||
$scrollToTopBtn.click(function () {
|
||||
$('html, body').animate({scrollTop: 0}, 'fast');
|
||||
});
|
||||
|
||||
// When the user scrolls down 20px from the top of the document, show the button
|
||||
window.onscroll = function () {
|
||||
scrollFunction()
|
||||
};
|
||||
|
||||
function scrollFunction() {
|
||||
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
|
||||
$scrollToTopBtn.addClass('showup');
|
||||
} else {
|
||||
$scrollToTopBtn.removeClass('showup');
|
||||
}
|
||||
}
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue