Java EE 应用程序的单体到微服务重构

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

你有没有想过如何将现有的 Java EE 单体应用程序重构为基于微服务的应用程序?

此博客解释了一个简单的购物车示例如何转换为基于微服务的应用程序,以及围绕它的一些问题。整体和基于微服务的应用程序的完整代码库位于: github.com/arun-gupta/microservices

继续阅读以获得完整的荣耀!

Java EE 单体

java ee 整体应用程序通常被定义为 war 或 ear archive。应用程序的全部功能都打包在一个单元中。例如,在线购物车可能包含用户、目录和订单功能。所有网页都在应用程序的根目录中,所有相应的 java 类都在 web-inf/classes 目录中,资源在 web-inf/classes/meta-inf 目录中。

让我们假设你的单体应用不是设计成一个 分布式的大泥球 ,并且应用程序是按照良好的软件架构构建的。一些常见的规则是:

  • 关注点分离,可能使用模型-视图-控制器
  • 高内聚低耦合使用定义明确的api
  • 不要重复自己(干)
  • interfaces/apis 和 implementations 是分开的,并且遵循 demeter 法则 。类不直接调用其他类,因为它们恰好在同一个存档中
  • 使用域驱动设计将与域/组件相关的对象保持在一起
  • yagni 否则你将不需要它:不要构建你现在不需要的东西

下面是一个简单的购物车整体战争档案的样子:

这个单体应用程序有:

  • 网页,例如 .xhtml 文件,用于用户、目录和订单组件,打包在存档的根目录中。跨不同网页共享的任何 css 和 javascript 资源也与这些页面打包在一起。
  • 这三个组件的类位于 web-inf/classes 目录中的单独包中。多个类使用的任何实用程序/公共类也都打包在这里。
  • 每个组件的配置文件都打包在 web-inf/classes/meta-inf 目录中。应用程序的任何配置文件,例如分别用于连接和填充数据存储的 persistence.xml load.sql ,也打包在这里。

具有知名架构、IDE友好、易于共享、简化测试、易于部署等 优点 。但也有 一些缺点, 例如敏捷性有限、持续交付障碍、技术堆栈“卡住”、技术债务不断增加等。

尽管如今微服务风靡一时,但单体应用也不错。即使是那些不为你工作的人也可能不会从迁移到微服务中获益太多,或者不会立即获益。其他方法,例如更好的软件工程和架构,可能会有所帮助。微服务既不是 免费午餐 也不是灵丹妙药,需要大量投资才能成功,例如服务发现、服务复制、服务监控、容器、PaaS、弹性等等。

甚至不要考虑微服务,除非您的系统太复杂而无法作为整体进行管理。

微服务溢价

Java EE 的微服务架构

好吧,我听说过所有这些,但想看看之前/之后,即单体代码库和重构的微服务代码库是什么样子的。

首先,让我们看一下整体架构:

该架构的关键部分是:

  • 应用程序应该在功能上进行分解,其中用户、订单和目录组件被打包为单独的 war 文件。每个 war 文件都应包含该组件所需的相关网页 ( #15 )、类和配置文件。
    • java ee 用于实现每个组件,但没有对堆栈的长期承诺,因为不同的组件使用定义良好的 api( #14 )相互通信。
    • 该组件中的不同类属于同一个域,因此代码更易于编写和维护。底层堆栈也可以改变,可能将技术债务保持在最低限度。
  • 每个档案馆都有自己的数据库,即不共享数据存储。这允许每个微服务发展并选择最合适的任何类型的数据存储——关系、nosql、平面文件、内存或其他类型。
  • 每个组件都将在服务注册表中注册。这是必需的,因为每个服务的多个无状态实例可能在给定时间运行,并且它们的确切端点位置只有在运行时才知道( #17 )。 netflix eureka etcd zookeeper 是这个空间中的一些选项( 更多详细信息 )。
  • 如果组件需要相互通信(这很常见),那么它们将使用预定义的 API 来实现。用于同步的 rest 或用于异步通信的 pub/sub 是实现此目的的常用方法。在我们的例子中,订单组件发现用户和目录服务并使用 rest api 与它们对话。
  • 应用程序的客户端交互在另一个应用程序中定义,在我们的例子中是购物车 ui。此应用程序主要从服务注册表中发现服务并将它们组合在一起。它应该主要是一个愚蠢的代理,调用不同组件的 ui 页面来显示界面( #18 )。可以通过提供标准的 css/javascript 资源来实现通用的外观。

这个应用程序相当简单,但至少突出了一些基本的架构差异。

整体与微服务

下面比较了单体和基于微服务的应用程序的一些统计数据:

单体应用程序的代码库位于: github.com/arun-gupta/microservices/tree/master/monolith/everest

启用微服务的应用程序的代码库位于: github.com/arun-gupta/microservices/tree/master/microservice

问题和待办事项

以下是在将整体重构为基于微服务的应用程序期间遇到的问题和待办事项:

  • java ee 已经启用了使用 ear 打包的应用程序的功能分解。应用程序的每个组件都可以打包为 war 文件并捆绑在 ear 文件中。他们甚至可以那样共享资源。现在这不是真正的微服务方式,但这可能是让您入门的过渡步骤。但是,请注意 @flowscoped bean 未在耳朵中正确激活 ( wfly-4565 )。
  • 使用jsf资源库模板提取所有模板文件。
    • 所有网页目前都在 everest 模块中,但它们应该存在于每个组件中( #15 )。
    • 资源库模板应该部署在一个中央位置,而不是与每个 war 文件一起打包 ( #16 )。
  • 将单一数据库分解为多个数据库需要为每个应用程序单独的 persistence.xml 和 ddl/dml 脚本。同样,需要相应地创建迁移脚本,例如使用 flyway。
  • 必须为需要由另一个组件访问的所有组件创建一个休息接口。
  • ui 仍然在单个 web 应用程序中。这应该包含在分解的战争( #15 )中,然后在哑代理中再次组合。这闻起来像 portlet 吗?
  • 在 paas 中部署多个 war 文件( #12
  • 每个微服务都应该可以轻松部署在容器中( #6

这是整体应用程序的完整类列表:


 ./target/classes/org/javaee7/wildfly/samples/everest/cart/cart.class
./target/classes/org/javaee7/wildfly/samples/everest/cart/cartitem.class
./target/classes/org/javaee7/wildfly/samples/everest/catalog/catalogitem.class
./target/classes/org/javaee7/wildfly/samples/everest/catalog/catalogitembean.class
./target/classes/org/javaee7/wildfly/samples/everest/catalog/catalogitemtype.class
./target/classes/org/javaee7/wildfly/samples/everest/checkout/order.class
./target/classes/org/javaee7/wildfly/samples/everest/checkout/orderbean.class
./target/classes/org/javaee7/wildfly/samples/everest/checkout/orderitem.class
./target/classes/org/javaee7/wildfly/samples/everest/checkout/shipping.class
./target/classes/org/javaee7/wildfly/samples/everest/uzer/uzer.class
./target/classes/org/javaee7/wildfly/samples/everest/uzer/uzerbean.class
./target/classes/org/javaee7/wildfly/samples/everest/uzer/uzeritem.class

这是基于微服务的应用程序的完整类列表:


 ./target/classes/org/javaee7/wildfly/samples/everest/cart/cart.class
./target/classes/org/javaee7/wildfly/samples/everest/cart/cartitem.class
./target/classes/org/javaee7/wildfly/samples/everest/catalog/catalogitem.class
./target/classes/org/javaee7/wildfly/samples/everest/catalog/catalogitembean.class
./target/classes/org/javaee7/wildfly/samples/everest/catalog/catalogitemtype.class
./target/classes/org/javaee7/wildfly/samples/everest/checkout/order.class
./target/classes/org/javaee7/wildfly/samples/everest/checkout/orderbean.class
./target/classes/org/javaee7/wildfly/samples/everest/checkout/orderitem.class
./target/classes/org/javaee7/wildfly/samples/everest/checkout/shipping.class
./target/classes/org/javaee7/wildfly/samples/everest/uzer/uzer.class
./target/classes/org/javaee7/wildfly/samples/everest/uzer/uzerbean.class
./target/classes/org/javaee7/wildfly/samples/everest/uzer/uzeritem.class

同样,完整的代码库位于 github.com/arun-gupta/microservices

未来话题

本系列的一些未来主题将是:

  • 微服务需要容器吗?
  • 如何使用容器部署多个微服务?
  • 如何轻松监控所有这些服务?
  • a/b测试
  • 使用微服务和容器进行持续部署

你还想看什么?