• 基于ABP实现DDD--实体创建和更新


      本文主要介绍了通过构造函数和领域服务创建实体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 _issueRepository; //Issue仓储
        private readonly IRepository _userRepository; //User仓储
        
        // 公有构造函数
        public IssueAppService(IssueManager issueManager, IRepository issueRepository, IRepository userRepository)
        {
            _issueManager = issueManager;
            _issueRepository = issueRepository;
            _userRepository = userRepository;
        }
    
        // 通过构造函数创建Issue
        public async Task 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);
    }
    
    折叠

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

      什么样的情况下会用领域服务创建实体,而不是通过实体构造函数来创建实体呢?主要用在创建实体时需要其它业务规则检测的场景。比如,在创建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 _issueRepository; // Issue的仓储
        
        // 公有构造函数,注入仓储
        public IssueManager(IRepository issueRepository)
        {
            _issueRepository=issueRepository;
        }
        
        public async Task 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 _issueRepository; //Issue的仓储
        private readonly IRepository _userRepository; //User的仓储
        
        // 公共的构造函数,注入所需的依赖
        public IssueAppService(IssueManager issueManager, IRepository issueRepository, IRepository userRepository){
            _issueManager=issueManager;
            _issueRepository=issueRepository;
            _userRepository=userRepository;
        } 
        
        // 创建一个Issue
        public async Task 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);
        }
    }
    
    // 定义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)

  • 相关阅读:
    【前端面试题】【布局】【样式】
    GaussDB之SQL Audit,面向应用开发的SQL审核工具
    JdbcTemplate查询操作
    【GhostNet】《GhostNet:More Features from Cheap Operations》
    SpringMVC04之JSON和全局异常处理
    Hololens开发之实时音视频通讯--02初始化项目+添加必要碰撞组件
    简单的数学运算如何改变算法
    工程伦理--13.1 什么是“邻避效应”?
    Himall商城-公共方法
    圆梦,手写了一个操作系统
  • 原文地址:https://www.cnblogs.com/shengshengwang/p/16514355.html