• 基于ABP落地领域驱动设计-06.正确区分领域逻辑和应用逻辑


    系列文章

    围绕DDDABP Framework两个核心技术,后面还会陆续发布核心构件实现综合案例实现系列文章,敬请关注!
    ABP Framework 研习社(QQ群:726299208)
    ABP Framework 学习及实施DDD经验分享;示例源码、电子书共享,欢迎加入!

    领域逻辑和应用逻辑

    正如前面提到的,领域驱动设计中的业务逻辑拆分为两部分:领域逻辑应用逻辑

    image

    领域逻辑由系统的核心领域规则组成,而应用程序逻辑实现特定于应用程序的用例。

    虽然定义很清楚,但实现可能并不容易,常常无法决定哪些代码应该放在应用层,哪些代码应该放在领域层。本节试图解释这些差异。

    多应用层

    当你的系统很大时,DDD有助于处理复杂性问题。特别是,在一个领域中开发多个应用,那么领域逻辑与应用逻辑的分离就变得更加重要。

    假设你正在构建一个有多个应用程序的系统:

    • 一个Web应用程序,使用 ASP .NET Core MVC,展示产品给用户。浏览产品,不需要进行身份验证;只有当用户执行某些操作时,比如向购物车中添加产品,才会要求登陆。
    • 一个后台管理应用程序,使用Angular UI+ REST APIs构建。此应用由公司办公人员做系统管理,比如:编辑产品描述。
    • 一个移动应用程序,和 Web应用程序 相比UI更加简单,通过REST APIs或其他技术(如:TCP/Socket)与服务器通信。

    image

    每一个应用都需要解决不同的需求,实现不同用例(应用服务方法),不同DTO,不同验证和授权规则等等。

    将所有这些逻辑混合到一个应用层中,将使你的服务包含太多的判断条件和复杂的业务逻辑,使代码更难开发、维护和测试,并导致潜在的Bug。

    如果你有单个领域关联多个应用程序:

    • 为每个应用程序或客户端创建单独的应用层,在这些单独层中实现特定于应用的业务逻辑。
    • 使用单个领域层共享核心领域逻辑

    这样的设计使得区分领域逻辑应用逻辑变得更加重要。

    为了更清楚地实现,可以为每种应用程序类型创建不同的项目(.csproj)。

    例如:

    • 后台管理应用创建 IssueTracker.Admin.ApplicationIssueTracker.Admin.Application.Contracts 项目
    • WEB应用创建 IssueTracker.Public.ApplicationIssueTracker.Public.Application.Contracts
    • 移动应用创建 IssueTracker.Mobile.ApplicationIssueTracker.Mobile.Application.Contracts

    示例:正确区分应用逻辑和领域逻辑

    本节包含一些应用服务和领域服务示例,讨论如何决定在这些服务中放置业务逻辑。

    示例:在领域服务中创建 Organization (组织)

    public class OrganizationManager:DomainService
    {
        private readonly IRepository<Organization> _organizationRepository;
        private readonly ICurrentUser _currentUser;
        private readonly IAuthorizationService _authorizationService;
        private readonly IEmailSender _emailSender;
    
        public OrganizationManager(
            IRepository<Organization> organizationRepository,
            ICurrentUser currentUser,
            IAuthorizationService authorizationService,
            IEmailSender emailSender
        )
        {
            _organizationRepository=organizationRepository;
            _currentUser=currentUser;
            _authorizationService=authorizationService;
            _emailSender=emailSender;
        }
        //创建组织
        public async Task<Organization> CreateAsync(string name)
        {
            //检测是否存在同名组织,存在则抛出异常。
            if(await _organizationRepository.AnyAsync(x=>x.Name==name))
            {
                throw new BusinessException("IssueTracking:DuplicateOrganizationName");
            }
            //检测是否拥有创建权限
            await _authorizationService.CheckAsync("OrganizationCreationPermission");
            //记录日志
            Logger.LogDebug($"Creating organization {name} by {_currentUser.UserName}");
            //创建组织实例
            var organization = new Organization();
            //发送提醒邮件
            await _emailSender.SendAsync(
                "systemadmin@issuetracking.com",
                "新组织",
                "新组织名称:"+name
            );
            //返回组织实例
            return organization;
        }
    }
    

    让我们一步一步来分析 CreateAsync 方法中的代码是否都应该放在领域服务中:

    • 正确:组织名重复检测,存在重复名称则抛出异常。该检测与核心领域规则相关,不允许重名。
    • 错误:领域服务不应该进行权限验证。权限验证应该放在应用层。
    • 错误:记录日志包含当前用户的用户名。领域服务不应该依赖当前用户,当前用户(Session)应该是展示层或应用层中的相关概念。
    • 错误:创建新组织发送邮件,我们仍然认为这是业务逻辑。可以能会根据用例来创建不同类型邮件。

    示例:在应用层创建新组织

    public class OrganizationAppService:ApplicationService
    {
        private readonly OrganizationManager _organizationManager;
        private readonly IPaymentService _paymentService;
        private readonly IEmailSender _emailSender;
    
        public OrganizaitonAppService(
            OrganizationManager organizationManager,
            IPaymentService paymentService,
            IEmailSender emailSender
        )
        {
            _organizationManager=organizationManager;
            _paymentService=paymentService;
            _emailSender=emailSender;
        }
        [UnitOfWork]
        [Authorize("OrganizationCreationPermission")]
        public async Task<Organization> CreateAsync(CreateOrganizationDto input)
        {
            //支付组织费用
            await _paymentService.ChargeAsync(
                CurrentUser.Id,
                GetOrganizationPrice()
            );
            //创建组织实例
            var organization = await _organizationManager.CreateAsync(input.Name);
            //保存组织到数据库
            await _organizationManager.InsertAsync(organization);
            //发送提醒邮件
            await _emailSender.SendAsync(
                "systemadmin@issuetracking.com",
                "新组织",
                "新组织名称:"+name
            );
            //返回实例
            return organization;
        }
        private double GetOrganizationPrice()
        {
            return 42;//Gets form somewhere...
        }
    }
    

    让我们看看 CreateAsync 方法,一步一步讨论其中的代码是否应该放在应用服务:

    • 正确:应用服务方法应该是工作单元,ABP框架工作单元系统自动实现,可以不用添加[UnitOfWork]特性。
    • 正确:权限验证应该放在应用层,可以使用 [Authorize] 特性。
    • 正确:在我们的业务逻辑中创建组织是付费服务,当前操作调用基础设施服务进行支付操作。
    • 正确:应用服务方法负责保存变更到数据库。
    • 正确:给系统管理员发送邮件通知。
    • 错误:不能返回实体,应该返回DTO。

    讨论:为什么我们不应该将支付逻辑放在领域服务中?

    你可能想知道为什么付款代码不在 OrganizationManager 里面。这是一件很重要的事情,我们绝不希望付款出错。

    然而,业务的重要性并不意味着要将其视为核心业务逻辑。我们可能有其他的支付用例,在这些用例中,我们不收取费用来创建一个新的组织。

    例如:

    • 后台办公系统中的管理员用户可以创建新组织,不用考虑支付。
    • 系统数据导入、整合、同步,也可能需要在没有任何支付操作的情况下,创建组织。

    如您所见,支付不是创建一个有效组织的必要操作。它是一个特定于用例的应用逻辑。

    示例:CRUD操作

    public class IssueAppService
    {
        private readonly IssueManager _issueManager;
        public IssueAppService(IssueManager issueManager)
        {
            _issueManager=issueManager;
        }
        public async Task<IssueDto> GetAsync(Guid id)
        {
            return await _issueManager.GetAsync(id);
        }
        public async Task CreateAsync(IssueCreationDto input)
        {
            await _issueManager.CreateAsync(input);
        }
        public async Task UpdateAsync(UpdateIssueDto input)
        {
            await _issueManager.UpdateAsync(input);
        }
        public async Task DeleteAsync(Guid id)
        {
            await _issueManager.DeleteAsync(id);
        }
    }
    

    应用服务并没有做任何事情,而是委托给领域服务来处理。只接收DTO参数,并传递给 IssueManger

    • 不要创建只实现简单 CRUD 操作的领域服务方法,而不带任何领域逻辑。
    • 不要传递 DTO 给领域服务,或领域服务方法返回 DTO。

    应用服务可以直接使用仓储,实现查询、创建、更新或删除数据,除非执行这些操作时需要处理领域逻辑,这种情况下,创建领域服务方法,但只针对那些真正需要的方法。

    不要因为将来可能会需要这些CRUD领域服务方法,就去提前创建这些方法! 当需要时再去创建,并重构现有的代码。由于抽象了应用层,重构领域层不会影响到UI层和其他客户端。

    学习帮助

    围绕DDDABP Framework两个核心技术,后面还会陆续发布核心构件实现综合案例实现系列文章,敬请关注!

    ABP Framework 研习社(QQ群:726299208)
    专注 ABP Framework 学习及DDD实施经验分享;示例源码、电子书共享,欢迎加入!
    image

    记录技术修行中的反思与感悟,以码传心,以软制道,知行合一!
  • 相关阅读:
    sqlserver 获取系统用户表结构信息
    Android访问WebService的两种方法
    C# WebService的简单和复杂参数类型和结果的JSON格式
    JQuery的Ajax使用Get,Post方法调用C#WebService并返回数据
    jQuery提交Json数据到Webservice,并接收返回的Json数据
    C#开发的WebService使用JSON格式传递数据+Ajax测试
    Web项目的三层架构和MVC架构异同
    SqlServer更新视图存储过程函数脚本
    SqlServer刷新所有视图
    SqlServer获取表结构语句
  • 原文地址:https://www.cnblogs.com/YGYH/p/14934819.html
Copyright © 2020-2023  润新知