领域层的服务

在第二节已基本介绍过服务的作用了,领域层服务的作用主要是为了解决业务上的逻辑问题,更多的时候,服务是一个与业务相关的动作。比如在上述例子中:

在Order表里记录的是每次交易的过程,每次商品的送货费(Freightage)为10元,当商品价格(Price)超过98元可免费送货,当用户 Person积分(Point)超过2000分可获7折优惠(Favorable),1000~2000分可获8折,1000分以下可有9折,最后总体价 格为为(TotalPrice)。

这复杂的业务逻辑,完全可以由一个领域服务类AccountManager来完成

复制代码
namespace Business.Service.DomainService
{
    public class AccountManager
    {
        private Person _person;
        private Order _order;

        public AccountManager(Person person, Order order)
        {
            _person = person;
            _order = order;
        }

        ///计算总体收费 
        public void Account()
        {
            //计算商品数量
            GoodsCount();
            //计算商品价格
            PriceAccount();
            //计算优惠等级
            FavorableAccount();
            double price1 = (_order.Price - _order.Favorable).Value;
            //计算运费
            FreightageAccount(price1);
            //计算总体价费
            _order.TotalPrice = price1 + _order.Freightage.Value;
        }

        //计算商品数量
        private void GoodsCount()
        {
            _order.Count=0;
            foreach (var OrderItem in _order.OrderItem)
                _order.Count += OrderItem.Count;
        }
 
        //商品总体价格
        private void PriceAccount()
        {
            _order.Price = 0;
            foreach (var OrderItem in _order.OrderItem)
                _order.Price += OrderItem.Price * OrderItem.Count;
        }

        //优惠分为三等,积分小于1000有9折,小于2000分为8折,大于2000为7折
        private void FavorableAccount()
        {
            int point = (int)_person.Point.GetInt();

            if (point < 1000)
                _order.Favorable = _order.Price * 0.1;
            if (point >= 1000 && point < 2000)
                _order.Favorable = _order.Price * 0.2;
            if (point > 2000)
                _order.Favorable = _order.Price * 0.3;
        }

        //如果价格在98元以上,可免运费。其余运费为10元
        private void FreightageAccount(double price)
        {
            if (price >= 98)
                _order.Freightage = 0;
            else
                _order.Freightage = 10;
        }
    }
}

你可能会说,在这个业务流程中,除了积分优惠Person.Point以外,其他的业务都只与Order的属性有关,按照充血型模型的方案,完全可以把这些业务放到Order的方法当中,而把积分优惠独立成为一个服务。但在下在很多的开发过程中发现,为模型附上动作会带来一连串的问题,好像你不知道哪些操作应该在模型动作上实现,哪里应该在服务中实现......。对于这些无休止的争论不会因为这里的一个小例子而停止,但在这里我会坚持使用贫血型模型,利用服务来完成所有的动作。

再举一个例子:在最后结单的时候Order表里会产生订单号码OrderNumber和下订日期Delivery,Person表的积分也会加上订单总价的点数。对应这个操作,也可以单独开发一个PaymentManager服务类进行管理。

复制代码
namespace Business.Service.DomainService
{
    public class PaymentManager
    {
        //下单结算
        public void Payment(Order order,Person person)
        {
            //确定下单,建立订单号
            order.OrderNumber = Guid.NewGuid().ToString();
            order.Delivery = DateTime.Now;
            //增加积分
            if (person.Point.HasValue)
                person.Point += (int)order.TotalPrice.GetValueOrDefault();
            else
                person.Point = (int)order.TotalPrice.GetValueOrDefault();
        }
    }
}

利用领域层的服务,使得每个Manager服务类的职能非常明确,业务管理起来也十分地方便,领域层可以随着业务的改变而灵活变动。而且领域层具有 "高内聚,低耦合" 特性,它并不依赖其它任何一层,而只是把业务逻辑包含在里面。

回到目录

六、工厂模式Factory

Factory是常用到软件开发模式,在网上像简单工厂、工厂方法、抽象工厂等开发模式的资料都到处可寻,可这并不是领域驱动设计的主题。在这一节里,我主要想介绍Factory的适用时机。

