首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >“业务逻辑应该在服务中,而不是在模型中”有多准确?

“业务逻辑应该在服务中,而不是在模型中”有多准确?
EN

Software Engineering用户
提问于 2013-11-09 21:54:23
回答 10查看 178.8K关注 0票数 452

Situation

今天晚上早些时候,我在回答上回答了一个关于StackOverflow的问题。

问题是:

对现有对象的编辑应该在存储库层还是在服务中完成?例如,如果我有一个有债务的用户。我想改变他的债务。我应该在UserRepository或服务(例如BuyingService )中通过获取对象、编辑对象和保存对象来完成它吗?

我的答案是:

您应该将更改对象的责任留给同一个对象,并使用存储库检索该对象。

实例情况:

代码语言:javascript
复制
class User {
    private int debt; // debt in cents
    private string name;

    // getters

    public void makePayment(int cents){
        debt -= cents;
    }
}

class UserRepository {
    public User GetUserByName(string name){
        // Get appropriate user from database
    }
}

我收到一条评论:

业务逻辑应该真正存在于服务中。不是在模特里。

互联网怎么说?

因此,这使我搜索,因为我从来没有真正(有意识地)使用服务层。我开始阅读服务层模式和工作单元模式,但到目前为止,我不能说我确信必须使用服务层。

例如,马丁·福勒( Martin )关于贫血域模型的反模式的这篇文章

领域空间中有许多以名词命名的对象,这些对象与真实领域模型所具有的丰富的关系和结构相联系。当您查看这些行为时,就会发现这些对象上几乎没有任何行为,这使得它们只不过是一大袋的getter和setter。实际上,这些模型通常伴随着设计规则,这些规则说,您不应该在域对象中放置任何域逻辑。相反,有一组服务对象捕获所有域逻辑。这些服务位于域模型之上,并将域模型用于数据。(...)域对象中应该包含的逻辑是域逻辑--验证、计算、业务规则--不管您喜欢将其称为什么。

在我看来,情况似乎就是这样的:我提倡通过在该类中引入方法来操作对象的数据。然而,我意识到,这应该是一种给定的方式,这可能与如何调用这些方法(使用存储库)有更大关系。

我还感觉到,在这篇文章中(参见下文),服务层更多地被认为是将工作委托给底层模型的法塔德,而不是实际的工作密集型层。

应用层他的服务层名称:定义软件应该执行的任务,并指导表达域对象解决问题。该层负责的任务对业务有意义,或者是与其他系统的应用层交互所必需的。这一层很薄。它不包含业务规则或知识,但只协调任务,并将工作委托给下一层的域对象协作。它没有反映业务情况的状态,但也可以具有反映用户或程序任务进度的状态。

这是增强的这里

服务接口。服务公开所有入站消息都发送到的服务接口。您可以将服务接口看作是将应用程序中实现的业务逻辑(通常是业务层中的逻辑)公开给潜在消费者的外观。

这里

服务层应该没有任何应用程序或业务逻辑,应该主要关注几个方面。它应该包装业务层调用,用客户端能够理解的公共语言翻译您的域,并处理服务器和请求客户端之间的通信介质。

这与讨论服务层的其他资源形成了严重的对比:

服务层应该由具有方法的类组成,这些方法是具有属于同一事务中的操作的工作单元。

或者我已经联系过的一个问题的第二答案

在某种程度上,您的应用程序需要一些业务逻辑。另外,您可能需要验证输入,以确保没有什么邪恶或不正常的东西被请求。这个逻辑属于您的服务层。

“解决方案”?

按照这个答案中的指导原则,我提出了以下使用服务层的方法:

代码语言:javascript
复制
class UserController : Controller {
    private UserService _userService;

    public UserController(UserService userService){
        _userService = userService;
    } 

    public ActionResult MakeHimPay(string username, int amount) {
        _userService.MakeHimPay(username, amount);
        return RedirectToAction("ShowUserOverview");
    }

    public ActionResult ShowUserOverview() {
        return View();
    }
}

class UserService {
    private IUserRepository _userRepository;

    public UserService(IUserRepository userRepository) {
        _userRepository = userRepository;
    }

    public void MakeHimPay(username, amount) {
        _userRepository.GetUserByName(username).makePayment(amount);
    }
}

class UserRepository {
    public User GetUserByName(string name){
        // Get appropriate user from database
    }
}

class User {
    private int debt; // debt in cents
    private string name;

    // getters

    public void makePayment(int cents){
        debt -= cents;
    }
}

结论

总之,这里并没有什么变化:控制器中的代码移到了服务层(这是一件好事,所以这种方法有好处)。然而,这看起来与我最初的答案没有任何关系。

我意识到设计模式是指导方针,而不是在任何可能的情况下都要执行的规则。然而,我还没有找到对服务层的明确解释,以及应该如何看待它。

  • 这是一种简单地从控制器中提取逻辑并将其放入服务中的方法吗?
  • 它应该在控制器和域之间形成一个契约吗?
  • 域和服务层之间应该有一个层吗?

最后但并非最不重要的一点是:遵循最初的评论

业务逻辑应该真正存在于服务中。不是在模特里。

  • 对吗?
    • 我将如何在服务而不是模型中介绍业务逻辑?
EN

回答 10

Software Engineering用户

