ABP VNext 微服务搭建入门(3)-- 业务逻辑写在哪里
业务逻辑可以分为领域逻辑和非领域逻辑。一般来说,领域逻辑包含新增和修改,由领域驱动且不易变,非领域逻辑包含查询和删除,由数据驱动且易变。
一、领域逻辑
1、领域模型
单个实体内部的领域逻辑,不进行持久化,持久化交给上层处理,如领域服务,应用服务。
public class Product : AuditedAggregateRoot<Guid>
{
[NotNull]
public string Code { get; private set; }
[NotNull]
public string Name { get; private set; }
public float Price { get; private set; }
public int StockCount { get; private set; }
public string ImageName { get; private set; }
private Product()
{
//Default constructor is needed for ORMs.
}
internal Product(
Guid id,
[NotNull] string code,
[NotNull] string name,
float price = 0.0f,
int stockCount = 0,
string imageName = null)
{
Check.NotNullOrWhiteSpace(code, nameof(code));
if (code.Length >= ProductConsts.MaxCodeLength)
{
throw new ArgumentException($"Product code can not be longer than {ProductConsts.MaxCodeLength}");
}
Id = id;
Code = code;
SetName(Check.NotNullOrWhiteSpace(name, nameof(name)));
SetPrice(price);
SetImageName(imageName);
SetStockCountInternal(stockCount, triggerEvent: false);
}
public Product SetName([NotNull] string name)
{
Check.NotNullOrWhiteSpace(name, nameof(name));
if (name.Length >= ProductConsts.MaxNameLength)
{
throw new ArgumentException($"Product name can not be longer than {ProductConsts.MaxNameLength}");
}
Name = name;
return this;
}
...
}
2、领域服务
涉及一个或多个完整实体的领域逻辑。实现 DomainService
public class ProductManager : DomainService
{
private readonly IRepository<Product, Guid> _productRepository;
public ProductManager(IRepository<Product, Guid> productRepository)
{
_productRepository = productRepository;
}
public async Task<Product> CreateAsync(
[NotNull] string code,
[NotNull] string name,
float price = 0.0f,
int stockCount = 0)
{
var existingProduct =
await _productRepository.FirstOrDefaultAsync(p => p.Code == code);
if (existingProduct != null)
{
throw new ProductCodeAlreadyExistsException(code);
}
return await _productRepository.InsertAsync(
new Product(
GuidGenerator.Create(),
code,
name,
price,
stockCount
)
);
}
}
3、领域事件
通过事件的发布订阅来处理领域逻辑,对复杂的流程业务进行解耦,实现事务的最终一致性。领域事件可以分为本地事件和分布式事件。
public class Product : AuditedAggregateRoot<Guid>
{
...
public Product SetStockCount(int stockCount)
{
return SetStockCountInternal(stockCount);
}
private Product SetStockCountInternal(int stockCount, bool triggerEvent = true)
{
if (StockCount < 0)
{
throw new ArgumentException($"{nameof(stockCount)} can not be less than 0!");
}
if (StockCount == stockCount)
{
return this;
}
if (triggerEvent)
{
AddDistributedEvent(
new ProductStockCountChangedEto(
Id,
StockCount,
stockCount
)
);
}
StockCount = stockCount;
return this;
}
}
二、非领域逻辑
仓储
仓储用于保存和获取聚合对象。仓储分为两种,一种是基于集合的,一种是基于持久化的。Abp 的 IRespository 实现了常用的CRUD方法。
如果不够用(极少情况),可以自定义仓储实现IRespository。所有不含业务逻辑的CRUD都写在这里。代码实现写在基础架构层,接口定义在Domian层。
应用服务
负责调用领域逻辑增改和处理非领域逻辑的查删,并持久化。实现 ApplicationService。
[Authorize(ProductManagementPermissions.Products.Default)]
public class ProductAppService : ApplicationService, IProductAppService
{
private readonly ProductManager _productManager;
private readonly IRepository<Product, Guid> _productRepository;
public ProductAppService(ProductManager productManager, IRepository<Product, Guid> productRepository)
{
_productManager = productManager;
_productRepository = productRepository;
}
public async Task<PagedResultDto<ProductDto>> GetListPagedAsync(PagedAndSortedResultRequestDto input)
{
await NormalizeMaxResultCountAsync(input);
var products = await _productRepository
.OrderBy(input.Sorting ?? "Name")
.Skip(input.SkipCount)
.Take(input.MaxResultCount)
.ToListAsync();
var totalCount = await _productRepository.GetCountAsync();
var dtos = ObjectMapper.Map<List<Product>, List<ProductDto>>(products);
return new PagedResultDto<ProductDto>(totalCount, dtos);
}
public async Task<ListResultDto<ProductDto>> GetListAsync() //TODO: Why there are two GetList. GetListPagedAsync would be enough (rename it to GetList)!
{
var products = await _productRepository.GetListAsync();
var productList = ObjectMapper.Map<List<Product>, List<ProductDto>>(products);
return new ListResultDto<ProductDto>(productList);
}
public async Task<ProductDto> GetAsync(Guid id)
{
var product = await _productRepository.GetAsync(id);
return ObjectMapper.Map<Product, ProductDto>(product);
}
[Authorize(ProductManagementPermissions.Products.Create)]
public async Task<ProductDto> CreateAsync(CreateProductDto input)
{
var product = await _productManager.CreateAsync(
input.Code,
input.Name,
input.Price,
input.StockCount,
input.ImageName
);
return ObjectMapper.Map<Product, ProductDto>(product);
}
[Authorize(ProductManagementPermissions.Products.Update)]
public async Task<ProductDto> UpdateAsync(Guid id, UpdateProductDto input)
{
var product = await _productRepository.GetAsync(id);
product.SetName(input.Name);
product.SetPrice(input.Price);
product.SetStockCount(input.StockCount);
product.SetImageName(input.ImageName);
return ObjectMapper.Map<Product, ProductDto>(product);
}
[Authorize(ProductManagementPermissions.Products.Delete)]
public async Task DeleteAsync(Guid id)
{
await _productRepository.DeleteAsync(id);
}
private async Task NormalizeMaxResultCountAsync(PagedAndSortedResultRequestDto input)
{
var maxPageSize = (await SettingProvider.GetOrNullAsync(ProductManagementSettings.MaxPageSize))?.To<int>();
if (maxPageSize.HasValue && input.MaxResultCount > maxPageSize.Value)
{
input.MaxResultCount = maxPageSize.Value;
}
}
}