# Reactive and onion architectures ![](https://i.imgur.com/iN8JACY.png) <!-- 反应式架构是一种重新定位动作排序方式的方法,你可以指定动作執行的順序 洋葱架构是一种当你应用函数式编程实践时自然发生的模式,强调模块化、便於修改與方便重複使用 本章會使用反应式编程技术来处理事件和请求,同时使用洋葱架构将代码结构化为抽象层级。 总体而言,将反应式和洋葱架构结合起来可以帮助我们构建高度响应、弹性、可扩展、模块化、可测试和可维护的系统。 --> ## Reactive architecture Reactive architecture is a way to fundamentally reorient the way actions are sequenced so that you specify what actions happen in response to another action. ## Onion architecture Onion architecture is a pattern that occurs naturally when you apply functional programming practices. ## Example of reactive architecture - Add item to cart. ![](https://i.imgur.com/tVl7C1Q.png) ## Tradeoffs of the reactive architecture - NOT “Do X then do Y” - “Do Y whenever X happens” - It is not a silver bullet. - You should compare the two architectures (typical and reactive) to see if it achieves your goals. <!-- 反应式架构的权衡 反应式架构颠覆了我们在代码中表达顺序的典型方式。反应式风格不是说 "先做X再做Y",而是说 "只要X发生就做Y"。有时这使我们的代码更容易编写、阅读和维护。但有时并不如此!它不是银弹。你必须用你的判断力来决定何时使用它和如何使用它。深入了解反应式架构让你做什么会对你有所帮助。然后你可以比较两种架构(典型的和反应式的),看看它是否能实现你的目标。 3點必須考慮到 --> ### Decouples effects from their causes Separating the causes from their effects can sometimes **make code less readable**. However, it can also be very freeing and let you express things much more precisely. <!-- 将效果与原因脱钩 将原因与结果分开,有时会使代码的可读性降低。然而,它也可以非常自由,让你更精确地表达事情。我们将看到这两种情况的例子。 --> ### Treats a series of steps as pipelines We made good use of them by chaining functional tools together. That was a great way to compose calculations into more sophisticated operations. Reactive architecture lets you do a similar composition with **actions and calculations** together. <!-- 将一系列的步骤视为流水线 我们已经看到了由数据转换步骤组成的管道的力量。我们通过将功能工具连接在一起,很好地利用了它们。这是一个将计算组合成更复杂的操作的好方法。反应式架构让你可以用行动和计算来做类似的组合。 --> ### Creates flexibility in your timeline When you reverse the expression of ordering, you gain flexibility in your timeline. Of course, as we’ve seen, that flexibility could be bad if it leads to unwanted possible orderings. But if used with skill, the same flexibility could shorten timelines. <!-- 在你的时间线中创造彈性灵活性 当你颠倒排序的表达方式时,你在你的时间线上获得了灵活性。当然,正如我们所看到的,如果这种灵活性导致了不需要的可能的排序,那么它可能是不好的。但如果有技巧地使用,同样的灵活性可以缩短时间线。 为了研究这个问题,我们将开发一个非常强大的一流的状态模型,它在许多网络应用程序和功能程序中都是通用的。状态是应用程序的一个重要部分,包括功能性应用程序。让我们在下一页深入研究这个模型。我们将用我们开发的东西来解释每个标题。 --> ### ValueCell(v2) ```javascript= function ValueCell(initialValue) { var currentValue = initialValue; var watchers = []; return { val: function() { return currentValue; }, update: function(f) { var oldValue = currentValue; var newValue = f(oldValue); if(oldValue !== newValue) { currentValue = newValue; forEach(watchers, function(watcher) { watcher(newValue); } )}; }, addWatcher: function(f) { watchers.push(f); } }; } // Before var shopping_cart = {}; function add_item_to_cart(name, price) { var item = make_cart_item(name, price); shopping_cart = add_item(shopping_cart, item); var total = calc_total(shopping_cart); set_cart_total_dom(total); update_shipping_icons(shopping_cart); update_tax_dom(total); } // After var shopping_cart = ValueCell({}); function add_item_to_cart(name, price) { var item = make_cart_item(name, price); shopping_cart.update(function(cart) { return add_item(cart, item); }); var total = calc_total(shopping_cart.val()); set_cart_total_dom(total); // update_shipping_icons(shopping_cart.val()); update_tax_dom(total); } shopping_cart.addWatcher(update_shipping_icons); ``` > Add watcher when we can update shipping icons when the cell changes ### FormulaCell ```javascript= function FormulaCell(initialValue) { var myCell = ValueCell(f(upstreamCell.val())); upstreamCell.addWatcher(function(newUpstreamValue) { myCell.update(function(currentValue) { return f(newUpstreamValue); }); }; return { val: myCell.val, addWatcher: myCell.addWatcher }; } // Before var shopping_cart = ValueCell({}); function add_item_to_cart(name, price) { var item = make_cart_item(name, price); shopping_cart.update(function(cart) { return add_item(cart, item); }); var total = calc_total(shopping_cart.val()); set_cart_total_dom(total); update_tax_dom(total); } shopping_cart.addWatcher(update_shipping_icons); // After var shopping_cart = ValueCell({}); var cart_total = FormulaCell(shopping_cart, calc_total); function add_item_to_cart(name, price) { var item = make_cart_item(name, price); shopping_cart.update(function(cart) { return add_item(cart, item); }); } shopping_cart.addWatcher(update_shipping_icons); cart_total.addWatcher(set_cart_total_dom); cart_total.addWatcher(update_tax_dom); ``` ### Decouples effects ![](https://i.imgur.com/7Nx25At.png) ![](https://i.imgur.com/a6z8Xul.png) ![](https://i.imgur.com/EbVeZKt.png) <!-- 效果與原因脫鉤 --> ### Treat series of steps as pipelines ![](https://i.imgur.com/gUt9lN3.png) If you need a stream of events instead of just one event, the ReactiveX (https://reactivex.io) suite of libraries gives you the tools you need. There are also external streaming services, such as Kafka (https://kafka.apache.org) or RabbitMQ (https://www.rabbitmq.com). Those let you implement a reactive architecture at a larger scale in your system between separate services. <!-- 廣泛使用的message queue,而在RabbitMQ 的世界裡面有三個角色,分別是Producer、Queue、Consumer.--> ### Flexibility in your timeline ![](https://i.imgur.com/94exrTy.png) <!-- 在这个例子中,购物车的ValueCell使用watcher函数来处理当前购物车的价值,而这些watcher函数不需要自己读取购物车ValueCell,因此它们不会将购物车全局变量视为一个资源。同样,total FormulaCell通过它的watcher函数来处理当前总价值,而DOM更新也不会使用total FormulaCell。每个DOM更新都会修改DOM的不同部分,因此我们可以将它们视为不同的资源,因此,这些时间线都没有任何共享资源。--> ## Onion architecture ![](https://i.imgur.com/e0yUHdD.png) ### Traditional layered architecture - A typical layout for a layered web server: ![](https://i.imgur.com/g2XAb0O.png) - We see it in frameworks like Ruby on Rails, which builds the domain model (the M in MVC) using active record objects ### I: Interaction Layer - Routines for querying a user record from the database - Accessing the Library of Congress API - Routines for storing a new address for a patron - Routines for displaying the checkout screen to a library patron ### D: Domain Layer - Routines to determine which shelf a book is on given its topic - Routines to calculate the library fines due given a list of checked out books ### L: Language Layer - A string processing library you’ve imported - The Lodash JavaScript library ### Review: - Actions, calculations, and data ### A functional architecture ![](https://i.imgur.com/oDmWAae.png) ![](https://i.imgur.com/VkZoJaD.png) ![](https://i.imgur.com/9Z9jpsW.png) ### Analyze readability and awkwardness Readability depends on quite a few factors. Here are some major ones: • The language you are writing in • The libraries you are using • Your existing legacy code and code style • What your programmers are accustomed to The image of the onion architecture we’ve seen here is an idealized view of a real system. People can easily tie themselves in knots trying to reach that ideal of 100% purity of the onion architecture vision. **However, nothing is perfect.** Part of your role as architect is to trade off between conformance to the architecture diagram and real-world concerns. <!-- 分析可读性和突兀性 好吧,我们在这里变得真实。让我们非常清楚:有时候,某个特定段落的好处是不值得花费的。这包括选择将你的领域的一部分作为计算实现。尽管完全以计算的方式实现你的领域是完全可能的,但我们必须考虑到,有时在一个特定的环境中,一个动作比同等的计算更容易阅读。 可读性取决于相当多的因素。下面是一些主要的因素: - 你所使用的语言 - 你所使用的库 - 你现有的遗留代码和代码风格 - 你的程序员所习惯的东西 我们在这里看到的洋葱架构的图像是一个真实系统的理想化视图。人们很容易把自己捆绑在一起,试图达到洋葱架构的100%纯度的理想愿景。然而,没有什么是完美的。作为架构师,你的部分角色是在符合架构图和现实世界的关注之间进行权衡。 --> #### Code readability While functional code is usually very readable, occasionally the programming language makes a nonfunctional implementation many times clearer. Be on the lookout for those times. For short- term clarity, it may be best to adopt the nonfunctional way. However, be on the lookout for a clear and readable way to cleanly separate the domain layer calculations from the interaction layer actions, usually by extracting calculations. <!-- 虽然函数式代码通常是非常可读的,但偶尔编程语言会使非函数式的实现清晰许多倍。要注意这些时候。对于短期的清晰性,采用非功能化的方式可能是最好的。然而,要注意寻找一种清晰可读的方式,将领域层的计算与交互层的操作干净地分开,通常是通过提取计算。 函数式编程的目标是编写既具有功能性又易读,。虽然有时为了实现短期的清晰度需要采用非函数式的方法,但该书鼓励开发人员不断努力寻求函数式和易读的解决方案,并寻找机会对代码进行重构,使其更具模块化和可维护性。--> #### Development speed Sometimes we need features to get out the door faster than we would like for business reasons. Rush jobs are never ideal, and many compromises are made when rushed. Be ready to clean up the code later to conform to the architecture. You can use the standard skills we’ve learned throughout the book: extracting calculations, converting to chains of functional tools, and manipulating timelines. <!-- 有时候,由于业务原因,我们需要功能比我们希望的更快地出炉。仓促的工作从来都不理想,仓促的时候会做出很多妥协。要准备好以后清理代码以符合架构的要求。你可以使用我们在全书中学到的标准技能:提取计算结果,转换为功能工具链,以及操作时间线。 简而言之,虽然仓促完成的工作可能需要做出妥协,但开发人员始终应该努力维护一个清洁和可维护的代码库,即使这意味着随后需要额外的工作来清理代码。--> #### System performance We often make compromises for system performance. For instance, mutable data is undoubtedly faster than immutable data. Be sure to isolate these compromises. Better still, consider the opti- mization to be part of the interaction layer and see how the calculations in the domain layer can be reused in a speedier way. <!-- 代码的可读性 系统性能 我们经常为系统性能做出妥协。例如,可变的数据无疑比不可变的数据快。一定要把这些妥协隔离开来。更好的是,考虑将操作化为交互层的一部分,看看领域层的计算如何以更快的方式被重用。我们在第52页看到一个例子,我们通过一次从数据库中获取较少的数据来优化电子邮件的生成。域的计算完全没有改变。 --> ## Final example ![](https://i.imgur.com/Vlq0ugH.png) <!-- 你需要做一个 去年销售的所有产品的报告。你 编写一个函数,接收产品并生成报告: 一切都很好,功能齐全。但后来有了新的需求,报告需要改变。你现在需要在报告中包括折扣。不幸的是,产品记录只包括一个可选的折扣标识符,而不是整个折扣记录。那条折扣记录也需要从数据库中获取: --> ![](https://i.imgur.com/o0tzslV.png) ![](https://i.imgur.com/TbKIHA0.png) <!-- 最简单的做法是在回调中获取折扣的ID,以减少。但这将使generateReport()成为一个动作。你需要在顶层做动作--与从数据库中获取产品的代码在同一层次。 记住,总是可以从计算中建立你的域,并将交互层与域层干净地分开。 --> ## Conclusion In this chapter, we got a high-level perspective on two architectural patterns: **reactive architecture and onion architecture**. Reactive architecture is a way to fundamentally reorient the way actions are sequenced so that you specify what actions happen in response to another action. Onion architecture is a pattern that occurs naturally when you apply functional programming practices. It’s a very useful perspective because it shows up at every level of our code. <!-- 总体而言,将反应式和洋葱架构结合起来可以帮助我们构建高度响应、弹性、可扩展、模块化、可测试和可维护的系统。 -->