八、把一切放到一起看看

所有的软件项目都是特殊的,不可能有一种“一刀切”的方法,但是正如我们所看到的,许多想法已经涉及到各种不同的开发方法。 一个项目经过许多阶段的开发,通常它开始探索基本的想法,有时我们甚至不能确定在那个阶段项目的领域是什么。 然后,我们开始提出应用的某个核心竞争力,一个核心领域开始发展。 在这个阶段,业务专家的参与对于确保领域与业务需求一致是至关重要的,并且项目不会因为误解而偏离方向,同时通用语言也在不断发展。 项目往往从一个或两个开发人员增长到一个更大的团队,和团队组织变得更加重要,因为我们需要开始考虑开发所涉及的通信开销,假设每个人都或多或少熟悉代码库中的一切不再适用。 在这一点上,团队可以决定采用领域驱动的方法,并开始对现在已建立的领域进行更详细的建模。 尽管在后期阶段可能不需要每天都有业务专家的参与,但是一致的参与对于确保项目不会偏离核心领域需求是很重要的。

这种理想化的项目增长形式依赖于多个环境因素,不仅需要建立团队来做出所描述的选择,应用还需要为方法做好准备。 我们以前已经看到,并不是所有项目都能很好地使用域驱动方法,而且有许多不同种类的 JavaScript 项目,它们可以在不同的开发阶段适合这种方法。

在本章中,我们将看看不同的项目、一些领域,以及这两个领域如何与领域驱动的设计相结合,以适应整个场景。 我们将探索:

  • 不同类型的项目,包括 JavaScript
  • 客户端和服务器端开发如何影响项目
  • 领域驱动设计的不同问题及其适用性
  • 域驱动设计的示例域

各类 JavaScript 项目

JavaScript,,作为一种非常通用的语言,经历了不同的开发阶段。 它最初被设想为一种在浏览器中启用更多动态行为的方法,但它不仅征服了开发的复杂领域(使用浏览器作为平台和运行时的类似客户机的应用),而且现在也可以在最显著地使用 Node.js 的服务器端应用中看到。

从通过合并效果使文档看起来更具交互性,到在客户端呈现整个应用,这是一个复杂和应用的广泛范围。 有些方法可能更注重应用设计,有些方法可能最好采用较小的类似脚本的方法,保持逻辑简单和本地,以获得最佳维护。

增强用户体验

许多业务应用都是由许多在服务器端呈现的页面组成的应用提供的。 很长一段时间以来,这一直是可行的方法,而且仍然是最简单的方法,因为它将技术堆栈保持在最低限度。 当页面开始变得复杂时,添加一些动态元素可以帮助用户体验很多。 这些元素可以用来指出功能或指导用户。 例如,对输入进行一些客户端验证是非常有用的,这样用户就不会发送一个明显无效的请求,并不得不等待来自服务器的缓慢响应:

<form>
  <label>
    Check Me: <input type="checkbox" id="check-me"></input>
  </label>
  <button id="disable-me">
    I can be clicked if you checked the box
  </button>
</form>

我们经常看到这样的表单,我们想要阻止用户能够点击按钮,直到一个复选框被选中,也可能需要在请求有效之前达成一致。 在服务器端进行验证是很重要的,但是在点击按钮之前向用户提供一些反馈将是一个很好的改进。 一个小的 JavaScript 函数,如以下,可以很容易地完成:

window.onload = function () {
  var checkMeBox = document.getElementById("check-me")
  var disableMeBtn = document.getElementById("disable-me")

  function checkBoxHandler() {
    if(checkMeBox.checked) {
        disableMeBtn.removeAttribute("disabled")
      } else {
        disableMeBtn.setAttribute("disabled", "true")
      }
    }

  checkBoxHandler()
  checkMeBox.onclick = checkBoxHandler
}

我们检查复选框的值,并根据需要关闭或激活按钮。

这是一条业务规则,我们希望看到它反映在代码中; 另一方面,该规则也在服务器端执行,因此没有必要让它毫无疑问地工作。 像这样的问题在应用中经常出现,我们不希望直接使用功能强大的工具。 例如,如果我们开始将表单对象设计为业务对象,并封装表单是否可发送的规则,我们将以代码的可读性为代价,得到一个可以说是更干净的设计。 这是在主要增强用户体验的项目中经常出现的权衡。 一般来说,将视图代码与业务规则混合在一起是不好的; 另一方面,过度设计非常小的增强,比如前面的代码,很容易在为更清晰的模型创建更复杂的基础设施的开销中丢失。

