4

基于ABP实现DDD--实体创建和更新 - 阿升1990

 2 years ago
source link: https://www.cnblogs.com/shengshengwang/p/16514355.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

  本文主要介绍了通过构造函数和领域服务创建实体2种方式,后者多用于在创建实体时需要其它业务规则检测的场景。最后介绍了在应用服务层中如何进行实体的更新操作。

一.通过构造函数创建实体

假如Issue的聚合根类为:

public class Issue : AggregateRoot<Guid>
{
    public Guid RepositoryId { get; private set; } //不能修改RepositoryId,原因是不支持把一个Issue移动到另外一个Repository下面
    public string Title { get; private set; } //不能直接修改Title,可以通过SetTitle()修改,主要是在该方法中要加入对Title不能重复的校验
    public string Text { get; set; } //可以直接修改
    public Guid? AssignedUserId { get; internal set; } //同一个程序集中是可以修改AssignedUserId的
    
    // 公有构造函数
    public Issue(Guid id, Guid repositoryId, string title, string text=null) : base(id)
    {
        RepositoryId = repositoryId;
        Title = Check.NotNullOrWhiteSpace(title, nameof(title));
        Text = text; //可为空或者null
    }
    
    // 私有构造函数
    private Issue() {}

    // 修改Title的方法
    public void SetTitle(string title)
    {
        // Title不能重复
        Title = Check.NotNullOrWhiteSpace(title, nameof(title));
    }
    // ...
}

在应用服务层创建一个Issue的过程如下:

public class IssueAppService : ApplicationService.IIssueAppService
{
    private readonly IssueManager _issueManager; //Issue领域服务
    private readonly IRepository<Issue, Guid> _issueRepository; //Issue仓储
    private readonly IRepository<AppUser, Guid> _userRepository; //User仓储
    
    // 公有构造函数
    public IssueAppService(IssueManager issueManager, IRepository<Issue, Guid> issueRepository, IRepository<AppUser, Guid> userRepository)
    {
        _issueManager = issueManager;
        _issueRepository = issueRepository;
        _userRepository = userRepository;
    }

    // 通过构造函数创建Issue
    public async Task<IssueDto> CreateAssync(IssueCreationDto input)
    {
        var issue = new Issue(GuidGenerator.Create(), input.RepositoryId, input.Title, input.Text);
    }
    
    if(input.AssigneeId.HasValue)
    {
        // 获取分配给Issue的User
        var user = await _userRepository.GetAsync(input.AssigneeId.Value);
        // 通过Issue的领域服务,将Issue分配给User
        await _issueManager.AssignAsync(issue, user);
    }
    
    // 插入和更新Issue
    await _issueRepository.InsertAsync(issue);
    
    // 返回IssueDto
    return ObjectMapper.Map<Issue, IssueDto>(issue);
}

二.通过领域服务创建实体

  什么样的情况下会用领域服务创建实体,而不是通过实体构造函数来创建实体呢?主要用在创建实体时需要其它业务规则检测的场景。比如,在创建Issue的时候,不能创建Title相同的Issue。通过Issue实体构造函数来创建Issue实体,这个是控制不住的。所以才会有通过领域服务创建实体的情况。
阻止从Issue的构造函数来创建Issue实体,需要将其构造函数的访问权限由public修改为internal:

public class Issue : AggregateRoot<Guid>
{
    // ...

    internal Issue(Guid id, Guid repositoryId, string title, string text = null) : base(id)
    {
        RepositoryId = repositoryId;
        Title = Check.NotNullOrEmpty(title, nameof(title));
        Text = text; //允许为空或者null
    }
    
    // ...
}

通过领域服务IssueManager中的CreateAsync()方法来判断创建的Issue的Title是否重复:

public class IssueManager:DomainService
{
    private readonly IRepository<Issue,Guid> _issueRepository; // Issue的仓储
    
    // 公有构造函数,注入仓储
    public IssueManager(IRepository<Issue,Guid> issueRepository)
    {
        _issueRepository=issueRepository;
    }
    
    public async Task<Issue> CreateAsync(Guid repositoryId, string title, string text=null)
    {
        // 判断Issue的Title是否重复
        if(await _issueRepository.AnyAsync(i=>i.Title==title))
        {
            throw new BusinessException("IssueTracking:IssueWithSameTitleExists");
        }
        // 返回创建的Issue实体
        return new Issue(GuidGenerator.Create(), repositoryId, title, text);
    }
}

在应用服务层IssueAppService中通过IssueManager.CreateAsync()创建实体如下:

public class IssueAppService :ApplicationService.IIssueAppService
{
    private readonly IssueManager _issueManager; //Issue的领域服务
    private readonly IRepository<Issue,Guid> _issueRepository; //Issue的仓储
    private readonly IRepository<AppUser,Guid> _userRepository; //User的仓储
    
    // 公共的构造函数,注入所需的依赖
    public IssueAppService(IssueManager issueManager, IRepository<Issue,Guid> issueRepository, IRepository<AppUser,Guid> userRepository){
        _issueManager=issueManager;
        _issueRepository=issueRepository;
        _userRepository=userRepository;
    } 
    
    // 创建一个Issue
    public async Task<IssueDto> CreateAsync(IssueCreationDto input)
    {
        // 通过领域服务的_issueManager.CreateAsync()创建实体,主要是保证Title不重复
        var issue=await _issueManager.CreateAsync(input.RepositoryId, input.Title, input.Text);
        
        // 获取User,并将Issue分配给User
        if(input.AssignedUserId.HasValue)
        {
            var user =await _userRepository.GetAsync(input.AssignedUserId.Value);
            await _issueManager.AssignToAsynce(issue,user);
        }
        // 插入和更新数据库
        await _issueRepository.InsertAsync(issue);
        // 返回IssueDto
        return ObjectMapper.Map<Issue,IssueDto>(issue);
    }
}

// 定义Issue的创建DTO为IssueCreationDto
public class IssueCreationDto
{
    public Guid RepositoryId{get;set;}
    [Required]
    public string Title {get;set;}
    public Guid? AssignedUserId{get;set;}
    public string Text {get;set;}
}

现在有个疑问是为什么不把Title的重复检测放在领域服务层中来做呢,这就涉及一个区分核心领域逻辑还是应用逻辑的问题了。显然这里Title不能重复属于核心领域逻辑,所以放在了领域服务中来处理。为什么标题重复检测不在应⽤服务中实现?详细的解释参考[1]。

三.实体的更新操作

接下来介绍在应用层IssueAppService中来update实体。定义UpdateIssueDto如下:

public class UpdateIssueDto
{
    [Required]
    public string Title {get;set;}
    public string Text{get;set;}
    public Guid? AssignedUserId{get;set;}
}

实体更新操作的UpdateAsync()方法如下所示:

public class IssueAppService :ApplicationService.IIssueAppService
{
    private readonly IssueManager _issueManager; //Issue领域服务
    private readonly IRepository<Issue,Guid> _issueRepository; //Issue仓储
    private readonly IRepository<AppUser,Guid> _userRepository; //User仓储
    
    // 公有构造函数,注入依赖
    public IssueAppService(IssueManager issueManager, IRepository<Issue,Guid> issueRepository, IRepository<AppUser,Guid> userRepository){
        _issueManager=issueManager;
        _issueRepository=issueRepository;
        _userRepository=userRepository;
    }
    
    // 更新Issue
    public async Task<IssueDto> UpdateAsync(Guid id, UpdateIssueDto input)
    {
        // 从Issue仓储中获取Issue实体
        var issue = await _issueRepository.GetAsync(id);
        
        // 通过领域服务的issueManager.ChangeTitleAsync()方法更新Issue的标题
        await _issueManager.ChangeTitleAsync(issue,input.Title);
        
        // 获取User,并将Issue分配给User
        if(input.AssignedUserId.HasValue)
        {
            var user = await _userRepository.GetAsync(input.AssignedUserId.Value);
            await _issueManager.AssignToAsync(issue, user);
        }
        issue.Text=input.Text;
        // 更新和保存Issue
        // 保存实体更改是应用服务方法的职责
        await _issueRepository.UpdateAsync(issue);
        // 返回IssueDto
        return ObjectMapper.Map<Issue,IssueDto>(issue);
    }
}

需要在IssueManager中添加ChangeTitle():

public async Task ChangeTitleAsync(Issue issue,string title)
{
    // Title不变就返回
    if(issue.Title==title)
    {
        return;
    }
    // Title重复就抛出异常
    if(await _issueRepository.AnyAsync(i=>i.Title==title))
    {
        throw new BusinessException("IssueTracking:IssueWithSameTitleExists");
    }
    // 请它情况更新Title
    issue.SetTitle(title);
}

修改Issue类中SetTitle()方法的访问权限为internal:

internal void SetTitle(string title)
{
    Title=Check.NotNullOrWhiteSpace(title,nameof(title));
}

参考文献:
[1]基于ABP Framework实现领域驱动设计:https://url39.ctfile.com/f/2501739-616007877-f3e258?p=2096 (访问密码: 2096)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK