Java架构:一文读懂微服务架构的重构策略

你颇有可能正在处理大型复杂的单体应用程序,天天开发和部署应用程序的经历都很缓慢并且很痛苦。微服务看起来很是适合你的应用程序,但它也更像是一项高不可攀的必杀技。如何才能走上微服务架构的道路?下面将介绍一些策略,帮你摆脱单体地狱,而无须从头开始重写你的应用程序。前端

经过开发所谓的绞杀者应用程序(strangler application),能够逐步将单体架构转换为微服务架构。绞杀者应用程序的想法来自绞杀式藤蔓,这些藤蔓在雨林中生长,它们包围绕树木生成,甚至有时会杀死树木。绞杀者应用程序是一个由微服务组成的新应用程序,经过将新功能做为服务,并逐步从单体应用程序中提取服务来实现。随着时间的推移,当绞杀者应用程序实现愈来愈多的功能时,它会缩小并最终消灭单体应用程序。开发绞杀者应用程序的一个重要好处是,与宇宙大爆炸式的完全重写不一样,它能够马上落地,更快为企业提供价值。数据库

有三种主要策略能够实现对单体的“绞杀”,并逐步用微服务替换之:后端

1) 将新功能实现为服务。 2)隔隔表现层和后端。 3) 经过将功能提取到服务中来分解单体。架构

第一种策略阻止了单体的发展。它一般是一种快速展现微服务价值的方法,有助于让迁移和重构的工做得到公司内部各个层面支持。另外两种策略打破了单体。在重构单体时,你有时可能会使用第二种策略,但你确定会使用第三种策略,由于它能实现将功能从单体迁移到绞杀者应用程序中。app

下面让咱们来看一看这些策略。框架

1.将新功能实现为服务微服务

“挖坑法则”(The Law of Holes)指出:若是你发现本身已经陷入了困境,就不要再给本身继续挖坑了。当你的单体应用变得没法管理时,这是一个很好的可供参考的建议。换句话说,若是你有一个庞大的、复杂的单体应用程序,请不要经过向单体添加代码来实现新功能。这将使你的单体变得更庞大,更难以管理。相反,你应该将新功能实现为服务。性能

这是开始将单体应用程序迁移到微服务架构的好方法。它下降了单体的生长速度,加速了新功能的开发(由于是在全新的代码库中进行开发),还能快速展现采用微服务架构的价值。测试

把新的服务与单体集成代理

图 1显示了将新功能实现为服务后的应用程序架构。除了新服务和单体外,该架构还包括另外两个将服务集成到应用程序中的元素:

■ API Gateway:将对新功能的请求路由到新服务,并将遗留请求路由到单体。

■ 集成胶水代码:将服务与单体结合。它使服务可以访问单体所拥有的数据,并可以调用单体实现的功能。

集成胶水的代码不是一个独立组件。相反,它由单体中的适配器和使用一个或多个进程间通讯机制的服务组成。

什么时候把新功能实现为服务

理想状况下,你应该在绞杀者应用程序中而不是在单体中实现每一个新功能。你将实现新功能做为新服务或做为现有服务的一部分。这样你就能够避免和单体代码库打交道。不幸的是,并不是每一个新功能均可以做为服务实现。

由于微服务架构的本质是一组围绕业务功能组织的松耦合服务。例如,某个功能可能过小而没法成为有意义的服务。例如,你可能只须要向现有类添加一些字段和方法。或者新功能可能与单体中的代码紧耦合。若是你尝试将此类功能实现为服务,则一般会发现,因为过多的进程间通讯而致使性能降低。你可能还会遇到数据一致性的问题。若是新功能没法做为服务实现,则解决方案一般是首先在单体中实现新功能。以后,你能够将该功能以及其余相关功能提取到本身的服务中。

以服务的方式实现新功能,能够加速这些功能的开发。这是快速展现微服务架构价值的好方法。它还可以下降单体的增加速度。但最终,你须要使用另外两种策略来分解单体。你须要经过将单体中的功能提取到服务,从而将单体中的功能迁移到绞杀者应用程序。你也能够经过水平分割单体架构来提升开发速度。咱们来看看如何作到这一点。

2.隔离表现层与后端 缩小单体应用程序的一个策略是将表现层与业务逻辑和数据访问层分开。典型的企业应用程序包含如下各层:

■ 表现逻辑层:它由处理 HTTP 请求的模块组成,并生成实现 Web UI 的 HTML 页面。在具备复杂用户界面的应用程序中,表现层一般包含大量代码。

■ 业务逻辑层:由实现业务规则的模块组成,这些模块在企业应用程序中可能很复杂。

■ 数据访问逻辑层:包含访问基础设施服务(如数据库和消息代理)的模块。 表现逻辑层与业务和数据访问逻辑层之间一般存在清晰的边界。业务层具备粗粒度 API,由一个或多个封装业务逻辑的门面(Facade)组成。这个 API 是一个天然的接缝,你能够沿着它将单体分红两个较小的应用程序,如图 2 所示。

一个应用程序包含表现层,另外一个包含业务和数据访问逻辑层。分割后,表现逻辑应用程序对业务逻辑应用程序进行远程调用。

以这种方式拆分单体应用有两个主要好处。它使你可以彼此独立地开发、部署和扩展这两个应用程序。特别是,它容许表现层开发人员快速迭代用户界面并轻松执行A/B测试,而无须部署后端。这种方法的另外一个好处是它公开了业务逻辑的一组远程API,能够被稍后开发的微服务调用。

但这种策略只是部分解决方案。极可能至少有一个或两个最终的应用程序仍然是一个难以管理的单体。你须要使用第三种策略将单体替换为服务。

3.提取业务能力到服务中 将新功能实现为服务,并从后端拆分出前端Web应用程序并不会让你抵达胜利的彼岸。你仍将最终在单体代码中进行大量开发。若是你但愿显著改进应用程序的架构并提升开发速度,则须要经过逐步将业务功能从单体迁移到服务来拆分单体应用。当你使用此策略时,随着时间推移,服务实现的业务功能数量会增长,而单体会逐渐缩小。

你想要提取到服务中的功能是对单体应用自上而下的一个“垂直切片”。该切片包含如下内容:

■ 实现API端点的入站适配器。 ■ 领域逻辑。 ■ 出站适配器,例如数据库访问逻辑。 ■ 单体的数据库模式。

如图 3 所示,此代码从单体中提取并移至独立服务中。API Gateway 将调用提取的业务功能的请求路由到该服务,并将其余请求路由到单体。单体和服务经过集成胶水代码进行协做。集成胶水由服务中的适配器和使用一个或多个进程间通讯机制的单体组成。

提取服务具备挑战性。你须要肯定如何将单体的领域模型分红两个独立的领域模型,其中一个模型成为服务的领域模型。你须要打破对象引用等依赖。你甚至可能须要拆分类,以将功能移动到服务中。对了,你还须要重构数据库。

提取服务一般很耗时,尤为是当单体的代码库很混乱时。所以,你须要仔细考虑要提取的服务。应当重点关注重构那些可以提供不少价值的应用程序部分。在提取服务以前,问问本身这样作的好处是什么。

例如,提取一项实现对业务相当重要且不断发展的功能的服务是值得的。若是没有太多的好处,那么在提取服务方面投入精力是没有价值的。在本节的后面部分,我将介绍一些用于肯定服务提取范围和时间的策略。但首先让咱们更详细地了解一下在提取服务时将面临的一些挑战以及解决这些挑战的方法。

提取服务时会遇到如下这些挑战:

■ 拆解领域模型。 ■ 重构数据库。

拆解领域模型

为了提取服务,你须要从单体的领域模型中提取服务相关的领域模型。你须要进行大动做来拆分领域模型。你将遇到的一个挑战是消除跨越服务边界的对象引用。保留在单体中的类可能会引用已移动到服务的类,反之亦然。例如,想象一下,如图 4 所示,你提取了Order Service,其Order类引用了单体的Restaurant类。由于服务实例一般是一个进程,因此让对象引用跨越服务边界是没有意义的。你须要消除这种类型的对象引用。

解决此问题的一个好方法是根据DDD聚合进行思考。聚合使用主键而不是对象引用相互引用。所以,你能够将 Order 和 Restaurant 类视为聚合,如图5所示,将Order类中对 Restaurant 的引用替换为存储主键值的restaurantId 字段。

使用主键替换对象引用的一个问题是,虽然这是对类的一个小改动,但它可能会对指望对象引用的类的客户端产生很大的影响。在本节的后面部分,我将介绍如何经过在服务和单体之间复制数据来减小更改的范围。例如,Delivery Service能够定义一个Restaurant类,后者是单体中Restaurant 类的复制品。

提取服务一般比将整个类移动到服务中的工做量要大得多。拆分领域模型面临的更大挑战是提取嵌入在具备其余职责的类中的功能。这个问题常常出如今具备过多职责的上帝类(God Class)中。例如,Order 类是FTGO应用程序中的上帝类之一。它实现了多种业务功能,包括订单管理、送餐管理等。Delivery 实体会实现以前与Order类中的其余功能捆绑在一块儿的送餐管理功能。