像这样的 UX 增强并不适合域驱动的方法,因为关于业务逻辑的知识必须被复制,需要为 HTML 表示和服务器模型表示使用单独的适配器。 这样的适配器会产生一些开销,而且根据封装的功能数量,它们不一定有意义。 随着客户端代码的增长和更倾向于应用,它开始变得更有意义。

单页应用

近年来,厚客户端应用的概念再次变得越来越普遍。 在 Web 的早期,网站是静态的,后来使用 JavaScript 进行了增强,以简化导航或基本的用户交互。 近年来,浏览器中的客户端应用开始发展到许多业务逻辑驻留在前端的水平,前端本身成为了一个真正的应用。

提示

很久以前,当世界仍然围绕着大型机运转时,计算环境中的客户机通常是接受用户输入并显示输出的哑终端。 随着硬件变得越来越强大,越来越多的业务逻辑被转移到客户端进行处理,直到我们实现真正的客户端应用,比如运行 Microsoft Office。 我们现在可以在浏览器中看到同样的情况,因为应用变得更加复杂,浏览器也变得更加强大。

一个单页的应用通常实现大量的业务逻辑,这些逻辑都是用 JavaScript 实现的,作为查询服务器的胖客户端。 此类应用的示例有很多,从更传统的面向文档的样式到使用 HTML、CSS 和 JavaScript 作为运行时环境的浏览器内应用,这些应用或多或少地完全接管了浏览器。

在开发浏览器内应用时,底层代码的结构比增强网页的某些功能更重要。 问题空间被分成几个部分。 首先,需要以这样一种方式组织代码,即随着应用的增长和变化,代码在较长一段时间内保持可维护性。 随着前端应用代码现在实现了业务逻辑的更大部分,维护负担和重写更大部分的风险也随之增加。 该应用是系统的主要投资。 其次,尽管客户端的技术栈在 HTML、CSS 和 JavaScript 方面似乎相当稳定,但最佳实践和浏览器对特性的支持正在快速发展。 同时,向后兼容性是至关重要的,因为开发人员对用户的升级过程没有太多的控制。 第三,客户端应用的性能方面很重要。 尽管 JavaScript 运行时引擎的加速有了巨大的进步,但用户对应用的期望越来越高,更重要的是,他们同时运行越来越多的应用。 我们不能期望我们的单页应用拥有它所运行的机器的大部分,但是我们必须小心花费资源。

不断增长的对性能的需求与对灵活性的需求之间的对比是支持客户端应用开发的框架和技术开发中的一个驱动因素。 我们希望框架是灵活的,同时避免过度抽象,这可能会对我们的应用的性能造成昂贵的代价。 另一方面,随着客户端应用的增长,我们的用户希望增加交互性,这需要越来越复杂的应用代码来管理。

不同的框架及其含义

JavaScript 框架的世界是非常广阔的,有着不同承诺的新框架不断发布,也不断被抛弃。 所有框架都有自己的用例,虽然提倡不同的架构,但都考虑提供一种组织 JavaScript 应用的方法。

一方面,有一些小框架,或者微框架,它们几乎像库一样,只提供最基本的组织和抽象。 其中最著名和可能最广泛使用的是 Backbone。 目标是提供一种在客户端路由用户的方法——处理 URL,并在浏览器中重写和更新应用状态。 另一方面,状态被封装到模型中,提供并抽象了对内部客户端状态和远程服务器端状态的数据访问,因此可以管理这两种状态的同步。

另一方面,我们发现更大的应用框架,比如 Ember,在浏览器中提供了更集成的开发体验。 处理数据同步,在应用页面中路由太多不同的控制器,以及通过模板(包括接口和后端模型表示之间的数据绑定)向浏览器呈现不同对象的高级视图层。 这非常类似于 Smalltalk 的老派方法,比如 Model - View - Controller 模式。

一个简单的应用使用 Ember 给我们的兽人命名可以像这样工作:

window.App = Ember.Application.create()

App.Orc = Ember.Object.extend({
  name: "Garrazk"
})

App.Router.map(function () {
  this.route(' index' , { path: ' /'  })
})

var orc

App.IndexRoute = Ember.Route.extend({
  templateName: ' orc' ,
  controllerName: ' orc' ,
  model: function() {
    if(!orc) orc = App.Orc.create();
    return orc
  }
});

