关于程序员:基于ABP实现DDD实体创建和更新

8次阅读

共计 5813 个字符,预计需要花费 15 分钟才能阅读完成。

  本文次要介绍了通过构造函数和畛域服务创立实体 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/25… (拜访明码: 2096)

本文由 mdnice 多平台公布

正文完
 0