发布于 2013-11-12 22:29:13

至于你的头衔,我认为这个问题没有道理。MVC模型由数据和业务逻辑组成。说逻辑应该在服务中,而不是在模型中,就像说,“乘客应该坐在座位上,而不是在车里”。

再说一遍,“模型”一词是一个过载的术语。您可能不是指MVC模型,而是指数据传输对象(,DTO)意义上的模型。也就是一个实体。这就是马丁·福勒说的。

在我看来,马丁·福勒( Martin )是在一个理想的世界里谈论事情。在Hibernate和JPA (在Java中)的现实世界中,DTO是一个超级泄漏的抽象。我想把我的业务逻辑放在我的实体里。会让事情变得更干净。问题是,这些实体可能存在于托管/缓存状态中,这种状态很难理解,并且经常阻止您的工作。总结一下我的观点: Martin推荐了正确的方法,但是ORMs阻止了你这样做。

我认为鲍勃马丁有一个更现实的建议和他在这段不免费的视频里给了它。他说要让你的DTO不受逻辑限制。他们只需保存数据并将其传输到另一个更面向对象的层,而不直接使用DTO。这样就避免了漏洞百出的抽象概念来咬你。DTO和DTO本身的层不是OO。但是一旦你走出了这一层,你就会像马丁·福勒所倡导的那样成为OO。

这种分离的好处是它抽象化了持久层。您可以从JPA切换到JDBC (反之亦然),任何业务逻辑都不需要更改。它只是依赖于DTO,它不在乎这些DTO是如何填充的。

要稍微改变主题,您需要考虑SQL数据库不是面向对象的事实。但是ORMs通常每个表都有一个实体--即一个对象。所以从一开始你就已经输了一场仗。根据我的经验,您永远无法以面向对象的方式来表示实体。

至于“服务”,Bob将反对拥有一个名为FooBarService的类。那不是面向对象的。服务是做什么的?任何与FooBars相关的信息。它也可以被标记为FooBarUtils。我认为他会提倡一个服务层(更好的名称应该是业务逻辑层),但是该层中的每个类都有一个有意义的名称。

票数 46
EN

Software Engineering用户

发布于 2013-11-13 20:37:50

我现在正在做绿地项目,昨天我们不得不做出一些建筑上的决定。有趣的是,我不得不重温“企业应用程序体系结构模式”的几个章节。

这就是我们想出来的:

  • 数据层查询和更新数据库。该层通过可注入的存储库公开。
  • 域层这就是业务逻辑存在的地方。该层使用了可注入的存储库,并负责大部分业务逻辑。这是我们将彻底测试的应用程序的核心。
  • 服务层该层与域层对话,并为客户端请求提供服务。在我们的例子中,服务层非常简单--它将请求转发到域层,处理安全性和其他很少的横切关注点。这与MVC应用程序中的控制器没有多大不同--控制器又小又简单。
  • 客户层。通过SOAP与服务层对话。

最后,我们得出以下结论:

Client ->服务->域->数据

我们可以用合理的工作量取代客户、服务或数据层。如果您的域逻辑存在于服务中,并且您已经决定要替换甚至删除服务层,那么您必须将所有业务逻辑移到其他地方。这样的要求很少见,但可能会发生。

说了这些之后,我认为这与马丁·福勒所说的非常接近。

这些服务位于域模型之上,并将域模型用于数据。

下图很好地说明了这一点:

http://martinfowler.com/eaaCatalog/serviceLayer.html

票数 27
EN

Software Engineering用户

发布于 2014-09-11 14:47:54

说明为什么程序员不愿将域逻辑放入域对象的最简单的方法是,他们通常面临“我将验证逻辑放在哪里?”的情况。以这个域对象为例:

代码语言:javascript
复制
public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            if(value < 0) throw new ArgumentOutOfRangeException("value");
            this.someProperty = value;
        }
    }
}

因此,我们在setter中有一些基本的验证逻辑(不能是否定的)。问题是你不能真正重复使用这个逻辑。在某个地方,屏幕、ViewModel或Controller需要在将更改提交到域对象之前进行验证,因为它需要在用户单击“保存”按钮之前或单击“保存”按钮时通知用户他们不能这样做,以及原因。调用setter时对异常进行测试是一种丑陋的攻击,因为您甚至应该在启动事务之前完成所有的验证。

这就是为什么人们会移动验证逻辑--某种服务,比如MyEntityValidator。然后,实体和调用逻辑都可以获得对验证服务的引用并重用它。

如果不这样做,并且仍然希望重用验证逻辑,则最终将其放入实体类的静态方法中:

代码语言:javascript
复制
public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            string message;
            if(!TryValidateSomeProperty(value, out message)) 
            {
                throw new ArgumentOutOfRangeException("value", message);
            }
            this.someProperty = value;
        }
    }

    public static bool TryValidateSomeProperty(int value, out string message)
    {
        if(value < 0)
        {
            message = "Some Property cannot be negative.";
            return false;
        }
        message = string.Empty;
        return true;
    }
}

这将使您的域模型更少“贫血”,并将验证逻辑保持在属性旁边,这很好,但我认为没有人真正喜欢静态方法。

票数 12
EN
页面原文内容由Software Engineering提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://softwareengineering.stackexchange.com/questions/218011

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档