var names = [ "Yahg", "Hibub", "Crothu", "Rakgu", "Tarod", "Xoknath", "Gorgu", "Olmthu", "Olur", "Mug" ]

App.OrcController = Ember.Controller.extend({
  actions: {
    rename: function () {
      var newName = names[Math.floor(Math.random()*names.length)];
      this.set("model.name", newName)
    }
  }
})

顶级应用管理上下文,然后我们像在大多数 MVC 应用中那样定义路由和控制器。 这个模型可以很好地扩展,允许非常不同的应用。 优点是我们可以很大程度上依赖预先建造好的基础设施。 例如,在前面的代码中,通过声明性地分配templateNamecontrollerName,可以很容易地设置路由和控制器之间的连接。 此外,与视图的连接几乎已经完成,允许我们如下定义主应用模板:

<html>
  <head>
    <script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
    <script src="http://builds.emberjs.com/release/ember-template-compiler.js"></script>
    <script src="http://builds.emberjs.com/release/ember.min.js"></script>
    <script src="/app.js"></script>
  </head>
  <script type="text/x-handlebars" data-template-name="orc">
    <p> ORC! {{ name }} </p>
    <button {{action "rename"}}>Give me a Name!</button>
  </script>
</html>

使用Handlebars.js作为模板,使用preassign模型进行交互,Ember 被设计成可以扩展相当大的前端应用,接管浏览器交互并提供一个完整的应用框架。

沿着这个思路,我们几乎可以找到介于两者之间的一切。 在领域驱动开发的世界中,我们现在必须选择最适合我们的应用和开发风格的内容。 似乎较小的框架更适合领域驱动设计,因为它允许开发人员有更大的影响力,但事实并非如此。 对我们来说,最重要的是如何与框架挂钩。 与我们在服务器端进行交互的方式相同,我们希望将代码抽象为简单的 JavaScript 对象,将框架看作是我们获取显示的用户内容和用户输入回域层的一层。 我们希望我们的域层尽可能地从框架中分离出来。 随着模型-视图-控制器和类似组织在当今开发中的日益流行,框架允许更好地分离组织,只要我们不陷入围绕框架开发的陷阱, 但是,请坚持将所讨论的组织作为框架外部的普通对象,作为所需功能的实现与框架挂钩。

客户端渲染

根据我们正在开发的应用,使用完整的客户端应用可能不是正确的方法。 最终,大多数业务应用都是非常面向任务的,通过表单操作数据并基于此输入触发一些逻辑。 然后,操作的结果以类似文档的方式反映出来。 这代表了大多数业务是如何完成的,它包括一个完成任务的过程,并以一个报告结束。 考虑一下我们在整本书中一直在处理的应用,我们看到了一个类似的模式。 我们在应用中做的最多的部分是由几个步骤组成的,包括地下城主通过填写关于将要发生的运输的细节来触发特定的行动。 然后,后端决定条件是否满足,请求是否可以满足,并触发适当的操作。 大多数逻辑位于应用的服务器端,出于一致性考虑,也需要位于服务器端。 另一方面,客户端是非常面向表单的,任务涉及一个或多个表单步骤,需要根据给定任务的流程完成这些步骤。 流程和任务的逻辑在服务器上,因此完整的客户端应用需要复制大量的服务器知识,以提供客户端应用的感觉,但随后我们仍需要与后端进行检查以进行确认。 这在很大程度上消除了将逻辑转移到客户端的好处。

在这种情况下,一种折衷的方法很有意义,它可以确保利用服务器端的高级调试功能和监视功能,同时仍能给应用带来更流畅的感觉。 其想法是呈现要放置在页面上的 HTML 片段,但通过 JavaScript 将它们放置在页面上,使整个页面替换变得不必要。 最常用的库用来实现这一点是pjax请求 HTML 代码片段,它反过来使用 jQuery 将代码片段放在页面上:

var express = require("express")
var app = express()

app.get("/rambo-5", function (req, res) {
  res.send("<p>Rambo 5 is the 5\. best movie of the Rambo series</p>")
})

app.use(express.static(' public' ));

var server = app.listen(3000, function () {
  console.log("App started...")
})

在本例中,pjax 要求服务器发送一个 HTML 片段作为请求的结果放在页面上。 它只是一个段落标签,包含了关于兰博电影的一些信息:

<!DOCTYPE html>
<html>
  <head>
    <script src="/jquery.min.js"></script>
    <script src="/jquery.pjax.js"></script>
    <script>
      $(document).ready(function () {
        $(document).pjax(' a' , ' #container' )
        var date = new Date()
        $("#clock").html(date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds())
      })
    </script>

  </head>
  <body>
    <h1>About Rambo</h1>
    <div id="container">
      Go to <a href="/rambo-5">Rambo 5</a>.
    </div>
    <div>This page rendered at: <span id="clock"></span></div>
  </body>
</html>

在客户端,我们只需要让 pjax 劫持容器内的所有链接,使其发送一个 pjax 请求并插入适当的内容。 最终的结果是,页面的行为与带有链接的普通 HTML 页面类似,但页面不会在单击时完全刷新。 它将重新加载适当的块并更新窗口位置。

当构建需要大量服务器的应用时,这种方法非常有用,并且仍然能够维护一个流动的类似应用的界面,而不需要大量的构建开销,这涉及到完整的客户端呈现。 再一次,我们可以看到一个很大的区别,使前端更瘦客户端,因此这也许不是一个域驱动的主要候选方法,但与后端建立密切合作使用这种方法,因为它现在是真相的单一来源的应用逻辑。

使用 JavaScript 服务器端

JavaScript 作为一种语言,即使是,虽然它是为浏览器而开发的,但它并不仅限于在浏览器上下文中执行。 浏览器自然就包含了在页面上下文中执行 JavaScript 的环境。 当我们想要在浏览器之外运行 JavaScript 时,总是可以通过 JavaScript 引擎直接执行它。 有多种不同的引擎可用,如 Mozilla 的Spidermonkey或谷歌的V8。 仅仅让 JavaScript 可用显然是不够的,因此我们需要访问文件、套接字和许多其他东西,以便能够高效地处理服务器端代码。

Node.js 接管了这个部分,将谷歌 V8 引擎与标准 POSIX 函数绑定在一起,以访问系统级部分。 这绝不是第一个,还有来自 Mozilla 的Rhino,捆绑了 Java 生态系统和 Java,允许访问 JavaScript 标准库之外的所有部分:

Rhino 1.7 release 5 2015 01 29
js> var file = new java.io.File("./test.txt");
js> importPackage(java.nio.file)
js> Files.readAllLines(file.toPath())
[this is a test text file]

同样的事情在 Node.js 中看起来有点不同,更像我们期望的 JavaScript:

> var fs = require("fs")
> fs.readFile("./test.txt", "utf8", function (err, data) {
... if(err) {
..... return console.log(err)
....}
... console.log(data)
..})
> this is a test text file

有了交互的基础知识,我们就可以构建复杂的服务器端应用,并在有意义的地方利用服务器端开发的特性,就像我们在整本书中所做的那样。

提示

在即将发布的 ECMAScript 6 标准中,将引入一个新的模块语法,以增加客户端和服务器端的 JavaScript 应用的模块化。 ECMAScript 6 几乎完成了,但在编写时它还不是随处可用。 是一个很好的源代码,用于即将进行的更改,并显式地用于 ECMAScript 6 模块,http://www.2ality.com/2014/09/es6-modules-final.html

受控环境的优点

本书大部分内容依赖于 Node.js 作为执行环境的原因是,它提供了一组我们可以依赖的固定特性。 另一方面,浏览器一直是非常灵活和多变的。 在开发业务应用时,这是一个很大的优势。 作为开发人员,我们当然总是想要利用最新和最好的技术,在有意义的地方依赖这些技术是很有意义的,但我们也需要意识到稳定的平台在什么地方是最大的优势。

如果我们想为应用的业务逻辑建模,我们几乎不依赖任何新技术。 我们需要一个稳定的环境,在那里我们可以执行我们已经拥有的和将继续存在的东西。 当然,JavaScript 的优势在于我们可以在客户端和服务器端执行,这意味着如果我们以后决定将某些逻辑滚到客户端,我们可以这样做,并且仍然可以在需要时退回到服务器端执行规则进行验证。

先进的模块化

在过去,JavaScript 一直被称为浏览器语言,在很长一段时间里,加载脚本超出了语言本身的范围,而是由环境中的 HTML 部分通过脚本标记来处理的。

