zoukankan      html  css  js  c++  java
  • ABP框架

    文档目录

    本节内容:

    简介

    领域服务(或服务)用来执行领域操作和业务规则。Eric Evans描述一个好的服务需要三个特点(在他的DDD书里):

    1. 操作与领域概念(不是一个实体或值对象天生的一部分)相关。
    2. 接口要按照领域模型的其它元素来定义。
    3. 操作是无状态的。

    应用服务获取/返回DTO(数据传输对象)不同,领域服务获取/返回领域对象(如实体或值类型)。

    领域服务可被应用服务或其它领域服务调用,但不直接被展现层(应用服务是针对它的)使用。

    IDomainService 接口和DomainService 服务

    ABP定义了IDomainService接口,按约定所有的领域服务都要实现它,实现之后,领域服务被自动暂时的注册到依赖注入系统。

    同样,领域服务(随意地)可以从DomainService类继承,因此它可以使用继承得来的日志、本地化、等属性。即使不继承DomainService类,也可以在需要时注入它。

    例子

    假设我们有一个任务管理系统,并有分配任务给人的业务规则。

    创建一个接口

    首先,我们为服务定义一个接口(不是必需,但是一个好的实践):

    public interface ITaskManager : IDomainService
    {
        void AssignTaskToPerson(Task task, Person person);
    }

    如你所见,TaskManager服务使用领域对象:一个Task和一个Person。命名领域服务有些约定,可以命名为:TaskManager、TaskService、或TaskDomainService...

    实现服务

    让我们看一下实现:

    public class TaskManager : DomainService, ITaskManager
    {
        public const int MaxActiveTaskCountForAPerson = 3;
    
        private readonly ITaskRepository _taskRepository;
    
        public TaskManager(ITaskRepository taskRepository)
        {
            _taskRepository = taskRepository;
        }
    
        public void AssignTaskToPerson(Task task, Person person)
        {
            if (task.AssignedPersonId == person.Id)
            {
                return;
            }
    
            if (task.State != TaskState.Active)
            {
                throw new ApplicationException("Can not assign a task to a person when task is not active!");
            }
    
            if (HasPersonMaximumAssignedTask(person))
            {
                throw new UserFriendlyException(L("MaxPersonTaskLimitMessage", person.Name));
            }
    
            task.AssignedPersonId = person.Id;
        }
    
        private bool HasPersonMaximumAssignedTask(Person person)
        {
            var assignedTaskCount = _taskRepository.Count(t => t.State == TaskState.Active && t.AssignedPersonId == person.Id);
            return assignedTaskCount >= MaxActiveTaskCountForAPerson;
        }
    }

    此处我们有两个业务规则:

    • 一个Task应当处于活跃状态,才能分配给一个新的Person。
    • 一个Person最多可以分配3个活跃Task

    你可能想知道为什么我在第一个检查里抛出一个ApplicationException,在第二个检查里抛出UserFriendlyException(见异常处理),这跟领域服务毫不相干,我这么做只是举个例子,具体怎么做完全取决于你。我认为用户接口必须检查一个Task的状态并且不应该分配给一个Person,我认为这是一个应用错误,应当对用户隐藏。第二个是UI上的检查,我们显示一个具有可读性的错误信息给用户。

    使用应用服务

    现在,让我们看一下一个应用服务如何使用TaskManager服务:

    public class TaskAppService : ApplicationService, ITaskAppService
    {
        private readonly IRepository<Task, long> _taskRepository;
        private readonly IRepository<Person> _personRepository;
        private readonly ITaskManager _taskManager;
    
        public TaskAppService(IRepository<Task, long> taskRepository, IRepository<Person> personRepository, ITaskManager taskManager)
        {
            _taskRepository = taskRepository;
            _personRepository = personRepository;
            _taskManager = taskManager;
        }
    
        public void AssignTaskToPerson(AssignTaskToPersonInput input)
        {
            var task = _taskRepository.Get(input.TaskId);
            var person = _personRepository.Get(input.PersonId);
    
            _taskManager.AssignTaskToPerson(task, person);
        }
    }

    TaskApplicationService使用给定的DTO(输入)和仓储来获取相关的task和person,然后把它们传递给TaskManager(领域服务)。

    相关论述

    根据以上例子,你可能有些问题想问。

    为什么不只用应用服务

    也就是说为什么应用服务不实现领域服务里的业务逻辑?

    我们可以简单的这么说:它不是应用服务的任务,因为它不是一个使用案例,而是一个业务操作。我们可能在另一个使用案例里使用同一个“分配任务给人员”的领域逻辑,例如:我们在另一个界面上,用某种方式更新task,这个更新包含分配任务给另一个人员;我们可能有两个不同的UI(一个移动应用和一个Web应用)共享相同的领域;或是一个为远程客户端提供分配task操作的Web Api等。

    如果你的领域很简单, 只有一个UI、只有一个地方用到分配任务给人员的操作,你可能想跳过领域服务,直接在你的应用服务里实现业务逻辑,这不是DDD的最佳实践,但ABP没有强制你不能使用这种设计。

    如何强制你使用领域服务?

    应用服务可以简单的这么做:

    public void AssignTaskToPerson(AssignTaskToPersonInput input)
    {
        var task = _taskRepository.Get(input.TaskId);
        task.AssignedPersonId = input.PersonId;
    }

    写应用服务的开发人员可能不知道TaskManager的存在,直接把给定的PersonId赋值给AssignedPersonId,所以要如何阻止这么做呢?

    关于这个问题,在DDD领域里有很多的论述,也有一些使用模式,我们不准备非常深入,但我们提供一个简单的方式:

    我们把Task实体修改成如下所示:

    public class Task : Entity<long>
    {
        public virtual int? AssignedPersonId { get; protected set; }
    
        //...other members and codes of Task entity
    
        public void AssignToPerson(Person person, ITaskPolicy taskPolicy)
        {
            taskPolicy.CheckIfCanAssignTaskToPerson(this, person);
            AssignedPersonId = person.Id;
        }
    }

    我们把AssignedPersonId的设置器修改成protected,所以在实体类外就不能修改它。添加一个AssignToPerson方法,接受一个Person和一个TaskPolicy。CheckIfCanAssignTaskToPerson方法检查是否可以分配,并在不能分配时抛出一个对应的异常(此处如何实现不重要)。然后应用服务如下所示:

    public void AssignTaskToPerson(AssignTaskToPersonInput input)
    {
        var task = _taskRepository.Get(input.TaskId);
        var person = _personRepository.Get(input.PersonId);
    
        task.AssignToPerson(person, _taskPolicy);
    }

    我们把_taskPolicy注入给ITaskPolicy并传给AssignToPerson方法。至此就没有第二方式能把一个任务分配给一个人员了,我们只能使用AssignToPerson,再也跳不过业务规则。

    英文原文:http://www.aspnetboilerplate.com/Pages/Documents/Domain-Services

  • 相关阅读:
    Windows Phone SDK 8.0 新特性Speech
    《101 Windows Phone 7 Apps》读书笔记Groceries
    创建分辨率自适应的Windows Phone 8应用程序
    《101 Windows Phone 7 Apps》读书笔记BABY MILESTONES
    Windows Phone SDK 8.0 发布
    《101 Windows Phone 7 Apps》读书笔记BABY NAME ELIMINATOR
    《101 Windows Phone 7 Apps》读书笔记BOOK READER
    Word转PDF文档时,如何嵌入字体
    《101 Windows Phone 7 Apps》读书笔记NOTEPAD
    《101 Windows Phone 7 Apps》读书笔记Cowbell
  • 原文地址:https://www.cnblogs.com/kid1412/p/5995248.html
Copyright © 2011-2022 走看看