抽象化你的依赖关系

111 阅读8分钟

本系列的前几部分,我们讨论了领域的概念,以及它如何与你的应用程序中的独立和易维护的模块相关联。大多数介绍的概念都集中在分离你的应用程序的各个部分,并避免它们之间的紧密耦合。即使你使你的模块独立,但一旦你不得不替换它们,仍然会有一些事情导致大量的、耗时的重写--第三方。

在这篇文章中,你将学习如何使你的应用程序也能独立于它们!

为什么模块化架构是不够的?

想象一下下面的情景。你在4年前开始了你的公司,并决定使用无头的WooCommerce作为你的后台平台,并使用Nuxt作为前台,因为当时对你这个自以为是的网络开发者来说,这是最简单的设置。随着时间的推移,你的业务不断增长,WooCommerce对你来说不再是一个可行的选择。你决定用一个更复杂的平台来取代它,比如Commercetools。尽管你的前台已经与后台解耦,并且前台功能保持不变,但你仍然必须为新的平台从头开始编写你的商店业务逻辑(几乎),因为你的所有代码都依赖于WooCommerce的数据格式和它的API。

上面的例子完美地说明了,使你的应用程序模块化并不足以确保它在未来不会遭受严重的重写。事实上,没有什么会发生,但我们仍然可以做很多事情来使它不那么容易发生。让你的模块不互相耦合是一件事,但每个应用程序都严重依赖不同的第三方服务和库。

解决上述问题是我们创建Vue Storefront的主要原因之一。电子商务技术领域的变化如此之快,以至于改变技术栈的一部分是客户经常做的事情。结合API-first的方法,你不是使用一个单一的电子商务平台来管理整个商店,而是使用专门的服务组合,每个服务都专注于电子商务领域的一个小子领域,如PIM(产品信息管理),OMS(订单管理软件),税收,购物车等。

如果你觉得这听起来像是永无止境的重写和无休止的投资--你绝对是对的,而且电子商务行业并不是唯一向这个方向转变的行业。API优先的方法正在风靡网络开发领域

那么,我们可以做什么来避免每次决定改变我们的应用程序中的一些第三方时的耗时和昂贵的重写?

使用抽象概念

我们可以从面向对象的设计模式(如AdapterBridge)中学到的解决这个问题的常见方法是使用抽象。我们不直接使用第三方服务(如电子商务平台或CMS)的API,而是创建一个接口,将其与我们的应用程序连接起来,这个接口是连接该服务和应用程序其他部分的唯一部分。

一个有助于理解这个概念的好的心理模型是把这个抽象概念看作一个适配器。如果你的电脑只有USB-C接口,那么它就只能与USB-C电缆兼容,除非我们找到一个可以用于HDMI或显示端口的适配器。这正是我们将为我们的第三方所做的事情。

创建正确的抽象

为了找到一个好的抽象,你必须找到一个给定类型的所有服务之间的相似性,并创建一个通用的接口,让所有的服务都能适应。编写抽象并不容易,你通常会在头几次做错,直到你将连接足够多的服务,找到它们工作方式的相似之处。不要灰心--当你不得不改变一个重要的后端服务时,这仍然比重写一半的代码库要少得多。

当我在考虑正确的抽象时,我通常会研究一些特定类型的服务,并尝试实现一些我知道会在我的应用程序中广泛使用的用例。一旦我有了一些代码样本,我就会写下它们的差异和相似之处。在此基础上,我创建了最简单、最通用的API,并尝试为每个服务编写适配器来实现它。

当然,你的过程可能是完全不同的。每个人都在以自己的方式弄清楚这些事情。我所分享的是到目前为止对我最有效的模式。它看起来很耗时,但你用得越多,你就能在脑海中捕捉到越多的东西,甚至不用写代码就能想出来!这就是 "两次测量"。把它看作是对我们的代码的 "两次测量,一次切割"。

Learn Vue.js 3 With Vue School

**题外话:**我强烈建议你阅读Eric Elliot的这篇关于抽象的文章。他有很好的天赋,能用简单的语言解释复杂的术语。

抽象支付方法

让我给你看一个(特意简化的)例子来说明这个过程。让我们想象一下,我们的应用程序中有两个支付提供商。其中一个将用户重定向到他/她可以进行支付的另一个页面,另一个在现有的URL中打开模态窗口。在这两种情况下,我们都希望在付款后出现一个感谢页面或错误页面。