更高级的客户端应用的兴起和服务器端 JavaScript 的兴起改变了这种语言。 这将在下一个版本中包含一个模块标准。 现在,有多种加载其他资源的方法,使用其中一种方法是一个好主意,但这很可能并不重要。 这里重要的一点是,加载外部模块可以让我们更好地将代码分割成逻辑单元,而不是像许多客户端 JavaScript 程序那样需要 1000 多行文件。 在服务器端,这是一个已解决的问题,客户端也不远了。

有了这些不同类型的 JavaScript 程序和挑战,我们就可以考虑在设计业务应用时我们的目标是什么,以及我们如何看到领域驱动设计在开发过程中扮演的角色。

不同种类的复杂性

每个业务应用在整个开发过程中都面临着不同类型的问题。 领域驱动设计的目标是隔离应用的复杂性,通过提供一种语言以及一组用于域内对象交互的规则,使其易于更改和维护。

正如我们在整本书中所看到的,领域驱动的设计都是关于业务逻辑建模的,因此领域专家可以访问它来进行判断和评估。 这是应用的重要部分,如果操作正确,可以在整个开发周期中节省很多麻烦。 当通过域驱动设计来驱动应用时,我们需要识别核心域及其子域。 取决于我们的领域是什么,用于建模的纯业务复杂性并不是唯一的复杂性。

对于业务规则来说,并非每个应用都很复杂,也不是每个应用都适合用我们在整个过程中看到的面向对象方法建模。 有不同性质的复杂性,经常接近困难的计算机科学思考为核心的问题领域,以及与每一领域都有其特定的说话方式和模型这些部分非常明显,我们应该用这个当我们遇到它。

提示

使计算机科学成为另一个商业领域是一种抽象的方法,我们在处理计算机科学问题时遇到的复杂性。 通常情况下,试图将这些问题暴露给业务领域本身是没有用的,而且会导致更多的混乱。 我们可以把计算机科学相关的主题作为我们互动的核心来解决非常具体的问题,如果我们想要孤立它,就可以这样发展它。

算法复杂度

|   | 在数学和计算机科学中,算法(i/ˈælɡərɪðəm/ AL-gə-ri-dhəm)是一套要执行的自包含的分步操作集。 |   | |   | ——维基百科 |

在其核心,我们所做的所有事情都可以被描述为算法。 他们可能非常短,非常独特,但他们是一系列的步骤。 例如,我们在业务应用中遇到的算法是启动囚犯运输所需的一系列步骤。 我们遇到的算法是业务规则,最好作为域本身的一部分进行建模,因为它们直接涉及域对象。 然而,我们可以从数学或计算机科学中重用其他算法,这些算法更抽象,因此不适合业务领域。

当我们谈论算法复杂性时,我们通常指的是众所周知的算法,如树搜索或算法数据结构,如列表或跳过列表。 这些抽象的想法并不适合我们所建模的领域,而是在外部。 当我们在某个领域遇到一个问题,并且已知的算法很好地解决了这个问题时,我们应该利用这个事实,而不是用这些知识使这个领域变得混乱,而是保持它的独立性。

有些应用必定具有很高的算法复杂性,而且它们很可能不是领域驱动设计的主要候选者。 这方面的一个例子可能是搜索,大量的知识驻留在数据结构中,这使得搜索更加高效,因此可以在更大范围内使用。 重要的一点是,在这些领域中,业务专家就是开发人员,我们应该以一种开发人员能够进行最佳交流的方式来对待这个领域。 最基本思想保持一样,我们可能想要培养沟通通过常见的术语,但是在这种情况下,普通的开发商具体条款,和表达的最好方式是在代码中,因此,方法是编写代码,并尝试一下。

逻辑复杂性

与算法问题密切相关的另一个领域是逻辑问题。 取决于的领域,这些可以经常出现,并具有不同程度的复杂性。 此类问题的一个很好的例子是任何类型的配置器,例如,一个允许您订购汽车的应用涉及到额外组件可能冲突的问题。 取决于有多少不同的额外内容和冲突,问题可能很快就会失控。

在逻辑编程中,我们陈述事实,让引擎为我们推导可能的解决方案:

var car = new Car()
var configurator = new Configurator(car)

configurator.bodyType(BodyTypes.CONVERTIBLE)
configurator.engine(Engines.V6)
configurator.addExtra(Extras.RADIO)
configurator.addExtra(Extras.GPS)

configurator.errors() // => {conflicts: [{ "convertible": "v6" }]}