并非生成所有对象的时候,都需要用到工厂模式。在生成简单对象的时候,可以直接利用构造函数来代替工厂,也可以添加工厂方法来生成对象。但如果在生成对象时,内部属性之间存在一系统复杂的业务规则的时候,就可以把生成方法独立到一个Factory类里面。这时候客户端无需理会潜在逻辑关系,而直接通过这个Factory来生成相应的对象。

举个例子,在新建Order的时候,业务上规定运费是总体金额的1%,折扣规定是7.5折...... 。如果由客户端新建一个对象Order,然后为这些属性负值,那相关的业务逻辑就会暴露在外。这时候就可以使用Factory模式,把属性之间的关系封装到Factory之内,客户端通过Factory就能轻松地生成Order对象而无需要理会复杂的内部关系。

至于较复杂的Factory模式,在此不多作介绍,各位可以在网上查找相关资料。

回到目录

七、细说应用层

1. SOA系统中应用层的特点

在开发SOA分布式系统的时候,应用层是一个重点,它主要有两个作用。

第一,应用层主要作用是协调领域层工作,指挥领域对象解决业务问题,但应用层本身不会牵扯到业务状态。

第二,在SOA系统当中应用层是数据运输中心和信息发放的端口,担负着数据转换与数据收发的责任。

它有以下的特点:

  • 粗粒度

分布式系统与普通网站和应用程序不同,因为它假定外界对系统内部是毫无了解的,用户只想输入相关数据,最后得到一系列计算结果。所以我们应该把计算结果封装在一个数据传输对象(DTO)内,实现粗粒度的传递**,这是一般项目与SOA系统在服务层的一个最明显的差别** 。 想想如果一个页面需要同时显示一个顾客的个人资料、某张订单的详细资料,那将要同时获取Person、Order、OrderItem三张表的信息。在普通系统的开发过程中,这并不会造成太大问题,但在使用远程服务的时候,如果用三个方法分别获取,那将会造成不少的性能损耗。特别是在分布式开发系统中,应用层与表现层之间是实现分离的,更多时候两者是由不同部门所开发的模块,表现层不会了解应用层中的逻辑关系,Person,Order,OrderItem三样东西在表现层看来,也就是同一样东西,那就是返回值。所以在系统内,应该把多张表的信息封装在一个DTO对象内,通过应用层一个远程方法一次性返还。使用粗粒度的数据元素是分布式系统的一个特点

  • 传输性

如果你熟悉SOA系统,对DTO(Data Transfer Object 数据传输对象)这个词一定并不陌生。DTO属于一个数据传输的载体,内部并不存在任何业务逻辑,通过DTO可以把内部的领域对象与外界隔离。DTO所封装的是客户端的数据,所以它的设计更多地是针对客户端的需求,而不是业务逻辑。比如说本来Person与Order是一对多的关系,但当一个页面只要显示的是一个客户的单张订单信息,那我们就可以根据需要把DTO中的Person和Order设计为一对一的关系。如果你是使用MVC开发一般的网站,更多时候会把返回对象直接转化为Model。如果你开发是一个分布式系统,那更多时候会从系统性能与隐藏业务逻辑出发着想。而且考虑到把内部对象转化为DTO,将是一件麻烦的事,建议应该考虑DTO的兼容性,使DTO可以作为多个方法的返还载体。(注意:在SOA系统内,应该从性能出发优先考虑粗粒度元素的传输性问题)

  • 封装性

在SOA系统当中应用层服务的发布并不需要复杂的模型,只需使用外观模式(Facade)把一些功能封装在少数的几个服务类里面,使用Web Service、TCP/IP套接字、MSMQ等服务方式向外界发布。

说到这里,我真的十分感激Martin先生带给我的帮助,在开发过程中,这些复杂的问题带给我不少的困扰,Martin先生一纸富有经验的独特见解,真的带给在下很大的启发。

2. 应用层的协调性

应用层服务会利用Repository,完成实体基本的插入、更新、获取等等操作,并调用领域层的服务管理的业务逻辑。注意观察,一切的业务逻辑都只会隐藏于领域层,应用层服务只起着协调作用,本身不应该包含有任何业务逻辑。