// redirects to the payment page (like redirectpayment.com/payment)
// once payment is accepted user is redirected to "returnUrl?status={paymentStatus}"
RedirectPayment.makePayment(cartId, returnUrl)

// opens the modal window with payment
// once the payment is done modal window is being closed and "afterPayment" called
ModalPayment.pay(cartId, afterPayment(paymentStatusCode))

让我们总结一下其中的区别。

  • 支付功能的名称不同
  • RedirectPayment ModalPayment 接受URL,而 接受一个回调函数。
  • 对于RedirectPayment ,我们不能依靠应用程序的状态,而将数据从支付状态之前传递到之后。
  • 从文档中我们还知道,他们发送不同的状态代码和有效载荷

似乎我们在处理完全不同的事情,对吗?如果你仔细看一下,你会发现也有很多相似之处

  • 两种支付方式都只使用一个函数来进行支付
  • 两种支付方法都返回支付状态代码
  • 两者都需要一个cartId ,以了解支付细节

让我们利用这些相似之处来创建我们的抽象,然后看看我们是否可以为它们各自写一个适配器,其中有不同的部分。

根据我们的列表,我们需要的是一个接受cartId ,并返回状态代码的单一方法。

AbstractedPayment.pay(cartId: string): status: string

一旦我们有了这个接口,让我们看看是否可以为我们的每一种支付方式写一个适配器!

有了ModalPayment ,这就小菜一碟了。

AbstractedModalPayment.pay = function (cartId) {
  function afterPayment(paymentStatusCode) {
    if (paymentStatusCode === 'ACCEPTED') {
      window.location.href = " ";
    } else {
      window.location.href = "http://www.myapp.com/payment-error";
    }
  }
  ModalPayment.pay(cartId, afterPayment)
}

RedirectPayment 但有了这个接口后,我们就可以为我们的支付方式编写一个适配器了。因为有两个URL,我们要把用户重定向到它们,而我们只能把其中一个提供给 方法,我们显然不能在不编写 函数以外的代码的情况下处理其支付。我们需要弄清楚我们的抽象应该如何改变以满足这种情况。RedirectPayment.pay() AbstractedModalPayment.pay()

调整抽象概念

因为我们要根据返回的状态代码进行重定向,这意味着我们不能只依赖抽象的pay 方法。我们需要另一个函数,它将在付款后被调用,并根据状态代码重定向到适当的页面。

让我们看看我们是否可以通过调整我们的抽象方法来实现这个用例,以暴露额外的afterPayment(statusCode) 函数。我们已经知道这对AbstractedModalPayment ,因为ModalPayment.pay() 也需要同样的函数,所以我将只关注抽象化RedirectPayment

AbstractedRedirectPayment = {
   pay: function (cartId) {
     // we will run "afterPayment" on "after-payment" page
     RedirectPayment.pay('http://www.myapp.com/after-payment')
   },
   afterPayment: function(paymentStatusCode) {
     if (paymentStatusCode === 'SUCCESS') {
       window.location.href = "http://www.myapp.com/thank-you";
     } else {
       window.location.href = "http://www.myapp.com/payment-error";
     }
   }
}

看来我们终于为我们的支付方法找到了正确的抽象了!这就是为什么我们要把支付方法的抽象化。一旦我们想通了,总是值得思考一段时间,是否有什么东西是只针对这两种支付方式的(而不是整个系列的),以及我们是否可以将它们概括为对所有的支付方式都有用。你挑战你的抽象的接口越多越好,但不要为之痴迷。

总结

保持应用程序的可维护性和可扩展性的最简单方法之一是使其各部分相互独立。在这个迷你系列中,我想与你分享一些关于如何实现的想法,但这只是冰山一角。根据我的经验,制作一个模块化的应用程序,并在各模块之间进行良好的沟通,并适当抽象出第三方,是非常困难的。它可能看起来只是一个简单的例子,但在现实世界的应用中,你将不得不整合许多服务,犯许多错误,并多次调整你的方法,最终学会如何使事情变得正确。而这是很好的!对我来说,我们可以不断地学习新的东西,改善我们未来的选择,这就是我们的工作最令人兴奋的地方!

Learn Vue.js 3 With Vue School

"Teleport - Vue 3的新功能 新的Vue 3应用初始化代码的好处"