在前面的示例中,配置器由规则引擎支持,规则引擎允许配置器确定配置中的潜在冲突并将冲突报告给用户。 为了做到这一点,我们创建了一个事实或约束列表:

configurator.engineConstraints = [
new Contstraint(BodyTypes.CONVERTIBLE, Engines.V8, Engines.V6_6L)
]

这样,当我们想要订购汽车时,规则引擎可以检查约束是否得到满足。

解决应用中的逻辑问题类似于算法问题,最适合为此目的构建的独立系统,公开特定于领域的逻辑语言,以逻辑术语清楚地表达问题,然后将其包装在领域中。

商业规则

当我们开发业务软件时,我们经常面临的复杂性是由我们开发软件的客户定义的业务规则。 这些规则的复杂性往往不是源于规则本身的高度复杂性,而是因为规则不是一成不变的,行为可以改变。 更重要的是,它需要快速更改以保持软件与业务相关。

实现业务规则意味着跟踪业务需要做什么,而这往往是基于业务领域专家头脑中的事实。 领域建模的重要部分是提取这些知识,并在整个业务中验证其有效性。 这是实域模型努力发挥作用的领域。

当我们能够与最了解该领域的人讨论该领域时,我们可以快速验证,如果我们与这个人使用共同的语言,他或她可以快速向我们解释未来的新规则。 通常,复杂的数据结构和算法不是构建的中心部分,比如应用,这些部分可以通过外部提供系统进行优化,领域的理解和灵活的建模是领域模型的力量所在。

适用于领域驱动设计的领域

在这本书中,我们专注于构建一个商业应用,从本质上确保我们不会超过或未预订我们的地牢,更具体地说,管理由于地牢的限制而需要移动的囚犯的转移。 作为开发人员,我们不得不严重依赖领域专家指导我们进行开发,因为我们还没有必要的业务领域知识。 在这种情况下,语言的建立非常有用,因为它允许我们以一种精确的方式谈论问题是什么,以及我们如何处理新的规则,从那时起。

在前面,我们已经看到并不是所有的域都适合这种策略,即使域也可能包含由辅助系统最好处理的部分。 特别是当开始一个新项目时,我们不能确定是否需要在一个重域层上进行投资。

银行应用

域指定好,有一个固定的规则集,并在很大程度上是处理应该是一个质数候选人被成熟的软件服务,为什么没有很多会计软件,为什么银行大量投资于他们的开发团队吗?

许多人从领域驱动的角度探讨会计问题,这些问题出现在类似的领域。 第一个是规则集,尽管这些规则似乎从外部定义得很好,但它们包含许多需要覆盖的边缘情况,并且需要正确覆盖,因为从本质上讲,大量资金正在系统中流动。 这些规则大多为一组专家所熟知,他们的工作就是在市场发生变化时进行必要的调整。 这给我们带来了第二个问题,许多非常微妙的更改需要表示,并在整个系统中保持一致。

所以,即使表面看来,关系数据库涵盖了很多情况下在一个银行应用,更改所需的敏捷性和内在需要很多沟通与银行专家使银行适合应用领域驱动设计后, 如果他们真的想开始新的发展。

提示

银行业是最好留给专家的领域之一。 如果不需要构建自己的会计系统,最好购买一个架子,因为领域复杂性和出错概率非常高。

旅游应用

在整个本书中,我们一直在关注与领域驱动设计、旅行和相关预订管理的另一个主要候选领域相关的领域。 比较地下城和酒店可能有点奇怪,但从软件的角度来看,两者的管理是相似的。 我们正在努力管理超额和过少预订,同时优化收入。

从表面上看,预订酒店是一个简单而明确的领域,但在深入挖掘时,往往会有许多调整和复杂的规则。 例如,在适当地查看数据库条目时,完全避免超额预订是很容易的,但这与最大化酒店收入的目标相违背。 需要一定数量的超额预订,以补偿最终退出的客人。

这不是管理预订的唯一复杂部分,业务的一个重要部分是根据季节和当前的市场情况进行调整。 预订酒店,而贸易展是在城里可以更昂贵的比普通的一天,尤其是如果不订了整个贸易展期间因为这将意味着一个房间可能保持空的,尽管它可能是轻松预订时可用于整个时间间隔。 另一方面,合作伙伴折扣可以让某些人在这些展览期间预订更便宜,我们希望确保这些人在预订其他客人时可以获得一定数量的房间。 所有预订都有多个需要管理的时间表,如折扣窗口、退款窗口等等。