重构数据库

拆分领域模型不只仅涉及更改代码。领域模型中的许多类都是在数据库中持久化保存的。它们的字段映射到具体的数据库模式。所以,当你从单体中提取服务时,你也会移动数据。你须要将表从单体的数据库移动到服务的数据库。

此外,拆分实体时,须要拆分相应的数据库表并将新表移动到服务中。例如,在将送餐管理提取到服务中时,你须要拆分Order实体并提取出一个Delivery实体。在数据库级别,你要拆分ORDERS表并定义新的DELIVERY表。而后,将DELIVERY表移动到该服务。

复制数据以免更普遍的更改

如上所述,提取服务须要你对单体的领域模型作出更改。例如,使用主键和拆分类替换对象引用。这些类型的更改可能会影响代码库,并要求你对单体各个受影响的部分进行普遍的更改。例如,若是拆分Order实体并提取Delivery实体,则必须更改代码中引用被移动字段而受影响的每一个部分。进行这些改变可能会很是耗时,而且可能成为打破单体的巨大障碍。

延迟并可能避免进行这些昂贵更改的一种好方法是使用相似于《数据库重构》一书中描述的方法。重构数据库的一个主要障碍是更改该数据库的全部客户端以使用新模式。本书中提出的解决方案是在过渡期内保留原模式,并使用触发器在原模式和新模式间同步。而后,你能够将客户端从旧模式迁移到新模式。

从单体中提取服务时,咱们可使用相似的方法。例如,在提取Delivery实体时,咱们将Order实体在过渡期内大部分保持不变。如图6所示,咱们将与交付相关的字段设置为只读,并经过将数据从Delivery Service复制回单体来使其保持最新。所以,咱们只须要在单体的代码中找到更新这些字段的位置,并更改它们为调用新的Delivery Service便可。

经过从Delivery Service复制数据来保留Order实体的结构,能够显著减小咱们须要当即完成的工做量。随着时间的推移,咱们能够将使用与交付相关的Order实体字段或ORDERS表列的代码迁移到Delivery Service。更重要的是,咱们可能永远不须要在单体中作出改变。若是随后将该代码提取到服务中,则该服务能够访问DeliveryService。

肯定提取何种服务以及什么时候提取

正如我所提到的,拆解单体是耗时的。它分散了实施新功能的人力资源。所以,你必须仔细肯定提取服务的顺序。你须要专一于提取可以带来最大收益的服务。更重要的是,你但愿不断向业务部门展现迁移到微服务架构的价值。

在任何旅程中,了解你要去的地方相当重要。开始迁移到微服务的好方法是使用时间框架来定义工做。你应该花费很短的时间,例如几周,集思广益讨论理想架构并定义一组服务。这将为你提供一个目标。可是,重要的是要记住,这种架构并不是一成不变。当你分解单体并得到经验后,你应该应用你所得到的经验对重构计划及时作出调整。

一旦肯定了目标,下一步就是开始拆分单体结构。可使用几种不一样的策略来肯定提取服务的顺序。

一种策略是有效地冻结单体架构的开发并按需提取服务。你能够提取必要的服务并进行更改,而不是在单体中实现功能或修复错误。这种方法的一个好处是它会迫使你打破单体。一个弊端是服务的提取是由短时间需求而不是长期需求驱动的。例如,即便你对系统中相对稳定的部分进行了少许更改,也须要你提取服务。所以,你作的大量工做可能只能换来较小的收益。

另外一种策略是更有计划的方法,你能够根据提取应用程序模块得到的预期收益,对应用程序的模块进行排名。提取服务有益的缘由有如下几点:

■ 加速开发:若是你的应用程序的路线图代表应用程序的特定部分将在明年进行大量开发,那么将其转换为服务可加速开发。

■ 解决性能、可扩展性或可靠性问题:若是应用程序的特定部分存在性能、可扩展性问题或不可靠,那么将其转换为服务是有价值的。

■ 容许提取其余一些服务:因为模块之间的依赖关系,有时提取一个服务会简化另外一个服务的提取。 你可使用这些条件将重构任务添加到应用程序的“待办事项”中,并按预期收益排名。这种方法的好处在于它更具战略性,而且更符合业务需求。在作 Sprint 的计划时,你能够肯定实现功能或提取服务哪一个更有价值。

喜欢能够+关注+转发+喜欢哦

相关文章
相关标签/搜索