可以看到OrderService就是通过调用AccountManager、PaymentManager等领域层服务来完成结账、付款等一系列复杂业务逻辑的。

复制代码
namespace Business.Service.ApplicationService
{
    public class PersonService
    {
        private IPersonRepository personRepository = DataAccess.CreatePersonRepository();

        public int AddPerson(Person person)
        {
            return personRepository.AddPerson(person);
        }

        public int UpdatePerson(Person person)
        {
            return personRepository.UpdatePerson(person);
        }

        public Person GetPerson(int personID)
        {
            return personRepository.GetPerson(personID);
        }

        public IList<Person> GetList()
        {
            return personRepository.GetList();
        }
    }

 public class OrderService
 {
        private IOrderRepository orderRepository = DataAccess.CreateOrderRepository();

        public int AddOrder(Order order)
        {
            //计算Order总体费用
            Account(order);
            //加入修改后的Order
            return orderRepository.AddOrder(order);
        }

        //调用领域层服务AccountManager,计算Order总体费用
        private void Account(Order order)
        {
            //获取对应Person对象
            IPersonRepository personRepository = DataAccess.CreatePersonRepository();
            Person person = personRepository.GetPerson(order.PersonID);

            //调用服务层的AccountManager对象,计算费用,修改Order
            AccountManager accountManager = new AccountManager(person, order);
            accountManager.Account();    
        }

        //调用领域层服务PaymentManager,确认订单
        public Order Payment(int orderID)
        {
            var order=orderRepository.GetOrder(orderID);
            if (order != null)
            {
                PersonRepository personRepository = new PersonRepository();
                var person=personRepository.GetPerson(order.PersonID);

                PaymentManager paymentManager = new PaymentManager();
                paymentManager.Payment(order, person);

                orderRepository.UpdateOrder(order);
                personRepository.UpdatePerson(person);

                return order;
            }
            else
                throw new Exception("Can not find order!");
        }

        public int DeleteOrder(Order order)
        {
            return orderRepository.DeleteOrder(order.ID);
        }

        public Order GetOrder(int orderID)
        {
            return orderRepository.GetOrder(orderID);
        }

        public IList<Order> GetList()
        {
            return orderRepository.GetList();
        }

        public IList<Order> GetListByPerson(int personID)
        {
            return orderRepository.GetListByPerson(personID);
        }

        public int UpdateOrder(Order order)
        {
            Account(order);
            return orderRepository.UpdateOrder(order);
        }

        public int AddOrderItem(OrderItem orderItem)
        {
            int index = orderRepository.AddOrderItem(orderItem);
            Order order = orderRepository.GetOrder(orderItem.OrderID);
            UpdateOrder(order);
            return index;
        }

        public int DeleteOrderItem(OrderItem orderItem)
        {
            int index = orderRepository.DeleteOrderItem(orderItem.ID);
            Order order = orderRepository.GetOrder(orderItem.OrderID);
            UpdateOrder(order);
            return index;
        }
        .......................
        .......................
    }
}
相关推荐
agent8971 小时前
Spring Boot 接口超时治理:从连接池、线程池到熔断限流的完整排查思路
java·spring boot·后端
星栈1 小时前
LiveView 的 LiveComponent:比 React 组件更轻,但我一开始真的把它用错了
前端·前端框架·elixir
Devin~Y1 小时前
抖音级短视频推荐与直播带货平台面试实战:从 Java 微服务到 RAG 智能客服全链路解析
java·spring boot·redis·spring cloud·kafka·agent·rag
林希_Rachel_傻希希1 小时前
web性能优化之延迟加载图片和<inframe>
前端·javascript·面试
翔云1234562 小时前
简单概括主库上 Executed_Gtid_Set 是什么时候更新的
数据库·mysql
帅次2 小时前
Android 高级工程师面试:Java 多线程与并发 近1年高频追问 22 题
android·java·面试
maxmaxma2 小时前
Konva 从入门到实践 - day1
前端
要开心吖ZSH2 小时前
Java事务与MySQL事务的关系及MVCC通俗解析
java·开发语言·mysql·mvcc
放弃 治疗2 小时前
Windows 11系统 最新 Launch4j 安装与使用教程:从 JAR 到 EXE 的完整打包指南
java·jar