近年来,领域驱动的设计使得旅行变得更加有趣,因为它的表现方式也有了很大的发展。 虽然之前这些系统被优化为可以通过电话或少数预订代理工作,但它们开始通过网络向公众开放。 这种暴露导致请求显著增加,也增加了所需的支持。 甚至在最近,这些系统不再直接操作,而是需要通过 api 进行访问,以便集成到搜索引擎中。

所有这些都使旅行变得复杂,远不止从数据库中存储和加载操作; 特别是,由于许多系统与一般公共访问相结合的集成给已开发系统的增长和扩展能力带来了巨大的负担,不仅在性能上,而且更重要的是在复杂性上。

域的先决条件

我们所关注的所有领域都涉及业务领域中不同形式的复杂性,这些复杂性可以使用领域驱动的设计方法得到很好的服务。 在前几节中,我们已经看到了非常适合这种方法的两个领域。 他们有什么共同之处?

正如之前所见,这都是关于我们需要处理的不同形式的复杂性。 在业务规则集中快速移动的领域需要更多地关注其建模,因为规则需要随着发展而调整。 更重要的是,不断发展的规则意味着开发人员不能完全理解规则,因此业务专家需要大量参与。 这意味着我们在领域驱动设计中构建的语言很快就会得到回报。 因此,领域驱动设计的一个重要部分是,它与开发人员的访问和理解领域的能力有关。 我们希望能够快速地将业务专家整合到流程中,以避免误解。 归根结底,业务专家才是推动领域发展的人。 作为开发人员,我们提供的软件可以让企业在其所做的事情上更成功。 作为我们领域驱动设计方法的一部分,我们确定了现在对业务真正重要的是什么,以及如何使其更有效和更不容易出错。

现在从另一个角度来处理这个问题,并且仍然考虑访问,这意味着从其他系统访问系统需要变得简单。 目前,许多领域都是如此,新设备一直都很流行,业务总体上朝着企业对企业环境中更高层次的集成迈进。 领域驱动的设计如何在其中发挥作用? 关键还是访问。 我们希望能够提供多个可从外部访问的接口,这些接口具有相同的底层逻辑。 在领域驱动设计中,我们都在构建一个强大的服务层,然后可以通过不同的接口将这一层公开给不同的系统,而不需要重复逻辑,这必然会导致部分和逻辑的分歧。

进一步阅读

正如这本书的名字所暗示的那样,它已经深受 Eric Evans 的思想提出了在他的书《领域驱动设计:解决复杂的软件addison - wesley,我建议这是一个后续。 通过提供不同的示例,并从经典 Java 后端方法的角度,他对通用方法进行了更详细的介绍。

另一本书,不应缺少任何后续软件设计,当然,的企业应用架构模式,马丁,addison - wesley,这是每天使用的大多数模式在面向对象的开发和进入更详细的一般模式。 本书更多地侧重于 Java 方面,但正如我们在本书中所看到的,以面向对象的方式使用 JavaScript 是非常可能的,并且在许多建模场景中都会推荐使用它。

小结

随着用 JavaScript 编写的应用变得越来越复杂,对更强大的应用设计的需求也随之增加。 浏览器应用正在增长,对它们的业务依赖也在增长。 因此,曾经是后端开发的领域开始在前端开发中变得重要。 很长一段时间以来,人们一直在改进后端应用建模的灵活性,这样它们就可以随着业务的发展而增长,现在浏览器应用也需要这样做。 我们可以从这些年来开发的方法中学习到很多东西,尽管有些方法不能直接移植到 JavaScript 中,甚至可能不需要,但总体思想移植得很好。 我希望我能在整本书中阐述这些观点。

另一方面,随着 Node.js 作为应用开发平台的兴起和采用,JavaScript 也进入了后端,在 Rails 后端系统上需要解决 Java 或 Ruby 的相同挑战,现在需要为 JavaScript/Node.js 解决。 保持 JavaScript 的本质是很重要的,就像 Node.js 一样,目标通常是让系统更简单,更容易在更小的块中管理。 这反过来意味着 Node.js 后端采用了比传统企业 Java 系统更轻的建模方法。 这对开发人员来说是一种授权,因为全面的大规模架构讨论转向了自底向上构建的更实际的方法。 这并不意味着体系结构不重要,但是随着前端和后端系统复杂性的分离,复杂性可以用更轻松的方法更好地管理。