JHipster-全栈开发-全-
JHipster 全栈开发(全)
原文:
zh.annas-archive.org/md5/1cf2727d6ba6d885f78c31bfa2415676译者:飞龙
前言
本书,使用 JHipster 进行全栈开发,旨在解决全栈开发者今天面临的以下挑战:
-
有大量的技术和选项需要学习
-
客户需求增加,因此上市时间变得更加紧迫
-
客户端框架变得复杂,难以集成
-
技术和概念之间的集成如此之多,以至于大多数新手甚至熟练的开发者都会感到不知所措
JHipster 为开发者提供了一个平台,使他们能够轻松地从零开始创建 Web 应用和微服务,无需花费大量时间来连接一切和集成技术。这为开发者节省了大量时间,使他们能够真正专注于解决方案,而不是花费时间学习和编写样板代码。JHipster 将帮助新手和经验丰富的开发者从第一天起就提高生产力。这就像与整个社区进行结对编程。
本书将带您从零开始,成为全栈开发的英雄。您将学习如何从头开始使用 JHipster 创建复杂的、生产就绪的 Spring Boot 和 Angular Web 应用,并将继续在云服务上开发部署功能和业务逻辑。您还将了解微服务,以及如何使用 JHipster 将单体应用转换为随着其发展而演变的微服务架构。此外,您还将学习如何利用 JHipster 中引入的新 React 支持,以及来自 JHipster 社区和核心开发团队的各项最佳实践和建议。
本书面向的对象
任何对构建 Java Web 应用有基本了解,并对 Spring 和 Angular/React 有基本接触的人,都可以通过使用本书学习如何使用 JHipster 进行前沿的全栈开发,或者通过减少样板代码和掌握新技术来提高他们的生产力。读者可以大致分为以下几类:
-
想要减少编写的样板代码并节省时间的全栈 Web 应用开发者,尤其是对于绿色项目。
-
想要学习使用 Angular 或 React 进行全栈开发的后端开发者
-
想要学习微服务开发的全栈开发者
-
想要快速启动全栈 Web 应用或微服务开发的开发者
-
想要快速原型化 Web 应用或微服务的开发者
本书涵盖的内容
第一章,现代 Web 应用开发简介,介绍了两种广泛使用的全栈 Web 应用开发架构。它还概述了全栈 Web 应用开发中常见的一些挑战。
第二章,开始使用 JHipster,介绍了 JHipster 平台。它还将为读者提供一个关于 JHipster 提供的不同服务器端、客户端和数据库技术选项的简要概述。本章还将提供安装和使用 JHipster 及其支持的各个工具和选项的说明。
第三章,使用 JHipster 构建单体 Web 应用程序,指导用户从头开始使用 JHipster 创建生产就绪的 Spring boot 和 Angular Web 应用程序,并将读者带入生成的代码、屏幕和概念。
第四章,使用 JHipster 领域语言进行实体建模,向读者介绍了 JHipster 领域语言(JDL),并教授如何使用实体建模和 JDL 以及 JDL studio 创建实体来构建业务逻辑。
第五章,定制和进一步开发,指导读者进一步开发生成的应用程序。它还将教授读者更多关于使用 Angular、Bootstrap、Spring Security、Spring MVC REST 和 Spring Data 等技术的方法。
第六章,测试和持续集成,指导读者进行测试并使用 Jenkins 设置持续集成管道。
第七章,进入生产阶段,展示了读者如何使用 Docker 以及如何构建和打包应用程序以用于生产。它还将介绍 JHipster 支持的某些生产云部署选项。
第八章,微服务服务器端技术简介,概述了 JHipster 微服务堆栈中可用的不同选项。
第九章,使用 JHipster 构建微服务,指导读者将 JHipster 单体 Web 应用程序转换为具有网关、注册表、监控控制台和多个微服务的完整微服务架构。它还将指导读者了解生成的代码和组件,如 JHipster 注册表、JHipster 控制台、API 网关和 JWT。
第十章,与微服务一起工作,指导读者在本地运行生成的应用程序并使用 JHipster 领域语言为微服务架构创建领域实体。
第十一章,使用 Docker Compose 部署,向读者介绍了微服务的先进本地和云部署选项。它还将指导用户使用 Docker Compose 和 JHipster 进行生成的微服务堆栈的本地部署和测试。
第十二章,使用 Kubernetes 在云中部署,指导用户使用 Kubernetes 和 JHipster 将生成的微服务堆栈部署到 Google 云。
第十三章,使用 React 进行客户端开发,使用 JHipster 生成应用程序,在客户端使用 React 而不是 Angular。
第十四章,使用 JHipster 的最佳实践,总结了读者迄今为止所学的内容,并将建议最佳实践和下一步骤来利用所学的技能。
为了充分利用本书
为了充分利用本书,您需要了解以下技术的基础知识:
-
网络技术(HTML、JavaScript 和 CSS)
-
Java 8
-
Spring 框架的基础知识
-
对 SQL 数据库的基本理解
-
构建工具(Maven 或 Gradle)
-
npm 或 Yarn
如果您熟悉使用 Docker 和 Kubernetes 等技术,这将使您更容易掌握一些章节。
您还需要安装 JDK8、Git、Docker 和 NodeJS;您喜欢的网络浏览器;一个终端应用程序;以及您喜欢的代码编辑器/IDE。
下载示例代码文件
您可以从www.packtpub.com的账户下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
您可以通过以下步骤下载代码文件:
-
在 www.packtpub.com 登录或注册。
-
选择 SUPPORT 标签。
-
点击代码下载与勘误。
-
在搜索框中输入本书的名称,并遵循屏幕上的说明。
一旦文件下载完成,请确保使用最新版本的软件解压缩或提取文件夹:
-
WinRAR/7-Zip for Windows
-
Zipeg/iZip/UnRarX for Mac
-
7-Zip/PeaZip for Linux
本书代码包托管在 GitHub 上,地址为github.com/PacktPublishing/Full-Stack-Development-with-JHipster。如果代码有更新,它将在现有的 GitHub 仓库中更新。
我们还有其他丰富的图书和视频的代码包,可在github.com/PacktPublishing/找到。查看它们!
使用的约定
本书使用了多种文本约定。
CodeInText:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“在后台,修改ProductOrderService.java中的save方法以创建发票和运货单,并为ProductOrder保存它们。”
代码块设置如下:
entity Product {
name String required
description String
price BigDecimal required min(0)
size Size required
image ImageBlob
}
enum Size {
S, M, L, XL, XXL
}
entity ProductCategory {
name String required
description String
}
当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:
entity ProductOrder {
placedDate Instant required
status OrderStatus required
invoiceId Long
code String required
}
任何命令行输入或输出都按照以下方式编写:
> cd invoice
> ./gradlew
粗体:表示新术语、重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词在文本中显示如下。以下是一个示例:“您可以通过 Gateway 应用程序进行替代测试。登录到我们的 Gateway 应用程序,然后导航到 Administration | Gateway。”
警告或重要提示看起来像这样。
小贴士和技巧看起来像这样。
联系我们
我们始终欢迎读者的反馈。
一般反馈:请将电子邮件发送至 feedback@packtpub.com,并在邮件主题中提及书籍标题。如果您对本书的任何方面有疑问,请通过 questions@packtpub.com 发送电子邮件给我们。
勘误:尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将不胜感激,如果您能向我们报告这个错误。请访问 www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入详细信息。
盗版:如果您在互联网上以任何形式发现我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过 copyright@packtpub.com 联系我们,并提供材料的链接。
如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问 authors.packtpub.com.
评论
请留下评论。一旦您阅读并使用了这本书,为何不在您购买它的网站上留下评论呢?潜在读者可以查看并使用您的客观意见来做出购买决定,Packt 公司可以了解您对我们产品的看法,我们的作者也可以看到他们对书籍的反馈。谢谢!
如需了解更多关于 Packt 的信息,请访问 packtpub.com.
第一章:现代 Web 应用开发简介
根据 2017 年 Stack Overflow 开发者调查(insights.stackoverflow.com/survey/2017#developer-profile-specific-developer-types),全栈 Web 开发者是最受欢迎的开发者头衔。软件行业将全栈开发者定义为能够在应用栈的不同领域工作的开发者。术语栈指的是构成应用的不同组件和工具。
在 Web 应用开发方面,技术栈可以大致分为两个领域——前端和后端技术栈或客户端和服务器端技术栈。前端通常指的是负责渲染用户界面的部分,而后端则指的是负责业务逻辑、数据库交互、用户认证、服务器配置等部分。全栈 Java Web 应用开发者需要在前端和后端技术方面工作,从编写用户界面的 HTML/JavaScript 到编写业务逻辑的 Java 类文件和数据库操作的 SQL 查询。
随着软件架构的不断演变,全栈 Web 开发者需要掌握的技术范围大大增加。仅仅能够编写 HTML 和 JavaScript 来构建用户界面已经不够了,我们还需要了解客户端框架,如 Angular、React、VueJS 等。仅仅精通企业 Java 和 SQL 也不够,我们还需要了解服务器端框架,如 Spring、Hibernate、Play 等。
在本章中,我们将介绍以下主题:
-
现代全栈 Web 开发
-
Web 架构模式
-
选择合适的模式
现代全栈 Web 开发
如果我们要开始讨论全栈开发者的生活,那将足以写成一整本书——所以让我们留到另一天再说。
让我们看看一个全栈 Java Web 应用的用例,看看其中涉及的内容。
让我们以开发一个典型 Java Web 应用的用户管理模块为例。假设你会为所有代码编写单元测试用例,所以我们在这里不会详细说明:
-
你会从设计该功能的架构开始。你会决定要使用的插件和框架、要遵循的模式等。
-
你将根据使用的数据库技术对特征进行领域模型建模。
-
然后,你会创建服务器端代码和数据库查询,以持久化和从数据库中检索数据。
-
数据准备好后,你会实现任何业务逻辑的服务器端代码。
-
然后,你会实现一个 API,可以通过 HTTP 连接提供数据。
-
你将为 API 编写集成测试。
-
现在,由于后端已经准备好了,你将开始用 JavaScript 或类似的技术编写前端代码。
-
你将编写客户端服务以从后端 API 获取数据。
-
你将编写客户端组件以在网页上显示数据。
-
你将根据提供的设计构建页面并对其进行样式设计。
-
你将为网页编写自动化的端到端测试。
-
这还没有完成。一旦你测试了一切在本地都能正常工作,你将创建拉取请求或将代码检查到所使用的版本控制系统中。
-
你将等待持续集成过程来验证一切,并修复任何损坏的部分。
-
一切都绿灯亮起,代码被接受后,通常你将开始将这个功能部署到预发布或验收环境,无论是在本地还是在云服务提供商那里。如果是后者,你还需要熟悉所使用的云技术。你还需要根据需要升级数据库模式,并在需要时编写迁移脚本。
-
一旦功能被接受,你可能会负责以类似的方式将其部署到生产环境中,并在必要时解决出现的问题。在一些团队中,你可能会与其他团队成员交换步骤,这样你就可以部署你的同事开发的功能,同时他们部署你的功能。
-
你可能还需要与你的同事一起确保生产环境正常运行,包括数据库、虚拟机等等。
如你所见,这不是一件容易的任务。责任范围从客户端的样式表更新到在生产云服务中的虚拟机上运行数据库迁移脚本。如果你不够熟悉,这将是一项艰巨的任务,你很快就会迷失在众多框架、技术和设计模式的大海中。
全栈开发不是一件容易的事情。它需要花费大量的时间和精力来保持自己与软件开发多个领域的各种技术和模式同步。以下是一些你可能作为全栈 Java 开发者会遇到的一些常见问题:
-
客户端开发不再仅仅是编写纯 HTML 和 JavaScript 了。它正变得和服务器端开发一样复杂,包括构建工具、转译器、框架和模式。
-
在 JavaScript 的世界里,几乎每周都会出现一个新的框架,如果你是从 Java 背景过来的,这可能会让你感到非常压倒性。
-
容器技术如 Docker 革命性地改变了软件行业,但它们也引入了许多新的学习内容,需要跟踪,例如编排工具、容器管理工具等等。
-
云服务每天都在增长。为了保持同步,你需要熟悉它们的 API 和相关编排工具。
-
近年来,随着 Scala、Groovy、Kotlin 等 JVM 语言的引入,Java 服务器端技术也经历了重大转变,迫使你必须跟上它们的步伐。另一方面,服务器端框架变得越来越功能丰富,因此也变得更加复杂。
最重要的是确保所有这些在需要时能够良好协作的痛苦。这需要大量的配置、一些粘合代码和无数杯咖啡。
转换器是源到源的编译器。与传统编译器从源代码编译到二进制代码不同,转换器将一种类型的源代码编译成另一种类型的源代码。TypeScript 和 CoffeeScript 是这方面的优秀例子,它们都编译成 JavaScript。
在这里很容易迷失方向,这就是 JHipster 和 Spring Boot 等技术的用武之地,它们可以帮助我们。我们将在后面的章节中看到详细内容,但简而言之,它们通过提供移动部件之间的连接,让你只需专注于编写业务代码。JHipster 还通过提供部署和管理应用程序到各种云提供商的抽象来帮助。
Web 架构模式
全栈领域由于目前广泛使用的不同 Web 架构模式而变得更加复杂。今天广泛使用的 Web 应用程序架构模式可以大致分为两种——单体架构和微服务架构,后者是新兴的架构模式。
让我们详细看看以下内容:
-
单体架构
-
微服务架构
单体 Web 架构
单体架构是 Web 应用程序最常用的模式,因为它在开发和部署上的简单性。尽管实际的运动部件会因应用程序而异,但一般模式保持不变。一般来说,单体 Web 应用程序可能执行以下操作:
-
它可以支持不同的客户端,如桌面/移动浏览器和原生桌面/移动应用程序
-
它可以暴露 API 供第三方消费
-
它可以通过 REST/SOAP Web 服务或消息队列与其他应用程序集成
-
它可以处理 HTTP 请求,执行业务逻辑,访问数据库,并且可以与其他系统交换数据
-
它可以在诸如 Tomcat、JBoss 等 Web 应用程序容器上运行
-
它可以通过增加运行其上的机器的功率进行垂直扩展,或者通过在负载均衡器后面添加额外的实例进行水平扩展
REST(表征状态转移)依赖于无状态的、客户端-服务器、可缓存的通信协议。HTTP 是 REST 最常用的协议。它是一种轻量级架构风格,其中 RESTful HTTP 通信用于在客户端和服务器或两个系统之间传输数据。
SOAP(简单对象访问协议)是一种使用 HTTP 和 XML 的消息协议。它在 SOAP Web 服务中广泛用于在两个不同的系统之间传输数据。
典型的单体 Web 应用程序架构的例子如下:
让我们想象一个在线酒店预订系统,该系统从客户那里在线接收预订订单,验证房间可用性,验证支付选项,进行预订,并通知酒店。该应用程序由多个层和组件组成,包括客户端应用程序,它构建了一个丰富的用户界面,以及负责管理预订、验证支付、通知客户/酒店等的多其他后端组件。
应用程序将作为单个单体Web 应用程序存档(WAR)文件部署,在 Web 应用程序容器(如 Tomcat)上运行,并通过在充当负载均衡器的 Apache Web 服务器后面添加多个实例进行水平扩展。请看以下图表:

单体式 Web 应用程序架构的优点如下所述:
-
开发更简单,因为技术栈在所有层都是统一的。
-
测试更简单,因为整个应用程序捆绑在一个单独的包中,这使得运行集成和端到端测试更容易。
-
部署更简单、更快,因为你只需要担心一个包。
-
扩展更简单,因为你可以通过增加负载均衡器后面的实例数量来扩展。
-
维护应用程序需要较小的团队。
-
团队成员共享或多或少相同的技能集。
-
技术栈更简单,大多数情况下更容易学习。
-
初始开发更快,因此使上市时间更快。
-
需要更简单的基础设施。即使是一个简单的应用程序容器或 JVM 也足以运行应用程序。
单体式 Web 应用程序架构的缺点如下所述:
-
组件紧密耦合在一起,导致出现不希望出现的副作用,例如一个组件的更改可能导致另一个组件的回归,等等。
-
随着时间的推移变得复杂和庞大,导致开发周期缓慢。新功能开发将花费更多时间,由于紧密耦合,现有功能的重构将更加困难。
-
任何更改都需要重新部署整个应用程序。
-
由于模块紧密耦合,可靠性较低。服务中的一个小的错误可能会破坏整个应用程序。
-
由于整个应用程序需要迁移,新技术采用困难。大多数情况下,增量迁移是不可能的。因此,许多单体应用程序最终会拥有过时的技术栈。
-
关键服务无法单独扩展,导致资源使用增加,因为整个应用程序都需要扩展。
-
大型单体应用程序将具有较长的启动时间和较高的 CPU 和内存资源使用率。
-
团队之间的相互依赖性将更强,扩展团队将更具挑战性。
微服务架构
近年来,微服务架构得到了快速发展,由于其模块化和可扩展性,在 Web 应用开发中越来越受欢迎。微服务架构几乎可以提供我们在早期部分看到的单体架构的所有功能。此外,它还提供了许多更多功能和灵活性,因此通常被认为是复杂应用的更优选择。与单体架构不同,微服务架构很难进行一般化,因为它可能严重依赖于用例和实现。但它们确实有一些共同特征,通常如下:
-
微服务组件是松散耦合的。组件可以独立开发、测试、部署和扩展,而不会干扰其他组件。
-
组件不需要使用相同的技术栈进行开发。这意味着单个组件可以选择自己的技术栈和编程语言。
-
它们经常利用高级功能,如服务发现、断路器、负载均衡等。
-
微服务组件大多是轻量级的,并且执行特定的功能。例如,一个身份验证服务只会关心将用户验证到系统中。
-
通常具有广泛的监控和故障排除设置。
一个微服务 Web 应用架构的例子如下:
让我们想象一个庞大的在线电子商务系统,顾客可以浏览商品类别,维护收藏夹,将商品添加到购物车,下单并跟踪订单等。该系统具有库存管理、客户管理、多种支付方式、订单管理等。应用程序由多个模块和组件组成,包括一个 UI 网关应用程序,它构建了一个丰富的用户界面,并处理用户身份验证和负载均衡,以及负责管理库存、验证支付和管理订单的几个其他后端应用程序。它还具有性能监控和服务的自动故障转移。
应用程序将以多个可执行 WAR 文件的形式部署在云提供商托管的 Docker 容器中。请查看以下图表:

微服务 Web 应用架构的优势如下详细说明:
-
松散耦合的组件导致更好的隔离性,更容易测试和更快地启动。
-
更快的开发周期和更短的市场投放时间。新功能可以更快地构建,现有功能也可以轻松重构。
-
服务可以独立部署,这使得应用程序更可靠,补丁更容易应用。
-
问题,例如某个服务中的一个内存泄漏,将被隔离,因此不会导致整个应用程序崩溃。
-
技术采用更容易,组件可以在增量迁移中独立升级,使得每个组件都有不同的堆栈成为可能。
-
可以建立更复杂和高效的扩展模型。关键服务可以更有效地扩展。基础设施的使用效率更高。
-
单个组件启动更快,这使得并行化和提高整体启动速度成为可能。
-
团队之间的依赖性会减少。最适合敏捷团队。
微服务 Web 应用架构的缺点如下所述:
-
在整体堆栈方面更为复杂,因为不同的组件可能有不同的技术堆栈,迫使团队投入更多时间来跟上它们。
-
由于堆栈中有更多移动部件,执行端到端测试和集成测试变得困难。
-
整个应用程序部署起来更复杂,因为涉及到容器和虚拟化的复杂性。
-
扩展更高效,但设置扩展更复杂,因为它需要高级功能,如服务发现、DNS 路由等。
-
需要一个更大的团队来维护应用程序,因为有更多的组件和更多的技术涉及。
-
团队成员根据他们工作的组件共享不同的技能组合,这使得替换和知识共享更困难。
-
技术堆栈复杂,大多数时候更难学习。
-
初始开发时间会更高,使得上市时间变慢。
-
需要复杂的基础设施。通常将需要容器(Docker)和多个 JVM 或应用容器来运行。
选择正确的模式
在启动新项目时,如今选择架构模式总是很困难。有许多因素需要考虑,很容易被围绕不同模式和技术的炒作所迷惑(参见炒作驱动开发(blog.daftcode.pl/hype-driven-development-3469fc2e9b22)))。以下是关于何时选择单体 Web 应用架构而不是微服务架构以及相反的一些一般性指南。
选择单体架构的时机
以下列表可以作为选择单体架构而不是微服务架构的一般性指南。这不是一个确定的列表,但可以给出何时选择单体架构的思路:
-
当应用程序****范围小且定义明确,并且你确信应用程序在功能上不会大幅增长。例如,一个博客、一个简单的在线购物网站、一个简单的 CRUD 应用程序等。
-
当团队规模较小时,比如说少于八人(这不是一个硬性限制,而是一个实际限制)。
-
当团队的平均技能水平为新手或中级时。
-
当上市时间至关重要时。
-
当你不想在基础设施、监控等方面花费太多时。
-
当你的用户基础相对较小,并且你预计它们不会增长。例如,针对特定用户群体的企业应用。
在大多数实际用例中,单体架构就足够了。继续阅读下一节,了解何时应考虑微服务架构而不是单体架构。
选择微服务架构的时机
以下列表可以作为选择微服务架构的一般指南。这不是一个确定的列表,但可以给出何时选择微服务架构而不是单体架构的想法。请注意,与选择单体架构不同,这里的决策更为复杂,可能涉及以下许多点的交叉考虑:
-
当应用范围大且定义明确,并且你确信应用在功能上会大幅增长。例如,一个在线电子商务商店、社交媒体服务、面向大量用户的视频流媒体服务、API 提供商等。
-
当团队规模较大时,必须有足够的成员来独立有效地开发各个组件。
-
当团队的平均技能水平较高,并且团队成员对高级微服务模式有信心。
-
当上市时间不是关键。微服务架构在初期需要更多时间来正确实施。
-
当你准备在基础设施、监控等方面投入更多,以提高产品质量。
-
当你的用户基础庞大,并且你预计它们会增长。例如,面向全球用户的社交媒体应用。
虽然在大多数情况下单体架构就足够了,但在一开始就投资于微服务架构,当应用规模变得巨大时,将带来长期的好处。
关于这些架构模式,你可以参考articles.microservices.com/monolithic-vs-microservices-architecture-5c4848858f59。
摘要
到目前为止,我们已经看到了全栈开发是什么,比较了两种最突出的架构模式。我们还学习了单体和微服务架构的优缺点,这有助于我们为手头的用例选择正确的模式。
在下一章中,我们将深入探讨 JHipster 平台,并查看它提供的所有选项。我们还将学习如何安装 JHipster 以及设置我们的工具和开发环境。
第二章:开始使用 JHipster
JHipster 是一个开发平台,它能帮助你从零开始,迅速成为英雄!JHipster 可以帮助你快速创建美观的 Web 应用程序和复杂的微服务架构。JHipster 还提供了各种工具,使用业务实体进一步开发应用程序,并将其部署到各种云服务和平台。在核心上,JHipster 是一个 Yeoman 生成器,用于创建基于 Spring Boot 和 Angular/React 的应用程序。它可以创建单体架构以及具有所有功能开箱即用的微服务架构。
在本章中,我们将涵盖以下主题:
-
为什么使用 JHipster 以及它与传统开发方法相比如何帮助
-
JHipster 的目标是什么?
-
JHipster 中可用的各种服务器端和客户端技术选项
-
准备开发环境
-
JHipster 及其依赖项的安装
Yeoman (yeoman.io) 是一个脚手架工具,它可以帮助你创建代码生成器。你可以使用它,借助内置的模板引擎和工具,创建任何类型的应用程序生成器。
为什么选择 JHipster?
如果你想知道为什么你应该使用 JHipster,那么请想象以下场景。你被要求构建一个 Web 应用程序,比如说一个具有 Angular 前端和 Java 后端的博客,具有用户创建博客文章和根据用户权限显示博客文章的功能。你还被要求构建管理模块,如用户管理、监控等。最后,你必须测试并将应用程序部署到云服务。
如果你以传统方式来应对这个挑战,你很可能会做以下步骤。为了简单起见,让我们跳过细节。所以,步骤如下:
-
设计架构栈并决定使用各种库(比如说你选择了 Spring 框架作为后端,使用 Spring Security 和 Spring MVC)
-
创建一个包含所有技术连接的应用程序基础(例如,你必须确保 Angular 客户端和 Spring Security 之间的身份验证流程正确连接)
-
为应用程序编写构建系统(比如说你使用了 webpack 来构建 Angular 客户端,Gradle 来构建服务器端)
-
为基础编写集成测试和单元测试
-
创建管理模块
-
设计业务实体,并在 Angular 客户端和 Java 服务器端以及测试覆盖率下创建它们
-
编写所有业务逻辑,测试应用程序,并部署它
虽然这种方法确实有效,但对于这个简单的应用程序,你可能需要花费四到六周的时间,具体取决于团队规模。现在,超过 70%的努力将花费在编写样板代码和确保所有库能够良好协作上。现在,如果你说我可以在不到 30 分钟内使用 JHipster 开发、测试和部署这个应用程序,你会相信我吗?是的,你可以,同时还能获得高质量的、带有许多额外功能的实际生产级代码。我们将在下一章中看到这一功能的应用,我们将使用 JHipster 构建一个真实世界的应用程序。
JHipster 的目标和采用情况
JHipster 的目标是为开发者提供一个平台,在这里你可以专注于业务逻辑,而不是担心将不同的技术连接在一起,同时也提供了一个出色的开发者体验。当然,你可以在组织内部或从互联网上使用可用的样板代码,并尝试将它们连接起来,但这样你会浪费很多时间重新发明轮子。使用 JHipster,你将创建一个现代 Web 应用程序或微服务架构,其中所有必需的技术都已连接并开箱即用,例如以下内容:
-
后端基于稳健且高性能的 Spring Framework 的 Java 堆栈
-
基于 Bootstrap 的丰富移动优先前端,支持 Angular 或 React
-
经受战火考验的微服务架构,统一了 Netflix OSS、Elastic stack 和 Docker
-
使用 Maven/Gradle、webpack 和 Yarn/NPM 的出色工具和开发工作流程
-
开箱即用的持续集成,使用 Jenkins、Travis 或 GitLab
-
开箱即用的出色 Docker 支持和 Kubernetes、Rancher、OpenShift 等编排工具的支持
-
支持各种云部署的即插即用功能
-
最重要的是,拥有大量最佳实践和行业标准的手指尖上的优秀代码
Netflix OSS (netflix.github.io) 是由 NETFLIX, INC 团队生产的开源工具和软件集合,旨在面向微服务架构。Elastic stack (www.elastic.co/products)(以前称为 ELK stack)是一系列软件工具集合,有助于监控和分析由 Elasticsearch (www.elastic.co)团队开发的微服务。
随着 Spring Boot 和 Angular 的兴起,JHipster 的受欢迎程度稳步上升,许多开发者开始将其作为实际上的 Web 开发框架。根据撰写本文时的官方统计数据(2018 年初),每月生成的应用程序超过 5,000 个,JHipster 的安装量约为 1 百万次。它拥有超过 400 位贡献者,其中包括来自 Google、RedHat、Heroku 等机构的官方贡献。
技术介绍
JHipster 默认支持大量现代网络应用程序技术。其中一些被用作生成应用程序的基础或核心,而一些技术则通过在应用程序生成过程中所做的选择进行选择。让我们简要地看看主要针对单体应用程序支持的不同技术:
-
客户端技术
-
服务器端技术
-
数据库选项
支持的技术还有很多,我们将在后续章节中探讨,当涉及到微服务时。
客户端技术
在全栈开发中,客户端技术的作用已经从仅使用 JavaScript 进行客户端验证,发展到使用客户端 MVVM 框架编写完整的单页应用程序。对于刚开始接触客户端领域的开发者来说,所使用的框架和工具链变得复杂且令人不知所措。幸运的是,JHipster 为以下广泛使用的客户端技术提供了支持。让我们简要地看一下,并熟悉我们将使用的重要工具和技术。无需担心它会令人不知所措,我们将在本书的进程中深入探讨一些更重要的话题。
HTML5 和 CSS3
网络技术,尤其是 HTML 和 CSS,由于现代浏览器的出色支持,已经经历了重大更新,并且每天都在变得越来越好。
HTML5
HTML5 (developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5) 是 HTML (超文本标记语言) 标准的最新版本,它引入了新的元素、属性和行为。该术语用于统称构建现代网络应用程序所使用的所有 HTML 技术。这一版本引入了对离线存储、WebSockets、Web Workers、WebGL 等功能的支持。JHipster 还使用了来自 HTML5 Boilerplate (html5boilerplate.com) 的最佳实践。
HTML5 Boilerplate 是一组现代技术、默认设置和最佳实践,它能更快地启动现代网络开发。
CSS3
CSS3 (developer.mozilla.org/en-US/docs/Web/CSS/CSS3) 是 层叠样式表 (CSS) 规范的最新版本。它增加了对媒体查询、动画、flexbox、圆角等功能的支持。CSS3 使得原生动画元素、应用特殊效果、应用过滤器等成为可能,从而消除了之前使用的许多 JavaScript 诡计。
弹性盒模型,或称 flexbox,是一种布局模式 (developer.mozilla.org/en-US/docs/Web/CSS/Layout_mode),它可以替代传统上使用的盒模型。这使得拥有灵活的盒模型,使得响应式布局更容易处理,无需处理浮动和边距塌陷问题。
Sass
语法优美的样式表 (Sass) (sass-lang.com) 是一种 CSS 扩展语言。它在编译时预处理并转换为 CSS。它与 CSS 有类似的语义,并且与所有版本的 CSS 100% 兼容。它还支持嵌套语法、变量、混合、继承、部分等高级功能。Sass 使得重用 CSS 和编写可维护的样式表成为可能。
Bootstrap
Bootstrap (getbootstrap.com) 是一种用于现代 Web 开发的响应式 UI 框架。它为 Web 开发提供了一种移动优先的方法,包括完全响应式的实用工具和 UI 组件。Bootstrap 4 是最新版本,使用 flexbox 进行布局,并完全使用 Sass 编写,这使得它更容易定制。Bootstrap 支持一个 12 列的网格框架,让您能够轻松构建响应式网页。JHipster 使用 ng-bootstrap (ng-bootstrap.github.io),因此使用纯 Angular 组件而不是 Bootstrap 提供的组件,这些组件是使用 JQuery 构建的,Bootstrap 仅用于样式。
移动优先的 Web 开发是一种设计方法,首先为较小的屏幕尺寸设计 UX/UI,从而迫使您关注要展示的最重要数据/元素。然后,这种设计逐渐增强以适应更大的屏幕尺寸,使最终结果既响应又高效。
MVVM 框架
模型-视图-视图-模型 (MVVM) 是由微软最初开发的一种架构模式。它有助于将客户端(GUI)开发与服务器端(数据模型)抽象或分离。视图模型是视图的抽象,代表模型中的数据状态。使用 JHipster,您可以选择 Angular 或 React 作为客户端框架。
Angular
AngularJS (angularjs.org)(版本 1.x) 是由 Google 维护的客户端 MVVM 框架,有助于开发 单页应用程序 (SPA)。它基于声明式编程模型,并扩展了标准 HTML,通过指令添加了额外的行为、元素和属性。它还引入了诸如 SPA 的概念,并扩展了标准 HTML,通过指令添加了额外的行为、元素和属性。
Angular (angular.io)(版本 2 及以上) 是框架的完全重写,因此与 AngularJS 不兼容。Angular 使用 TypeScript 编写,并推荐使用 TypeScript 编写 Angular 应用程序。Angular 移除了一些在 AngularJS 中使用的概念,例如作用域、控制器、工厂等。它还有绑定属性和事件的不同的语法。另一个主要区别是 Angular 库是模块化的,因此您可以选择所需的模块,以减少包大小。Angular 还引入了诸如 AOT (提前编译)、懒加载、响应式编程等高级概念。
TypeScript 是 ECMAScript 6 (ES6 - JavaScript 的第 6 个版本) 的超集,并且与 ES5 兼容。它具有静态类型、泛型、类属性可见性修饰符等附加功能。由于 TypeScript 是 ES6 的超集,我们还可以使用 ES6 功能(es6-features.org),如模块、lambda(箭头函数)、生成器、迭代器、字符串模板、反射、扩展运算符等。
React
React (reactjs.org) 不是一个完整的 MVVM 框架。它是一个用于构建客户端视图或用户界面的 JavaScript 库。它由 Facebook 开发并支持,背后有一个充满活力的社区和生态系统。React 采用 HTML in JS 方法,并有一个称为 JSX 的特殊格式来帮助我们编写 React 组件。与 Angular 不同,React 没有太多概念或 API 需要学习,因此更容易上手,但 React 只关注渲染 UI,因此要获得 Angular 提供的类似功能,我们不得不将 React 与其他库如 React Router (reacttraining.com/react-router)、Redux (redux.js.org)、MobX (mobx.js.org) 等搭配使用。JHipster 使用 React 以及 Redux 和 React Router,类似于 Angular,JHipster 也为 React 使用 TypeScript。但这不是必需的,因为 React 也可以使用 JavaScript 编写,最好是 ES6 (es6-features.org)。React 由于使用了虚拟 DOM (reactjs.org/docs/faq-internals.html) 来操作视图而不是实际浏览器 DOM,因此渲染速度快。
如果你正在启动一个新项目,最好选择 Angular 或 React,因为它们都得到了良好的维护。然而,在 JHipster 的旧版本中,AngularJS 1.x 也被提供作为选项,但它正在成为过时的技术,并将很快在 JHipster 5.x 中被停止支持。JHipster 将为仍然对使用 AngularJS 1.x 感兴趣的人提供官方蓝图。只需运行命令 jhipster --blueprint generator-jhipster-angularjs 即可使用它。
构建工具
客户端已经发展了很多,变得和服务器端一样复杂,因此它需要更多的工具来产生优化的结果。你需要一个构建工具来转换、最小化和优化你的 HTML、JavaScript 和 CSS 代码。其中最受欢迎的是 Webpack。JHipster 使用 Webpack 为 Angular 和 React。
Webpack
Webpack (webpack.js.org) 是一个具有非常灵活的加载器/插件系统的模块打包器。Webpack 会遍历依赖图,并通过配置的加载器和插件传递它。使用 Webpack,你可以将 TypeScript 转换为 JavaScript,最小化和优化 CSS 和 JS,编译 Sass,修订,对你的资产进行哈希处理等。Webpack 可以通过称为 摇树干 的过程删除死代码,从而减小包的大小。Webpack 使用配置文件进行配置,可以从命令行或通过 NPM/YARN 脚本运行。
BrowserSync
BrowserSync (browsersync.io) 是一个 NodeJS 工具,通过同步多个浏览器和设备上的网页文件更改和交互,帮助进行浏览器测试。它提供了诸如文件更改时自动重新加载、同步 UI 交互、滚动等功能。它集成了 Webpack/GulpJS,以提供高效的开发环境。它使得在多个浏览器和设备上测试网页变得非常简单。
测试工具
那些客户端代码不需要单元测试的日子已经过去了。随着客户端框架的发展,测试的可能性也得到了提高。现在有许多框架和工具可用于单元测试、端到端测试等。JHipster 使用 Karma 和 Jasmine 开箱即地为客户端代码创建单元测试,并支持使用 Protractor 创建端到端测试。
Karma
Karma (karma-runner.github.io/2.0/index.html) 是一个可以在真实浏览器中执行 JavaScript 代码的测试运行器。它创建一个 web 服务器,并针对源代码执行测试代码。Karma 支持多个测试框架,如 Jasmine、Mocha 和 Qunit,并且与持续集成工具集成良好。
Protractor
Protractor (www.protractortest.org) 是由 Angular 团队开发的一个端到端测试框架。它最初是为 Angular 和 AngularJS 应用程序设计的,但它足够灵活,可以与任何框架一起使用,例如 React、JQuery、VueJS 等。Protractor 使用 Selenium WebDriver API 对真实浏览器运行 e2e 测试。
国际化
国际化(i18n)是当今非常重要的一个特性,JHipster 支持开箱即用的国际化。在应用程序创建过程中可以选择多种语言。在客户端,这是通过为每种语言存储 JSON 文件中的 GUI 文本,并使用 Angular/React 库根据运行时选择的语言动态加载这些文本来实现的。
你知道为什么国际化被缩写为 i18n 吗?因为 I 和 N 之间有 18 个字符。在 Web 技术中还有其他类似命名的缩写,例如,无障碍性(a11y)、本地化(l10n)、全球化(g11n)和可本地化(l12y)。
服务器端技术
网络开发中的服务器端技术已经发展了很多,随着 Spring 和 Play 等框架的兴起,Java EE 的需求减少了,为更多功能丰富的替代品打开了大门,例如 Spring Boot。一些核心技术,如 Hibernate,将长期存在,而像 JWT、Liquibase、Swagger、Kafka 和 WebSockets 等新概念则带来了很多额外的机会。让我们快速了解一下 JHipster 支持的一些重要技术;我们将在本书的后续部分遇到这些技术,并对其中一些技术进行更深入的探讨。
Spring 框架
Spring 框架(spring.io)可能是 Java 世界中最棒的事情。它改善了 Java 网络应用领域的格局。在 Spring 兴起之前,JavaEE 供应商垄断了这一领域,Spring 兴起后不久,它成为了 Java 网络开发者的首选,为 JavaEE 带来了竞争。在核心上,Spring 是一个提供依赖注入和应用程序上下文的控制反转(IoC) (docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans)容器。Spring 的主要特性或 Spring 三角,以一致的方式将 IoC、面向切面编程(AOP) (docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop)和技术抽象结合起来。该框架有众多模块,针对不同的任务,如数据管理、安全、REST、网络服务等。Spring 框架及其模块是免费和开源的。让我们更详细地了解一下一些重要的模块。
IoC 是一种软件设计模式,其中自定义或任务特定的代码由库调用,而不是传统的程序性编程方法,在需要时自定义代码调用库。IoC 有助于使代码更加模块化和可扩展。AOP 提供了另一种思考程序结构的方式。模块化的单位是方面,它使诸如事务管理等跨越多个类型和对象的关注点模块化。
Spring Boot
Spring Boot (projects.spring.io/spring-boot) 是目前广泛使用的 Java 网络应用程序开发解决方案。它采用了一种基于配置的约定优先方法。它是完全由配置驱动的,使得使用 Spring 框架和许多其他第三方库变得愉快。Spring Boot 应用程序是生产级别的,可以在安装了 JVM 的任何环境中运行。它使用嵌入式 Servlet 容器(如 Tomcat、Jetty 或 Undertow)来运行应用程序。它尽可能自动配置 Spring,并为许多模块和第三方库提供启动 POM。它不需要任何 XML 配置,并允许您使用 Java 配置自定义自动配置的 bean。
JHipster 默认使用 Undertow 作为生成应用程序中的嵌入式服务器。Undertow 非常轻量级且启动速度快,非常适合轻量级应用程序的开发和生产。
Spring Security
Spring Security (projects.spring.io/spring-security) 是基于 Spring 框架的应用程序中事实上的安全解决方案。它提供 API 和实用工具来管理安全的所有方面,例如身份验证和授权。它支持广泛的身份验证机制,如 OAuth2、JWT、会话(Web 表单)、LDAP、SSO(单点登录)服务器、JAAS(Java 身份验证和授权服务)、Kerberos 等。它还具有诸如记住我、并发会话等功能。
Spring MVC
Spring MVC (docs.spring.io/spring/docs/current/spring-framework-reference/web.html) 是在 Spring 应用程序中与 Servlet API 交互的事实上的解决方案。它是一个基于请求的系统,并抽象化 Servlet API 以简化控制器的设计,以服务 HTTP 请求。REST 是当今设计 API 端点的既定标准,Spring MVC REST 是一个特定的子集,使得设计和实现 RESTful 服务更加容易。
Spring 数据
Spring data (projects.spring.io/spring-data) 是一个模块,它抽象了多种不同的数据访问技术和数据库的数据访问操作。它提供了一个一致的 API,以便与不同的底层实现无缝工作。这使我们免于担心底层数据库和数据访问技术。它具有诸如从方法名称动态生成查询、自定义对象映射抽象等功能。Spring data 支持 JPA、MongoDB、Redis、Elasticsearch 等多种数据源的工作。它还允许您将 Spring data 存储库作为 RESTful 资源导出。
安全
在现代 Web 应用程序中,有多种方式来实现身份验证和授权。Spring 安全支持广泛的机制,正如我们之前所看到的,JHipster 提供了对以下标准的支持。
JWT
JSON Web Token(JWT)(jwt.io) 是一个开放的行业标准,用于安全令牌。JWT 认证通过服务器和客户端传递和验证声明来实现。当用户凭据成功验证后,服务器生成 JWT 令牌并将其返回给客户端。客户端将此令牌本地存储,并在稍后通过在请求头中传递令牌来请求从服务器获取受保护资源。这是一个无状态的认证机制。这在本章的详细说明中有所解释,第九章,使用 JHipster 构建微服务。
会话
基于会话的身份验证是传统的基于表单的认证机制,其中服务器为验证过的用户凭据创建并维护一个会话。这是有状态的,除非你使用分布式 HTTP 会话,例如使用 Hazelcast 这样的分布式缓存,或者使用专用 Web 服务器或负载均衡器的会话复制功能,否则通常不太可扩展。JHipster 在标准机制之上添加了许多功能,例如存储在数据库中的安全令牌,可以被无效化,用于“记住我”机制等。
OAuth2
OAuth2 (developer.okta.com/blog/2017/06/21/what-the-heck-is-oauth) 是一个无状态认证和授权协议。与之前提到的机制相比,该协议允许应用程序获取对服务用户账户的有限访问权限。用户认证委托给一个服务,通常是 OAuth2 服务器。与之前提到的机制相比,OAuth2 的设置更为复杂。JHipster 支持使用OpenID Connect(OIDC)设置 OAuth,并且可以开箱即用使用 Keycloak (keycloak.org) 或 Okta (developer.okta.com/blog/2017/10/20/oidc-with-jhipster)。
构建工具
JHipster 支持使用 Maven 或 Gradle 作为服务器端代码的构建工具。两者都是免费且开源的。
Maven
Maven (maven.apache.org) 是一个构建自动化工具,它使用一个名为 pom.xml 的 XML 文档来指定应用程序的构建方式和其依赖项。插件和依赖项从中央服务器下载并本地缓存。Maven 构建文件被称为项目对象模型(POM),它描述了构建过程本身。Maven 有着悠久的历史,与 Gradle 相比,它更加稳定和可靠。它还有一个庞大的插件生态系统。
Gradle
Gradle (gradle.org) 是一个构建自动化工具,它使用 Groovy DSL 来指定构建计划和依赖关系。它是一个强劲的竞争者,迅速获得流行度和采用率。Gradle 比 Maven 更灵活、功能更丰富,使其成为非常复杂构建设置的理想选择。Gradle 的最新版本在速度和功能方面都轻松超越了 Maven。Gradle 的另一个独特优势是能够在构建脚本中编写标准的 Groovy 代码,这使得几乎可以程序化地完成任何事情。它还拥有出色的插件支持。
Hibernate
Hibernate (hibernate.org) 是最流行的 ORM(对象关系映射)工具,用于 Java。它通过 Java 注解帮助将面向对象的领域模型映射到关系数据库模式。Hibernate 实现了 JPA(Java 持久化 API),并且是 JPA 实现的首选提供商。Hibernate 还提供了许多附加功能,如实体审计、Bean 验证等。Hibernate 会根据底层数据库语义自动生成 SQL 查询,使得切换应用程序的数据库变得非常容易。它还使应用程序数据库独立,没有任何供应商锁定。Hibernate 是免费的开源软件。
Liquibase
Liquibase (www.liquibase.org) 是一个免费的开源数据库版本控制工具。它允许您使用配置文件跟踪、管理和应用数据库模式更改,而无需与 SQL 打交道。它是数据库无关的,并且与 JPA 兼容良好,使应用程序数据库独立。Liquibase 可以在应用程序内部运行,使得数据库设置和管理无缝,并且对于大多数数据库管理来说,无需 DBA。Liquibase 还可以向数据库添加或从数据库中删除数据,使其非常适合迁移。
缓存
缓存是软件开发中的良好实践,并且可以显著提高读取操作的性能。可以为 Hibernate 的第二级缓存启用缓存,也可以使用 Spring Cache 抽象在方法级别启用缓存。JHipster 支持 EhCache、Hazelcast 和 Infinispan 提供的与 JCache 兼容的 Hibernate 第二级缓存。
Ehcache
Ehcache (www.ehcache.org) 是一个开源的 JCache 提供商,并且是使用最广泛的 Java 缓存解决方案之一。它与 JCache 兼容,对于非集群应用程序来说是一个不错的选择。对于集群环境,需要额外的 Terracotta 服务器。它稳定、快速,并且易于设置。
Hazelcast
Hazelcast (hazelcast.org) 是一个开源的分布式内存数据网格解决方案。它对集群应用程序和分布式环境有出色的支持,因此成为缓存的一个好选择。虽然 Hazelcast 有许多其他功能和用例,但缓存仍然是其中之一。由于其分布式特性,它高度可扩展,是微服务的一个好选择。
Infinispan
Infinispan (infinispan.org) 是来自 Red Hat 的分布式缓存和键值存储。它是免费和开源的。它支持集群环境,因此是微服务的一个好选择。它还具有内存数据网格、MapReduce 支持等功能。
Swagger
OpenAPI 规范(之前被称为 Swagger 规范)是设计和使用 RESTful 网络服务和 API 的开放标准。OpenAPI 规范是由包括 Google、Microsoft 和 IBM 在内的多家公司共同创立的标准。Swagger (swagger.io) 的名字现在用于相关的工具。JHipster 支持使用 Swagger code-gen 进行 API-first 开发模型,同时也支持使用 Swagger UI 进行 API 可视化。
Thymeleaf
Thymeleaf (www.thymeleaf.org) 是一个与 Spring 集成非常良好的开源 Java 服务器端模板引擎。Thymeleaf 可以用于在服务器端生成网页,用于模板化电子邮件消息等。尽管服务器端网页模板正逐渐被客户端 MVVM 框架所取代,但如果想要比单页应用使用 Angular 更多的功能,它仍然是一个有用的工具。
Dropwizard metrics
Dropwizard metrics (metrics.dropwizard.io/4.0.0/) 是一个用于测量 Java 网络应用程序性能的出色开源库。与 Spring Boot 配对,通过测量 REST API 的性能、测量缓存层和数据库的性能等方式,可以带来很多价值。Dropwizard 提供了方便的注解来标记要监控的方法。它支持计数器、计时器等。
WebSocket
WebSocket(developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) 是一种在 TCP 之上工作的通信协议。它通过单个 TCP 连接提供全双工通信通道。它由 W3C(www.w3.org)标准化。它轻量级,并允许客户端和服务器之间进行实时通信。在 Web 应用方面,这允许服务器在客户端没有请求的情况下与浏览器中的客户端应用进行通信。这为实时推送数据从服务器到客户端以及实现实时聊天、通知等打开了大门。在服务器端,JHipster 依赖于 Spring,它提供了必要的支持(spring.io/guides/gs/messaging-stomp-websocket/)以与 WebSocket 协同工作。
Kafka
Kafka(kafka.apache.org) 是一个开源的流处理系统。它有一个基于分布式 pub/sub 的消息队列用于存储。它的容错性和可扩展性帮助它取代 JMS 和 AMQP,成为首选的消息队列。Spring 在 Kafka 之上提供了一个抽象层,使其更容易配置和使用 Kafka。
JMS(Java 消息服务)(en.wikipedia.org/wiki/Java_Message_Service) 是为 Java EE 开发的消息标准,它允许使用主题和队列在组件之间发送和接收异步消息。AMQP(高级消息队列协议)(www.amqp.org/) 是一种面向消息中间件的开源标准协议,提供如队列、路由和发布/订阅机制等功能。
测试框架
服务器端测试可以主要分为单元测试、集成测试、性能测试和行为测试。JHipster 支持所有这些测试,其中 JUnit 是默认提供的,其他则是可选的。
JUnit
JUnit(junit.org/junit5/) 是最广泛使用的 Java 测试框架。它是一个免费的开源软件。最初它旨在进行单元测试,但与 Spring Test Framework(docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testing-introduction)结合使用后,也可以用于集成测试。JHipster 使用 JUnit 和 Spring Test Framework 创建单元测试和 REST API 集成测试。
Gatling
Gatling (gatling.io/) 是一款免费且开源的性能和负载测试工具。它基于 Scala,并使用 Scala DSL 编写测试规范。它能够创建详细的负载测试报告,并可以用来模拟系统上的各种负载。它是性能关键应用中必备的工具。
Cucumber
Cucumber (cucumber.io/) 是一个主要用于验收测试的行为驱动开发(BDD)测试框架。它使用一种名为Gherkin的语言解析器,这种语言解析器非常易于人类阅读,看起来类似于普通的英语。
数据库选项简介
今天,市场上提供了各种各样的数据库选项。这些可以大致分为以下几类:
-
SQL 数据库
-
NoSQL 数据库
您可以访问db-engines.com/en/ranking以查看不同数据库的流行度。
JHipster 支持一些最广泛使用的数据库,具体详情请见此处。
SQL 数据库
SQL 数据库或关系数据库管理系统(RDBMS)是指支持关系表型数据模型的数据库。它们支持由固定名称和列/属性数量以及固定数据类型定义的表模式。表中的每一行都包含每个列的值。表可以相互关联。
H2
H2 (www.h2database.com/html/main.html) 是一款免费嵌入式的 RDBMS,通常用于开发和测试。它通常可以在文件系统模式下运行以实现持久性,或在内存模式下运行。它具有非常小的体积,并且配置和使用极其简单。它没有提供其他主流数据库引擎所提供的许多企业级功能,因此通常不适用于生产使用。
MySQL
MySQL (www.mysql.com/) 是最受欢迎的数据库引擎之一,它是一款免费且开源的软件。它由甲骨文公司开发,但也拥有一个非常活跃的社区。它具备诸如分片、复制、分区等企业级功能。它是目前最受欢迎的 SQL 数据库之一。
MariaDB
MariaDB (mariadb.org/) 是一个符合 MySQL 规范的数据库引擎,它还特别关注安全性、性能和高可用性。它越来越受欢迎,被视为 MySQL 的良好替代品。它是一款免费且开源的软件。
PostgreSQL
PostgreSQL (www.postgresql.org/) 是另一个非常受欢迎的免费和开源数据库系统。它由一个社区积极维护。PostgreSQL 的一个独特特性是具有索引和查询 JSON 对象的先进 JSON 对象存储能力。这使得它能够作为 NoSQL 数据库或混合模式使用。它还具备复制、高可用性等企业级功能。
MS SQL
MS SQL 服务器 (www.microsoft.com/nl-nl/sql-server/sql-server-2017) 是由微软开发和支持的数据库系统,是一个企业级数据库系统。它是商业软件,需要付费许可才能使用。它具有企业级功能,并享有微软的高级支持。它是用于关键任务系统的流行选择之一。
Oracle
Oracle (www.oracle.com/database/index.html) 由于其传统和企业功能而成为最常用的数据库。它是商业软件,需要付费许可才能使用。它具有企业级功能,如分片、复制、高可用性等。
NoSQL 数据库
这是一个广泛的范畴,包括所有不是关系型数据库管理系统(RDBMS)的数据库。这包括文档存储、宽列存储、搜索引擎、键值存储、图数据库管理系统、内容存储等等。这类数据库的一般特点是它们可以是模式无关的,并且不依赖于关系数据。
MongoDB
MongoDB (www.mongodb.com/) 是一个跨平台文档存储,是 NoSQL 数据库中最受欢迎的选择之一。它拥有专有的基于 JSON 的 API 和查询语言。它支持 MapReduce 和企业级功能,如分片、复制等。它是免费和开源软件。
MapReduce 是一种数据处理范式,将一个任务分割成多个并行映射任务,对产生的输出进行排序和归约成结果。这使得处理大型数据集变得高效且更快。
Cassandra
Apache Cassandra (cassandra.apache.org/) 是一个分布式列存储,专注于高可用性、可扩展性和性能。由于其分布式特性,它没有单点故障,因此成为最流行的关键高可用性系统选择。它最初由 Facebook 开发和开源。
你知道吗,Cassandra 每行可以拥有高达 20 亿个列?
Elasticsearch
Elasticsearch (www.elastic.co/products/elasticsearch) 是一个基于 Apache Lucene (lucene.apache.org/) 的搜索和分析引擎。它在技术上是一个 NoSQL 数据库,但由于其索引能力和高性能,主要用作搜索引擎。它具有分布式和多租户功能,并具备全文搜索能力。它拥有网络界面和 JSON 文档。它是使用最广泛的搜索引擎之一。
安装和设置
要开始使用 JHipster,您必须安装 JHipster CLI 工具。JHipster CLI 包含使用平台提供的所有功能所需的命令。
JHipster 在线:如果您想创建一个不需要安装任何东西的应用程序,您可以通过访问 start.jhipster.tech 来实现。您可以将应用程序授权以直接在您的 GitHub 账户中生成项目,或者您可以将其作为 ZIP 文件下载。
先决条件
在我们安装 JHipster CLI 之前,让我们先看看先决条件。我们需要安装一些依赖项,并配置我们最喜欢的 IDE 以最佳方式与生成的代码一起工作。您可以访问 www.jhipster.tech/installation/ 以获取有关此信息的最新信息。
需要的工具
以下是需要安装 JHipster 以及与生成的应用程序一起工作的工具。如果您尚未安装,请按照以下步骤进行安装。
在本节中,您需要使用命令行界面(命令提示符或终端应用程序),因此最好有一个终端已经打开。由于以下工具的安装将更改环境变量,您可能需要在安装一个工具后关闭并重新打开终端:
-
在 Windows 上,使用默认的 命令提示符 (CMD) 或 PowerShell
-
在 Linux 上,使用 Bash 或您喜欢的终端模拟器
-
在 macOS 上,使用 iTerm 或您喜欢的终端应用程序
安装过程
让我们看看每个工具的安装过程。
Java 8
Java 9 是最新版本的 Java,引入了模块、响应式流等功能。虽然 JHipster 应用程序可以与 Java 9 一起工作,但在 Java 9 支持在所有依赖项中稳定之前,建议继续使用更稳定的 Java 8。
生成的应用程序使用 Java 8,因此需要编译应用程序:
-
通过在终端中运行命令
java -version来检查您的 Java 版本。它应该显示java version "1.8.x",其中 x 可以是任何补丁版本。 -
如果您没有安装正确的版本,您可以访问 Oracle 网站 (
www.oracle.com/technetwork/java/javase/downloads/index.html) 并按照说明安装 Java 8 的 JDK。 -
安装完成后,再次检查步骤 1 中的命令,以确保无误。由于 JDK 会更改环境变量以设置
JAVA_HOME,因此您在这里需要打开一个新的终端。
Git
Git 是最常用的源代码管理版本控制系统。它促进了分布式修订控制,并且现在是开发的一个基本组成部分。
JHipster 使用 Git 进行应用程序升级,并且也推荐使用 Git 来确保 NodeJS 和 NPM 生态系统的顺畅运行:
-
通过在终端中运行
git --version来检查 Git。它应该显示git version x.x.x;版本号可以是任何数字。 -
如果命令未找到,您可以访问 git-scm(
git-scm.com/downloads)并按照说明在您的操作系统上安装 Git。 -
安装完成后,再次检查步骤 1 中的命令以确保无误。
Node.js
Node.js 是一个 JavaScript 运行环境。它彻底改变了 JavaScript 世界,使 JavaScript 成为当今开发者中最受欢迎的开发语言(根据insights.stackoverflow.com/survey/2017#technology-programming-languages)。Node 生态系统是世界上最大的,拥有超过 600,000 个包,并由 NPM(默认包管理器)管理。
JHipster CLI 是一个 NodeJS 应用程序,因此需要 NodeJS 来运行,并且生成应用程序中使用的许多工具也将需要 NodeJS:
-
在终端中键入
node -v来检查 NodeJS。它应该显示一个版本号。请确保版本号大于 8.9,并且对应于 NodeJS 的最新 LTS 版本。 -
如果命令未找到或您有较低版本的 NodeJS,则可以访问 Node.js 网站(
nodejs.org/en/download/)并按照说明安装最新可用的 LTS 版本。请注意,非 LTS 版本(当前版本)可能不稳定,建议不要使用它们。 -
安装完成后,再次检查步骤 1 中的命令以确保无误。由于 NodeJS 会修改环境变量,因此在这里您需要打开一个新的终端。
-
当您安装 NodeJS 时,NPM 会自动安装。您可以在终端中运行
npm -v来检查这一点。
您可以通过运行命令npm -g install bower gulp-cli或使用 Yarn,yarn global add bower gulp-cli来安装多个 NPM 包。
Yarn
Yarn 是 NodeJS 的包管理器。它与 NPM 的 API 和功能兼容,并提供更好的性能和平坦的包树。
默认情况下,JHipster 使用 Yarn 而不是 NPM,因为 Yarn 在撰写本文时速度更快。如果您希望使用 NPM,则可以跳过此步骤:
-
您可以访问 Yarn 网站(
yarnpkg.com/en/docs/install)并按照说明安装 Yarn。 -
安装完成后,通过运行
yarn --version来检查以确保无误。
Docker
Docker 是容器管理的默认标准,它使使用容器变得轻而易举。它提供了创建、共享和部署容器的工具。
您将需要 Docker 和docker-compose来运行生成的数据库镜像以及微服务开发:
-
在终端中运行
docker -v来检查 Docker。它应该显示一个版本号。 -
在终端中运行
docker-compose -v来检查docker-compose。它应该显示一个版本号。如果您使用的是 Mac 或 Linux,您可以一起运行docker -v && docker-compose -v。 -
如果找不到命令,您可以访问 Docker 网站 (
docs.docker.com/install/) 并按照说明进行安装。此外,按照说明安装 Docker Compose (docs.docker.com/compose/install/)。 -
安装完成后,再次检查步骤 1 中的命令,以确保无误。
可选安装 Java 构建工具:通常 JHipster 会根据您的构建工具选择自动安装 Maven Wrapper (github.com/takari/maven-wrapper) 或 Gradle Wrapper (docs.gradle.org/current/userguide/gradle_wrapper.html),如果您不想使用这些包装器,请访问官方 Maven 网站 (maven.apache.org/) 或 Gradle 网站 (gradle.org/) 进行自己的安装。
IDE 配置
JHipster 应用程序可以通过使用命令行界面和 JHipster CLI 创建。从技术上讲,IDE 不是必需的,但在继续开发生成的应用程序时,强烈建议您使用适当的 Java IDE,例如 IntelliJ、Eclipse 或 Netbeans。有时您也可以使用带有适当插件的先进文本编辑器,如 Visual Studio Code 或 Atom,以完成工作。根据您选择的 IDE/文本编辑器,建议使用以下插件以提高开发效率:
-
Angular/React:Tslint、TypeScript、编辑器配置
-
Java:Spring、Gradle/Maven、Java 语言支持(VS Code)
无论使用 IDE/文本编辑器,始终排除 node_modules、git、build 和 target 文件夹以加快索引速度。一些 IDE 会根据 .gitignore 文件自动执行此操作。
在您喜欢的浏览器中访问 www.jhipster.tech/configuring-ide/ 了解更多信息。
系统设置
在安装和深入研究 JHipster 之前,这里有一些提示,以帮助您准备可能遇到的一些常见问题:
-
当在 macOS 或 Linux 上使用 Yarn 时,您需要在路径中包含
$HOME/.config/yarn/global/node_modules/.bin。这通常在您安装 Yarn 时自动完成,如果没有,您可以在终端中运行命令 ```export PATH="\(PATH:`yarn global bin`:\)HOME/.config/yarn/global/node_modules/.bin"` 来完成此操作。 -
如果您在公司代理后面,您将需要绕过它以使 NPM、Bower 和 Maven/Gradle 正常工作。请访问
www.jhipster.tech/configuring-a-corporate-proxy/了解可以为不同工具设置哪些代理选项。
如果你使用的是 Mac 或 Linux,并且如果你使用的是 Oh-My-Zsh 或 Fisherman shell,那么你可以使用 JHipster 为此提供的特定插件。访问 www.jhipster.tech/shell-plugins/ 获取详细信息。
JHipster 安装
好的,现在让我们真正开始吧。JHipster 可以通过本地安装使用 NPM 或 Yarn,通过团队提供的 Vagrant 镜像,或者使用 Docker 镜像。或者,还有我们之前看到的 JHipster 在线应用程序。
在所有选项中,要充分利用 JHipster 的全部功能,最佳方式是通过使用 Yarn 或 NPM 安装 JHipster CLI。打开一个终端并运行:
> yarn add global generator-jhipster
如果你更喜欢使用 NPM,那么只需运行:
> npm install -g generator-jhipster
等待安装完成,然后在终端运行 jhipster --version。你应该会看到如下所示的版本信息:

就这些了;我们已经准备好开始了。
如果你是一个迫不及待想要新版本的人,你可以在安装 JHipster CLI 后按照以下步骤使用当前的开发代码:
-
在终端中,导航到你想要使用的目录。例如,如果你在主目录中有一个名为
project的文件夹,运行cd ~/projects/,对于 Windows 运行cd c:\Users\<username>\Desktop\projects。 -
运行
git clone https://github.com/jhipster/generator-jhipster.git -
现在,通过运行
cd generator-jhipster导航到该文件夹。 -
运行
npm link以在此文件夹中创建到全局安装的应用程序在global node_modules中的符号链接。 -
现在当你运行 JHipster 命令时,你将使用你克隆的版本,而不是你安装的版本
请注意,你应该只在绝对确定自己在做什么的情况下才这样做。另外请注意,软件的开发版本将始终是不稳定的,可能包含错误。
如果你更喜欢在虚拟环境中隔离安装,那么你可以使用 JHipster 团队提供的 Vagrant 开发箱或 Docker 镜像。访问 github.com/jhipster/jhipster-devbox 获取使用 Vagrant 箱的说明,或者访问 www.jhipster.tech/installation 并向下滚动到 Docker 安装(仅适用于高级用户)部分,获取使用 Docker 镜像的说明。
摘要
在本章中,我们发现了 JHipster 以及它提供的不同技术选项。我们对客户端和服务器端栈的重要组件进行了简要介绍。我们对 Spring 技术、Angular、Bootstrap 等进行了快速概述。我们还对 JHipster 支持的不同数据库选项进行了概述。我们了解了与 JHipster 一起工作所需的工具,并且我们已经成功设置了与 JHipster 一起工作的环境,并安装了 JHipster CLI。在下一章中,我们将看到如何使用 JHipster 来构建一个生产级别的单体 Web 应用程序。
第三章:使用 JHipster 构建 Monolithic Web 应用程序
让我们开始行动,使用 JHipster 构建一个生产级别的 Web 应用程序。在我们开始之前,我们需要一个用例。我们将构建一个电子商务 Web 应用程序,该应用程序管理产品、客户及其订单和发票。Web 应用程序将使用 MySQL 数据库进行生产,并将具有 Angular 前端。实际购物网站的 UI 将与后台功能不同,后台功能仅对具有管理员角色的员工可用。在这个练习中,我们只为面向客户的部分构建一个简单的 UI。我们将在本章中讨论其他选项。
在本章中,我们将:
-
查看如何使用 JHipster 创建单体 Web 应用程序
-
了解生成的代码的重要方面
-
查看生成的应用程序的安全方面
-
查看如何运行应用程序和测试
-
查看生成的前端屏幕
-
查看包含的工具,这些工具将简化进一步的开发
本章将需要在整个过程中使用终端(Windows 上的命令提示符)应用程序。您可以在上一章中查看更多关于此的信息。
应用程序生成
在我们开始生成应用程序之前,我们需要准备我们的工作空间,因为这个工作空间将贯穿整本书,您将在该工作空间上创建许多 Git 分支。
访问 rogerdudler.github.io/git-guide/ 以获取 Git 命令的快速参考指南。
第 1 步 – 准备工作空间
让我们为工作空间创建一个新的文件夹。创建一个名为 e-commerce-app 的文件夹,并在终端中导航到该文件夹:
> mkdir e-commerce-app
> cd e-commerce-app
现在,为我们的应用程序创建一个新的文件夹;让我们称它为 online-store 并导航到它:
> mkdir online-store
> cd online-store
现在,我们已经准备好调用 JHipster。让我们首先确保一切准备就绪,通过运行 jhipster --version 命令。它应该打印一个全局安装的 JHipster 版本,否则您需要遵循上一章中的说明来设置它。
总是使用最新版本的工具会更好,因为它们可能包含重要的错误修复。您可以使用命令 yarn global upgrade generator-jhipster 任何时间升级 JHipster。
第 2 步 – 使用 JHipster 生成代码
通过在终端中运行 jhipster 命令初始化 JHipster,这将产生以下输出:

JHipster 将提出一系列问题以获取关于不同选项的输入,这些选项是必需的。第一个问题是关于我们想要的程序类型,我们提供了以下四个选项:
-
单体应用程序:正如其名所示,它创建了一个基于 Spring Boot 后端和 SPA 前端的单体 Web 应用程序。
-
微服务应用程序:这创建了一个不带任何前端且设计用于与 JHipster 微服务架构一起工作的 Spring Boot 微服务。
-
微服务网关:这创建了一个与单体应用非常相似的 Spring Boot 应用,但针对微服务架构进行了额外的配置。它具有 SPA 前端。
-
JHipster UAA 服务器:这创建了一个 OAuth2 用户身份验证和授权服务。它不会包含任何前端代码,并设计用于在 JHipster 微服务架构中使用。
我们将为我们的用例选择单体应用。我们将在本书的第八章,“微服务服务器端技术简介”中详细讨论其他选项。
运行jhipster --help以查看所有可用命令。运行jhipster <command> --help以查看特定命令的帮助信息;例如,jhipster app --help将显示主应用生成过程的帮助信息。
服务器端选项
生成器现在将开始询问我们关于服务器端选项的需求。让我们逐一了解它们:
- 问题 1:这个提示要求我们为应用提供一个基本名称,该名称用于创建主类文件名、数据库名等。默认情况下,如果当前目录名不包含任何特殊字符,JHipster 将建议使用当前目录名。让我们将我们的应用命名为
store。请注意,文件将在你所在的当前目录中创建:

- 问题 2:这个提示要求我们选择一个 Java 包名。让我们选择
com.mycompany.store:

- 问题 3。这个提示询问我们是否需要为此实例配置 JHipster 注册表。JHipster 注册表提供了一个服务发现和配置服务器实现,这对于集中式配置管理和应用扩展非常有用。对于这个用例,我们不需要它,所以让我们选择“否”。我们将在本书的第八章,“微服务服务器端技术简介”中了解更多关于 JHipster 注册表的信息:

-
问题 4:这个提示要求我们选择一个身份验证机制。我们提供了三个选项:
-
JWT 身份验证
-
HTTP 会话身份验证
-
OAuth 2.0/OIDC 身份验证
-
我们已经在上一章中看到了它们的差异,并且对于我们的用例,让我们选择 JWT 身份验证:

- 问题 5:这个提示要求我们选择数据库类型;提供的选项是 SQL、MongoDB、Couchbase 和 Cassandra。我们在上一章中已经了解了不同的数据库选项。对于我们的应用,让我们选择一个 SQL 数据库:

- 问题 6:这个提示要求我们选择在生产中想要使用的特定 SQL 数据库;可用的选项是 MySQL、MariaDB、PostgreSQL、Oracle 和 Microsoft SQL Server。让我们在这里选择 MySQL:

- 问题 7:这个提示要求我们在我们选择的 SQL 数据库和 H2 嵌入式数据库之间进行选择,用于开发。H2 嵌入式数据库特别有用,因为它使开发更快且自包含,无需运行 MySQL 实例。因此,让我们在这里选择基于磁盘的 H2 持久性,因为它比运行完整的数据库服务更轻量级且易于开发:

如果您的用例需要在开发中处理持久数据,并且模型不会经常更改,那么您也可以选择 MySQL 进行开发,因为它会提供更快的启动时间。这是因为嵌入式 H2 数据库不需要初始化,但缺点是每次您进行模式更改或重新创建实体时,您都必须手动使用生成的 liquibase diff 更改日志更新数据库,或者手动擦除数据库并重新开始。使用嵌入式 H2 数据库,您可以通过运行 ./gradlew clean 来擦除它。
- 问题 8:这个提示要求我们选择一个 Spring 缓存实现。我们有选择不使用缓存、EHCache、Hazelcast 和 Infinispan 的选项。由于我们在上一章学习了这些内容,让我们在这里选择 Hazelcast:

- 问题 9。这个提示要求我们选择是否需要为 Hibernate 选择二级缓存。让我们选择是。它将使用我们在上一个问题中选择的相同的缓存实现:

- 问题 10:这个提示为我们提供了选择用于项目的构建工具;选项是 Maven 和 Gradle。让我们在这里选择 Gradle,因为它更现代且功能更强大:

-
问题 11:这个提示很有趣,因为它展示了 JHipster 支持的各种附加选项。选项包括:
-
社交登录:添加使用社交登录提供者(如 Facebook、Twitter 等)进行登录的支持(JHipster 5 中移除了社交登录选项,您需要选择 OAuth 2.0/OIDC 认证以使用 OIDC 提供商提供的社交登录)
-
Elasticsearch:为生成的实体添加 Elasticsearch 支持
-
WebSockets:使用 Spring WebSocket、SocketJS 和 Stomp 协议添加 WebSocket 支持
-
API 首先开发与 swagger-codegen:为 API 首先开发添加 Swagger 代码生成支持
-
Apache Kafka:添加使用 Kafka 的异步队列支持
-
让我们保持简单,选择使用 Spring WebSocket 的 WebSockets:

客户端选项
现在,生成器将询问我们关于客户端选项的问题,包括我们希望使用的客户端框架:
- 问题 1:这个提示要求我们选择一个客户端 MVVM 框架;选项包括 Angular 5 和 React。在这里我们选择 Angular 5:

- 问题 2。这个提示允许我们为我们的 CSS 启用 SASS 支持,由于 SASS 很棒,让我们通过选择“是”来启用它:

国际化选项
现在,我们将有机会启用国际化并选择我们想要的语言:
- 问题 1。这个提示允许我们启用 国际化(i18n)。在这里我们选择“是”:

- 问题 2:由于我们启用了 i18n,我们将被提供选择主要语言和附加 i18n 语言的选择。在撰写本文时,有 36 种受支持的语言,包括 2 种 RTL(从右到左)语言。让我们选择英语作为主要语言,简体中文作为附加语言:

测试
现在,我们可以为我们的应用程序选择测试选项。
这个提示允许我们为我们的应用程序选择测试框架,这将为应用程序和实体创建示例测试。选项包括 Gatling、Cucumber 和 Protractor。在这里我们选择 Protractor:

模块
这个提示允许我们从 JHipster 市场选择额外的第三方模块(www.jhipster.tech/modules/marketplace)。如果我们想使用 JHipster 直接不支持的功能,这可能很有帮助。我们将在后面的章节中探讨这个问题。现在,让我们选择“否”。不用担心,因为这些模块可以在需要时添加到应用程序中:

一旦所有问题都回答完毕,代码生成将开始,你将看到如下输出,列出创建的文件,然后运行 yarn 安装以安装所有前端依赖。
如果你不想运行 Yarn 安装和 Webpack 构建步骤,可以在运行 JHipster 时使用 --skip-install 标志来跳过这一步骤。只需运行 jhipster --skip-install
一旦安装完成,生成器将为客户端触发 webpack 构建,这样当我们启动应用程序时,我们就有了一切准备就绪:

JHipster 将检查你的环境,以查看是否安装了所有必需的依赖项,如 Java8、NodeJS、Git 和 NPM/Yarn。如果没有,它将在代码生成开始之前显示友好的警告消息。
一旦过程完成,你将看到如下成功消息,以及开始应用的说明:

在执行 jhipster 命令时,可以传递一些命令行标志。运行 jhipster app --help 将列出所有可用的命令行标志。例如,其中一个有趣的标志是 npm,它允许您在依赖项管理中使用 NPM 而不是 Yarn。
JHipster 将自动为文件夹初始化 Git 仓库并提交生成的文件。如果您希望亲自执行此步骤,可以在执行 jhipster --skip-git 时传递 skip-git 标志,然后手动执行以下步骤:
> git init
> git add --all
> git commit -am "generated online store application"
如果您愿意,也可以使用像 Sourcetree 或 GitKraken 这样的图形界面工具来与 Git 一起工作。
代码遍历
现在我们已经使用 JHipster 生成了应用程序,让我们来看看创建的源代码中的重要部分。让我们在我们的首选 IDE 或编辑器中打开我们的应用程序。
如果您使用 IntelliJ IDEA,您可以在应用程序文件夹的终端中执行 idea . 来启动它。否则,您可以使用菜单选项“文件 | 新建 | 从现有源创建项目”将应用程序导入为新 Gradle 项目,在选择 Gradle 之前选择项目文件夹,然后点击“下一步”和“完成”。如果您使用 Eclipse,请打开“文件 | 导入...”对话框,在列表中选择“Gradle 项目”,然后按照说明操作。
文件结构
创建的应用程序将具有以下文件结构:

如您所见,根目录相当繁忙,有几个文件夹但很多配置文件。其中最有趣的是:
-
src:这是源文件夹,包含主应用程序源和测试源文件。 -
webpack:这个文件夹包含所有用于开发、生产和测试的 Webpack 客户端构建配置。 -
gradle:这个文件夹包含 Gradle 包装器和额外的 Gradle 构建脚本,这些脚本将由主 Gradle 构建文件(如果选择 Maven,JHipster 也会提供类似的包装器)使用。 -
build.gradle:这是我们 Gradle 构建文件,它指定了应用程序的构建生命周期。它还指定了服务器端依赖项。构建使用与其一起定义的gradle.properties文件中的属性。您还可以找到一个名为gradlew(Windows 上的gradlew.bat)的可执行文件,它允许您使用 Gradle 而无需安装它。 -
.yo-rc.json:这是 JHipster 的配置文件。此文件存储了我们在应用程序创建期间选择的选项,并用于应用程序的重生成和升级。 -
package.json:这是 NPM 配置文件,它指定了所有客户端依赖项、客户端构建依赖项和任务。 -
tsconfig.json:这是 TypeScript 的配置文件。还有一个用于 Angular AOT(提前编译)的tsconfig-aot.json。 -
tslint.json:这是 TypeScript 的 lint 配置文件。
安装并配置你的 IDE 或编辑器中的 Typescript 和 Tslint 插件,以充分利用 Typescript。
现在,让我们看看源文件夹。它有一个主文件夹和一个测试文件夹,分别存储主应用程序源代码和相应的测试源代码。文件夹结构如下:
-
main:-
docker: 存储应用程序的 Dockerfile 以及所选选项的 Docker 配置 -
java: 存储应用程序的主要 Java 源代码 -
resources: 存储 Spring Boot 配置文件、Liquibase 变更日志以及应用程序使用的服务器端 i18n 文件和电子邮件模板等静态资源 -
webapp: 存储 Angular 应用程序源代码和客户端静态内容,如图片、样式表、i18n 文件等
-
-
test:-
java: 存储服务器端的单元和集成测试源代码 -
javascript: 存储客户端应用程序的 Karma 单元测试规范和 Protractor 端到端规范 -
resources: 存储 Spring 配置文件和用于测试的服务器端 i18n 文件和电子邮件模板等静态资源
-
服务器端源代码
服务器端代码位于src/main下的 Java 和资源文件夹中,如前一个截图所示。文件夹结构如下:

你可能会注意到,在生成的代码中,Spring 组件没有使用传统的@Autowired或@Inject注解进行依赖注入。这是因为我们使用构造函数注入而不是字段注入,Spring Boot 不需要为构造函数注入显式注解。构造函数注入被认为是一个更好的实践,因为它使我们能够编写更好的单元测试并避免设计问题,而字段注入则更为优雅,但容易使类变得单一。构造函数注入是 Spring 团队推荐的最佳实践。构造函数注入也使得单元测试组件变得更加容易。
Java 源代码
Java 源代码的重要部分是:
-
StoreApp.java: 这是应用程序的主要入口类。由于这是一个 Spring Boot 应用程序,主类是可执行的,你可以通过在 IDE 中运行这个类来启动应用程序。让我们看看这个类:- 该类被大量 Spring JavaConfig 注解标注:
@ComponentScan
@EnableAutoConfiguration(exclude = {MetricFilterAutoConfiguration.class, MetricRepositoryAutoConfiguration.class})
@EnableConfigurationProperties({LiquibaseProperties.class, ApplicationProperties.class})
-
-
第一个,
@ComponentScan,告诉 Spring 应用程序扫描源文件并自动检测 Spring 组件(服务、仓库、资源、定义 Spring bean 的配置类等)。 -
第二个是
@EnableAutoConfiguration,它告诉 Spring Boot 尝试猜测并自动配置应用程序可能需要的 bean,这些 bean 基于类路径上找到的类和我们所提供的配置。排除设置特别告诉 Spring Boot 不要自动配置指定的 bean。 -
第三个是
@EnableConfigurationProperties,它通过属性文件帮助为应用程序注册额外的配置。
-
-
- 类的主方法启动 Spring Boot 应用程序并运行它:
public static void main(String[] args) throws UnknownHostException {
SpringApplication app = new SpringApplication(StoreApp.class);
DefaultProfileUtil.addDefaultProfile(app);
Environment env = app.run(args).getEnvironment();
...
}
-
config:这个包包含数据库、缓存、WebSocket 等 Spring bean 配置。这是我们配置应用程序各种选项的地方。其中一些重要的配置是:-
CacheConfiguration.java:这个类配置应用程序的 Hibernate 二级缓存。由于我们选择了 Hazelcast 作为缓存提供者,这个类以相同的方式配置。 -
DatabaseConfiguration.java:这个类配置应用程序的数据库,并启用事务管理、JPA 审计和 JPA 存储库。它还配置 Liquibase 来管理数据库迁移和 H2 数据库用于开发。 -
SecurityConfiguration.java:这是应用程序的一个重要部分,因为它配置了应用程序的安全性。让我们看看类中的重要部分:- 这些注解启用了 Web 安全和方法级安全,这样我们就可以在单个方法上使用
@Secured和@Pre/PostAuthorize注解:
- 这些注解启用了 Web 安全和方法级安全,这样我们就可以在单个方法上使用
-
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
-
-
- 以下配置告诉应用程序忽略 Spring 安全配置中的静态内容和某些 API:
-
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/api/register")
.antMatchers("/api/activate")
.antMatchers("/api/account/reset-
password/init")
.antMatchers("/api/account/reset-
password/finish")
.antMatchers("/test/**")
.antMatchers("/h2-console/**");
}
-
-
- 以下配置告诉 Spring 安全哪些端点是所有用户允许访问的,哪些端点需要认证,以及哪些端点需要特定的角色(在这个例子中是
ADMIN):
- 以下配置告诉 Spring 安全哪些端点是所有用户允许访问的,哪些端点需要认证,以及哪些端点需要特定的角色(在这个例子中是
-
@Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.and()
.authorizeRequests()
.antMatchers("/api/register").permitAll()
...
.antMatchers("/api/**").authenticated()
.antMatchers("/websocket/tracker")
.hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/websocket/**").permitAll()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/**")
.hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/v2/api-docs/**").permitAll()
.antMatchers("/swagger-resources/configuration/ui").permitAll()
.antMatchers("/swagger-ui/index.html")
.hasAuthority(AuthoritiesConstants.ADMIN)
.and()
.apply(securityConfigurerAdapter());
}
-
WebConfigurer.java:这是我们设置 HTTP 缓存头、MIME 映射、静态资产位置和CORS(跨源资源共享)的地方。
JHipster 提供了开箱即用的 CORS 支持:
-
可以使用
jhipster.cors属性配置 CORS,如 JHipster 通用应用程序属性中定义的(www.jhipster.tech/common-application-properties/)。 -
在
dev模式下,它默认启用单体和网关。对于微服务,它默认禁用,因为您应该通过网关访问它们。 -
在
prod模式下,出于安全原因,它默认禁用单体和微服务。 -
domain:应用程序的域模型类在这个包中。这些是简单的 POJO,它们通过 JPA 注解映射到 Hibernate 实体。当选择 Elasticsearch 选项时,它们也充当文档对象。让我们看看User.java类:- 一个实体类由以下注解特征。
@Entity注解将类标记为 JPA 实体。@Table注解将实体映射到数据库表。@Cache注解启用实体的二级缓存,并指定了缓存策略:
- 一个实体类由以下注解特征。
@Entity
@Table(name = "jhi_user")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
- 这些类在字段级别使用了各种注解。
@Id标记实体的主键。@Column在没有提供覆盖时,通过相同的名称将字段映射到数据库表列。@NotNull、@Pattern和@Size是用于验证的注解。@JsonIgnore由 Jackson 用于在将对象转换为 JSON 时忽略字段,这些 JSON 将在 REST API 请求中返回。这对于与 Hibernate 一起使用尤其有用,因为它避免了关系之间的循环引用,这会创建大量的 SQL 数据库请求并失败:
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Pattern(regexp = Constants.LOGIN_REGEX)
@Size(min = 1, max = 50)
@Column(length = 50, unique = true, nullable = false)
private String login;
@JsonIgnore
@NotNull
@Size(min = 60, max = 60)
@Column(name = "password_hash",length = 60)
private String password;
-
- 数据库表之间的关系也使用 JPA 注解映射到实体。例如,它映射了用户与用户权限之间的多对多关系。它还指定了一个用于映射的连接表:
@JsonIgnore
@ManyToMany
@JoinTable(
name = "jhi_user_authority",
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name")})
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@BatchSize(size = 20)
private Set<Authority> authorities = new HashSet<>();
repository:此包包含实体的 Spring Data 存储库。这些通常是接口定义,由 Spring Data 自动实现。这消除了我们为数据访问层编写任何样板实现的需求。让我们看看UserRepository.java的示例:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findOneByActivationKey(String activationKey);
List<User> findAllByActivatedIsFalseAndCreatedDateBefore(Instant
dateTime);
Optional<User> findOneByResetKey(String resetKey);
Optional<User> findOneByEmailIgnoreCase(String email);
...
}
-
-
@Repository注解将此标记为 Spring 数据存储库组件。 -
接口扩展了
JpaRepository,这使得它可以继承所有默认的 CRUD 操作,如findOne、findAll、save、count和delete。 -
自定义方法按照 Spring 数据命名约定编写为简单的方法定义,以便方法名称指定要生成的查询。例如,
findOneByEmailIgnoreCase生成一个等价的查询SELECT * FROM user WHERE LOWER(email) = LOWER(:email)。
-
-
security:此包包含与 Spring 安全相关的组件和实用工具,由于我们选择了 JWT 作为我们的认证机制,它还包含与 JWT 相关的类,例如TokenProvider、JWTFilter和JWTConfigurer。 -
service:此包包含服务层,包括 Spring 服务豆、DTO、Mapstruct DTO 映射器和服务实用工具。 -
web:此包包含网络资源类、视图模型类和实用工具类。-
rest:此包包含用于 REST API 的 Spring 资源类。它还包含视图模型对象和实用工具。让我们看看UserResource.java:-
资源类被标记为 Spring 的
@RestController和@RequestMapping("/api")注解。后者指定了控制器的基 URL 路径,以便所有<applicationContext>/api/*请求都转发到这个类。 -
根据其目的,请求方法被注解,例如,下面的代码将
createUser方法标记为PostMapping用于"/users",这意味着所有发送到<applicationContext>/api/users的 POST 请求都将由该方法处理。@Timed注解用于测量方法的性能。@Secured注解限制了方法访问到指定的角色:
-
-
@PostMapping("/users")
@Timed
@Secured(AuthoritiesConstants.ADMIN)
public ResponseEntity createUser(@Valid @RequestBody ManagedUserVM managedUserVM) throws URISyntaxException {
...
}
-
WebSocket: 此包包含 WebSocket 控制器和视图模型。
JHipster 在服务器端使用 DTO(数据传输对象)和 VM(视图模型)。DTO 用于在服务层和资源层之间传输数据。它们 打破 Hibernate 事务,并避免由资源层触发的进一步延迟加载。VM 仅用于在 Web 前端显示数据,并不与服务层交互。
资源
资源的重要部分是:
-
config: 此处存放应用程序属性 YAML 文件和 Liquibase 变更日志。application.yml文件包含可配置的 Spring Boot、JHipster 和应用程序特定属性,而application.(dev|prod).yml文件包含在特定开发或生产配置活动时应应用的属性。测试配置位于src/test/resource/application.yml。 -
i18n: 此处存放服务器端的 i18n 资源文件。 -
mails: 此处存放电子邮件的 Thymeleaf 模板。 -
templates: 此处存放客户端的 Thymeleaf 模板。
客户端源代码
客户端源代码位于我们之前看到的 src/main/webapp 文件夹下。结构如下:

其中最值得注意的是:
-
app: 此文件夹包含 Angular 应用程序的 TypeScript 源代码,按功能组织,每个功能一个文件夹:app.main.ts: 这是 Angular 应用的主文件。它启动 Angular 应用程序。注意,它使用platformBrowserDynamic,这使得应用程序能够在浏览器中使用 JIT(即时)编译。这对于开发来说非常理想:
platformBrowserDynamic().bootstrapModule(StoreAppModule)
.then((success) => console.log(`Application started`))
.catch((err) => console.error(err));
app.module.ts: 这是 Angular 应用程序的主模块。它声明应用程序级别的组件和提供者,并导入其他模块以供应用程序使用。它还启动主应用程序组件:
@NgModule({
imports: [
BrowserModule,
...
StoreEntityModule,
// jhipster-needle-angular-add-module JHipster
will add new module here
],
declarations: [
JhiMainComponent,
...
FooterComponent
],
providers: [
ProfileService,
...
UserRouteAccessService
],
bootstrap: [ JhiMainComponent ]
})
export class StoreAppModule {}
-
account: 此模块包含与账户相关的功能,如激活、密码、密码重置、注册和设置。每个典型组件包括component.html、component.ts、route.ts和service.ts文件。 -
admin: 此模块包含与管理员相关的功能,如审计、配置、文档、健康、日志、指标、跟踪器和用户管理。每个典型组件包括component.html、component.ts、route.ts和service.ts文件。-
blocks: 此文件夹包含应用程序使用的 HTTP 拦截器和其它配置。 -
entities: 这是创建实体模块的地方。 -
home: 首页模块。 -
layouts: 此文件夹包含布局组件,如导航栏、页脚、错误页面等。 -
shared: 此模块包含所有共享服务(认证、跟踪器、用户)、组件(登录、警报)、实体模型以及应用程序所需的实用工具。
-
-
content: 此文件夹包含静态内容,如图片、CSS 和 SASS 文件。 -
i18n: 这里的 i18n JSON 文件存放位置。每种语言都有一个文件夹,其中包含按模块组织的众多 JSON 文件。 -
swagger-ui: 这个文件夹包含开发中用于 API 文档的 Swagger UI 客户端。 -
index.html: 这是 Web 应用程序的索引文件。它包含用于加载 Angular 应用程序主组件的非常少的代码。这是一个单页 Angular 应用程序。您还会在这个文件上找到一些注释掉的实用代码,如 Google 分析脚本和服务工作者脚本。如果需要,可以启用这些代码:
<!doctype html>
<html class="no-js" lang="en" dir="ltr">
<head>
...
</head>
<body>
...
<jhi-main></jhi-main>
<noscript>
<h1>You must enable javascript to view this page.</h1>
</noscript>
...
</body>
</html>
要使用服务工作者启用 PWA 模式,只需在src/main/webapp/index.html中取消注释相应的代码以注册服务工作者。JHipster 使用 workbox (developers.google.com/web/tools/workbox/),它创建相应的服务工作者并动态生成sw.js。
启动应用程序
现在,让我们启动应用程序并查看输出。运行应用程序有多种方式:
-
通过在终端/命令行中使用 Spring Boot Gradle 任务
-
通过从 IDE 中执行主 Java 类
src/main/java/com/mycompany/store/StoreApp.java -
通过使用
java -jar命令执行打包的应用程序文件
让我们使用 Gradle 任务启动应用程序。如果您想在 IDE 中直接运行应用程序,只需打开前面提到的主应用程序文件StoreApp.java,右键单击,并选择运行StoreApp。
要通过 Gradle 启动应用程序,请打开终端/命令行并导航到应用程序文件夹。然后,按照以下方式执行 Gradle 命令(如果您在 Windows 上,请执行gradlew.bat)。这将触发默认任务bootRun:
> cd online-store
> ./gradlew
执行./gradlew相当于执行./gradlew bootRun -Pdev。对于客户端,在第一次启动服务器之前需要运行 webpack 构建,否则您将看到一个空白页面。此任务在应用程序生成期间自动运行,但如果由于某些原因失败,可以通过运行yarn run webpack:build手动触发。也可以通过 Gradle 命令直接触发,通过运行./gradlew webpackBuildDev bootRun -Pdev。
Gradle 将开始下载包装器和依赖项,经过一段时间(几秒到几分钟不等,取决于网络速度)后,您应该会看到类似于以下截图的控制台输出:

应用程序已成功启动,并可在http://localhost:8080上访问。打开您喜欢的浏览器并导航到该 URL。
注意,由于过程持续运行,前面的构建将保持在 90%。
应用程序模块
让我们看看开箱即用的不同模块。模块可以分为:
-
首页和登录
-
账户
-
管理员
首页和登录模块
一旦您打开 URL,您将在主页上看到一位酷炫的潮人正在喝咖啡,如下所示:

这是主页。让我们使用默认凭据登录应用程序。
- 点击页面上的登录链接,或账户 | 登录。您将看到以下登录屏幕。输入默认凭据——用户名—
admin,密码—admin,然后点击登录:

登录后,您将在导航栏中看到所有已验证的菜单项的认证主页:

- 由于我们启用了国际化,我们得到了一个语言菜单。让我们尝试切换到另一种语言。点击语言菜单并选择下一个可用的语言:

账户模块
现在,让我们看看开箱即用的账户模块。在账户菜单下,您将看到一个退出选项和以下模块:
-
设置
-
密码
-
注册
设置
此模块允许您更改用户设置,如姓名、电子邮件和语言:

密码
此模块允许您更改当前用户的密码。还有一个带有电子邮件验证的默认忘记密码流程:

要使用电子邮件功能,您必须在应用程序属性中配置 SMTP 服务器。我们将在后面的章节中探讨这一点。
注册
此模块仅在您未登录时可用。这允许您作为新用户注册应用程序。这将触发一个带有激活邮件和验证的用户激活流程。当选择 OAuth2 作为您的身份验证时,此模块将不可用:

管理员模块
现在,让我们看看生成的管理员模块屏幕。这些对于应用程序的开发和监控非常有用。在管理员菜单下,您将找到以下模块:
-
用户管理
-
指标
-
健康
-
配置
-
审计
-
日志
-
API
用户管理
此模块为您提供了 CRUD 功能来管理用户。默认情况下,结果会分页。默认情况下,使用注册模块注册的用户将处于非激活状态,除非他们完成注册过程:

指标
此模块可视化由 Spring Boot actuator 和 Dropwizard 指标提供的数据。这对于监控应用程序性能非常有用,因为它提供了方法级别的性能信息,以及 JVM、HTTP、数据库和缓存指标。靠近线程的图标将允许您查看线程转储:

健康
此模块提供了数据库和其他信息(如磁盘空间)等应用程序组件的健康状态:

配置
此模块有助于可视化当前生效的应用程序配置。这对于解决配置问题非常有用:

审计
此模块列出了自 JHipster 为 Spring 安全性启用审计以来所有的用户身份验证审计日志,因此所有安全事件都被捕获。有一个特殊的 Spring 数据仓库,它将审计事件写入数据库。这对于安全方面非常有用:

日志
此模块有助于在运行时查看和更新应用程序日志级别。这对于解决故障非常有用:

API
此模块提供了应用程序 REST API 的 Swagger API 文档。它还提供了一个用于端点的“试一试”编辑器:

运行生成的测试
良好的软件开发永远不能没有良好的测试。JHipster 默认生成相当多的自动化测试,并且还有选择更多测试的选项。让我们运行生成的服务器端和客户端测试,以确保一切按预期工作。
首先,打开一个终端/命令行,导航到项目文件夹。
服务器端测试
服务器端集成测试和单元测试位于 src/test/java 文件夹中。
这些可以直接从 IDE 中运行,通过选择一个包或单个测试并运行它,或者通过命令行运行 Gradle 的 test 任务。让我们使用命令行来运行它。在一个新的终端中,导航到应用程序源文件夹,并执行以下命令。它应该以一个成功消息结束,如下所示:
> ./gradlew test
...
BUILD SUCCESSFUL in 45s
8 actionable tasks: 6 executed, 2 up-to-date
客户端测试
客户端单元测试和端到端测试位于 src/test/javascript 文件夹下。
这些测试可以使用提供的 npm 脚本或提供的 Gradle 任务来运行。
您可以通过运行 ./gradlew tasks 来查看所有可用的 Gradle 任务。
让我们使用 npm 脚本来运行它们。首先,让我们运行 Karma 单元测试。在终端中执行以下代码。如果您更喜欢使用 npm 而不是 yarn,也可以这样做:
> yarn test
最终应该产生类似的输出:
PhantomJS 2.1.1 (Linux 0.0.0): Executed 56 of 56 SUCCESS (1.672 secs / 1.528 secs)
=============================== Coverage summary ===============================
Statements : 69.25% ( 903/1304 )
Branches : 40.43% ( 112/277 )
Functions : 48.89% ( 154/315 )
Lines : 67.72% ( 795/1174 )
================================================================================
Done in 37.25s.
现在,让我们使用 npm 脚本来运行 Protractor 端到端测试。为了运行 e2e 测试,我们需要确保服务器正在运行。如果您已经关闭了我们之前启动的服务器,请确保通过在终端中运行 ./gradlew 再次启动它。现在,打开一个新的终端并导航到应用程序文件夹,并执行以下命令:
> yarn e2e
这将启动 protractor 测试,它将在新的 Chrome 浏览器实例中执行测试。完成后,您应该在控制台看到以下类似的内容:
Started
..........
10 specs, 0 failures
Finished in 11.776 seconds
[00:02:57] I/launcher - 0 instance(s) of WebDriver still running
[00:02:57] I/launcher - chrome #01 passed
摘要
在本章中,我们学习了如何使用 JHipster 创建单体 Web 应用程序。我们还详细探讨了创建的源代码的重要方面,并了解了如何运行创建的应用程序和自动测试。我们还浏览了创建的模块,并观察了它们在实际中的应用。在下一章中,我们将了解如何利用 JHipster 对我们的业务用例进行建模并为它们生成实体。我们还将学习关于JHipster 领域语言(JDL)的内容。
第四章:使用 JHipster 领域语言进行实体建模
在上一章中,我们看到了如何使用 JHipster 生成一个具有许多出色功能的生产级 Web 应用程序,例如 i18n、管理模块、账户管理等。在本章中,我们将看到如何通过业务实体和模型来丰富该应用程序。
在本章中,我们将学习以下内容:
-
JHipster 领域语言(JDL)(JDL)
-
JDL 工作室
-
使用 JDL 进行实体和关系建模
-
实体生成
JDL 简介
JDL (www.jhipster.tech/jdl/) 用于创建 JHipster 应用程序的领域模型。它提供了一个简单且用户友好的 DSL 来描述实体及其关系(仅适用于 SQL 数据库)。
JDL 是为应用程序创建实体的推荐方式,可以替代 JHipster 提供的实体生成器,在创建大量实体时使用起来可能比较困难。JDL 通常在具有 .jh 扩展名的一个或多个文件中编写。
访问 www.jhipster.tech/jdl/ 获取 JDL 的完整文档。
如果你更喜欢使用 UML 和 UML 建模工具,那么请查看 JHipster-UML (www.jhipster.tech/jhipster-uml/),这是一个可以从流行的 UML 工具创建实体的工具。
JDL 的 DSL 语法
现在,让我们看看 JDL 语法。在撰写本文时,JDL 支持生成带有关系和选项(如 DTO、服务层等)的完整实体模型。语法可以分解为以下内容:
-
实体声明
-
关系声明
-
选项声明
在以下语法中,[] 表示可选,* 表示可以指定多个。
可以将 Javadocs 添加到实体声明中,并将 /** */ Java 注释添加到字段和关系声明中。可以使用 // 语法添加 JDL 仅注释。
在 JDL 中也可以定义数值常量,例如,DEFAULT_MIN_LENGTH = 1。
使用 JDL 进行实体建模
实体声明使用以下语法进行:
entity <entity name> ([<table name>]) {
<field name> <type> [<validation>*]
}
<entity name> 是实体的名称,将用于类名和表名。表名可以使用可选的 <table name> 参数进行覆盖。
<field name> 是你想要为实体添加的字段(属性)的名称,而 <type> 是字段类型,例如 String、Integer 等。有关所有支持的字段类型,请参阅 www.jhipster.tech/jdl/#available-types-and-constraints。ID 字段将自动创建,因此不需要在 JDL 中指定。
<validation> 是可选的,并且可以根据字段类型支持的验证指定一个或多个 <validation> 字段。对于如最大长度和模式这样的验证,值可以在大括号中指定。
一个示例实体声明可能如下所示:
/**
* This is customer entity javadoc comment
* @author Foo
*/
entity Customer {
/** Name field */
name String required,
age Integer,
address String maxlength(100) pattern(/[a-Z0-9]+/)
}
可以使用以下语法声明枚举:
enum <enum name> {
<VALUE>*
}
这里有一个例子:
enum Language {
ENGLISH, DUTCH, FRENCH
}
关系管理
可以使用此语法声明实体之间的关系:
relationship <type> {
<from entity>[{<relationship name>[(<display field>)] <validation>*}]
to
<to entity>[{<relationship name>[(<display field>)] <validation>*}]
}
<type> 是 OneToMany、ManyToOne、OneToOne 或 ManyToMany 中的一个,正如其名,它声明了 <from entity> 和 <to entity> 之间的关系类型。
<from entity> 是关系或源的所有者实体的名称。<to entity> 是关系的目的地。
<relationship name> 是可选的,可以用来指定在领域对象中创建的关系的字段名。<display field> 可以在括号中指定,以控制要在生成的网页上的下拉菜单中显示的实体字段,默认情况下将使用 ID 字段。<validation> 可以在 <from entity> 或 <to entity> 上指定,是可选的。目前只支持必需的。
OneToMany 和 ManyToMany 关系在 JHipster 中总是双向的。在 ManyToOne 和 OneToOne 关系的情况下,可以创建双向和单向关系。对于单向关系,只需在目的地/实体上跳过 <relationship name> 即可。
同一类型的多个关系可以在同一块中声明,用逗号分隔。
一个示例关系声明可能看起来像以下这样:
entity Book
entity Author
entity Tag
relationship OneToMany {
Author{book} to Book{writer(name) required},
Book{tag} to Tag
}
用户是 JHipster 中的一个现有实体,可以与用户有某些关系。可以声明多对多和一对一关系,但其他实体必须是源或所有者。也可以与用户实体有多个一对一关系。
DTO、服务和分页选项
JDL 还允许我们轻松声明与实体相关的选项。当前支持的选项包括:
-
service:默认情况下,JHipster 生成直接调用实体存储库的 REST 资源类。这是一个最简单的选项,但在现实世界的场景中,我们可能需要一个服务层来处理业务逻辑。此选项让我们可以创建一个简单的 Spring 服务 bean 类或使用传统的接口和实现来创建服务 bean。可能的值是serviceClass和serviceImpl。选择后者将创建一个接口和实现,这是某些人所偏好的。 -
dto:默认情况下,领域对象直接用于创建的 REST 端点,这在某些情况下可能不是所希望的,你可能想使用一个中间的数据传输对象(DTO)来获得更多控制。JHipster 允许我们使用 Mapstruct (mapstruct.org/) 生成 DTO 层,Mapstruct 是一个注解预处理库,它可以自动生成 DTO 类。当使用 DTO 时,建议使用服务层。可能的值是mapstruct。更多信息请访问:www.jhipster.tech/using-dtos/。 -
filter: 此选项允许我们为实体启用基于 JPA 的过滤功能。这仅在使用了服务层时才有效。更多详情请访问:www.jhipster.tech/entities-filtering/。 -
paginate: 此选项允许我们为实体启用分页功能。这将在资源层上启用分页,并在客户端实现分页选项。可能的值有 pager、pagination 和 infinite-scroll。 -
noFluentMethod: 这允许我们禁用为生成的实体域对象启用的 Fluent API 风格的设置器。 -
skipClient/skipServer: 这些选项允许我们在生成过程中跳过客户端代码或服务器端代码。 -
angularSuffix: 此选项允许我们为前端代码中的文件夹和类名指定后缀。
选项声明的通用语法是 <OPTION> <ENTITIES | * | all> [with <VALUE>] [except <ENTITIES>]。
以下是一些可能的选项和它们可以声明的不同语法:
entity A
entity B
...
entity Z
dto * with mapstruct
service A with serviceImpl
service B with serviceClass
paginate * with pagination except B, C
paginate B, C with infinite-scroll
filter A, B
JDL Studio
我们将使用 JDL Studio (start.jhipster.tech/jdl-studio/) 来创建我们的 JDL 文件。这是一个由 JHipster 团队构建的在线网络应用程序,用于在可视化编辑器中创建 JDL 文件。该工具显示了创建的实体模型的视觉表示,并允许您导入/导出 JDL 和捕获图像快照:

工具还提供诸如语法高亮、自动完成、错误报告和 Sublime Text 风格的键盘快捷键等功能。
使用您喜欢的浏览器导航到 start.jhipster.tech/jdl-studio/ 以打开应用程序。
请注意,默认情况下,此应用程序将 JDL 存储在您的浏览器本地存储中。如果您想将 JDL 文件保存到云端,可以使用 JHipster 在线创建账户。
用例实体模型及解释
现在,让我们看看我们的用例和实体模型。在那之前,请清除 JDL Studio 编辑器中的默认 JDL。
实体
让我们先定义我们的实体:
- 将以下片段复制到
Product和ProductCategory的 JDL Studio 编辑器中:
/** Product sold by the Online store */
entity Product {
name String required
description String
price BigDecimal required min(0)
size Size required
image ImageBlob
}
enum Size {
S, M, L, XL, XXL
}
entity ProductCategory {
name String required
description String
}
Product 实体是域模型的核心;它包含产品信息,如 name、description、price、size 和 image(这是一个 Blob)。name、price 和 size 是必填字段。price 还有一个最小值验证。size 字段是一个具有定义值的枚举类型。
ProductCategory 实体用于将产品分组在一起。它有 name 和 description,其中 name 是必填字段。
- 将以下片段添加到 JDL Studio 编辑器的
Customer中:
entity Customer {
firstName String required
lastName String required
gender Gender required
email String required pattern(/^[^@\s]+@[^@\s]+\.[^@\s]+$/)
phone String required
addressLine1 String required
addressLine2 String
city String required
country String required
}
enum Gender {
MALE, FEMALE, OTHER
}
Customer 实体包含使用在线购物门户的客户的详细信息。大多数字段都被标记为必填项,email 字段有正则表达式模式验证。gender 字段是一个枚举类型。此实体与我们将很快详细了解的系统用户相关。
- 将以下关于
ProductOrder和OrderItem的片段添加到 JDL Studio 编辑器中:
entity ProductOrder {
placedDate Instant required
status OrderStatus required
code String required
}
enum OrderStatus {
COMPLETED, PENDING, CANCELLED
}
entity OrderItem {
quantity Integer required min(0)
totalPrice BigDecimal required min(0)
status OrderItemStatus required
}
enum OrderItemStatus {
AVAILABLE, OUT_OF_STOCK, BACK_ORDER
}
ProductOrder和OrderItem实体用于跟踪客户制作的产品订单。ProductOrder包含订单的placedDate、status和code,这些都是必需字段,而OrderItem包含关于单个项目的quantity、totalPrice和status的信息。所有字段都是必需的,而quantity和totalPrice字段有最小值验证。OrderStatus和OrderItemStatus是枚举字段。
- 将以下关于
Invoice和Shipment的片段添加到 JDL Studio 编辑器中:
entity Invoice {
date Instant required
details String
status InvoiceStatus required
paymentMethod PaymentMethod required
paymentDate Instant required
paymentAmount BigDecimal required
}
enum InvoiceStatus {
PAID, ISSUED, CANCELLED
}
enum PaymentMethod {
CREDIT_CARD, CASH_ON_DELIVERY, PAYPAL
}
entity Shipment {
trackingCode String
date Instant required
details String
}
Invoice和Shipment实体分别用于跟踪产品订单的发票和运输。Invoice中的大多数字段是必需的,而status和paymentMethod字段是枚举类型。
枚举被用来包含某些字段的范围,这为这些字段提供了更细粒度的控制。
关系
现在我们已经定义了我们的实体,让我们在它们之间添加关系:
- 将以下关于关系片段添加到 JDL Studio 编辑器中:
relationship OneToOne {
Customer{user} to User
}
首先声明的关系是一个Customer实体和内置的User实体之间的单向OneToOne关系:
Customer (1) -----> (1) User
这意味着Customer实体了解User并且是关系的所有者,但User并不知道Customer,因此我们无法从User中获取客户。这使我们能够将客户映射到User实体,并在以后用于授权目的,确保一个客户只能映射到一个系统用户。
- 将以下关于关系的片段添加到 JDL Studio 编辑器中:
relationship ManyToOne {
OrderItem{product} to Product
}
这个声明定义了从OrderItem到Product的单向ManyToOne关系:
OrderItem (*) -----> (1) Product
这意味着OrderItem了解它们的Product,但Product并不知道OrderItem。这样设计保持了代码的整洁性,因为我们不希望在这个用例中从产品中了解订单。将来,如果我们想了解为某个产品制作的订单,我们可以使这种关系双向。
- 将以下关于关系片段添加到 JDL Studio 编辑器中:
relationship OneToMany {
Customer{order} to ProductOrder{customer},
ProductOrder{orderItem} to OrderItem{order},
ProductOrder{invoice} to Invoice{order},
Invoice{shipment} to Shipment{invoice},
ProductCategory{product} to Product{productCategory}
}
这个声明很有趣,因为我们有多个OneToMany声明:
Customer (1) <-----> (*) ProductOrder
ProductOrder (1) <-----> (*) OrderItem
ProductOrder (1) <-----> (*) Invoice
Invoice (1) <-----> (*) Shipment
ProductCategory (1) <-----> (*) Product
它们都是双向的,意味着源实体和目标实体都知道对方。
我们声明一个Customer可以有多个ProductOrder,ProductOrder可以有多个OrderItem和发票,Invoice可以有多个Shipment,而ProductCategory可以有多个Product。从目标实体出发,源实体被映射为ManyToOne。
实体选项
将以下关于选项片段添加到 JDL Studio 编辑器中:
service * with serviceClass
paginate Product, Customer, ProductOrder, Invoice, Shipment, OrderItem with pagination
在选项中,我们保持简单,声明我们想要为所有实体创建服务类。我们还为可能随着时间的推移获得大量条目的某些实体启用了分页。
图表显示了完整的模型,包括所有实体及其在 JDL Studio 中显示的关系:

现在,让我们将此 JDL 文件下载到我们的文件系统中:
-
点击 JDL Studio 应用程序右上角的下载按钮。
-
将文件以
online-store.jh的名称保存在我们在上一章创建应用程序的online-store目录中。
使用 JHipster 生成实体
现在,是时候使用我们的 JDL 生成领域模型了。我们将使用 JHipster 的 import-jdl 命令来完成此操作。
打开你喜欢的终端应用程序,导航到我们之前创建应用程序的 online-store 文件夹。然后,执行 import-jdl 命令:
> cd online-store
> jhipster import-jdl online-store.jh
这将触发实体创建过程,你将被要求确认覆盖现有文件以应用更改。请查看以下截图:

输入 a 以确认覆盖所有带有更改的文件。一旦文件生成,JHipster 将触发一个 yarn webpack:build 步骤来重新构建客户端代码。完成后,你将看到如下所示的成功消息:

在终端上运行 git status 显示我们修改了五个文件,并添加了许多新文件。让我们将更改提交到 Git。执行这里显示的命令:
> git add --all
> git commit -am "generated online store entity model"
生成代码流程
现在,让我们看看生成了什么。让我们在我们的首选 IDE/编辑器中打开应用程序代码。让我们看看为 Product 实体生成了什么。
你可能已经注意到项目根目录中有一个 .jhipster 文件夹,如果你查看它,你会看到一些 JSON 文件。让我们看看 Product.json。它包含有关生成实体的元数据,并由 JHipster 在需要时用于重新生成和编辑实体:
{
"fluentMethods": true,
"relationships": [
{
"relationshipType": "many-to-one",
"relationshipName": "productCategory",
"otherEntityName": "productCategory",
"otherEntityField": "id"
}
],
"fields": [
{
"fieldName": "name",
"fieldType": "String",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "description",
"fieldType": "String"
},
{
"fieldName": "price",
"fieldType": "BigDecimal",
"fieldValidateRules": [
"required",
"min"
],
"fieldValidateRulesMin": 0
},
{
"fieldName": "size",
"fieldType": "Size",
"fieldValues": "S,M,L,XL,XXL",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "image",
"fieldType": "byte[]",
"fieldTypeBlobContent": "image"
}
],
"changelogDate": "20180114123458",
"javadoc": "Product sold by the Online store",
"entityTableName": "product",
"dto": "no",
"pagination": "pagination",
"service": "serviceClass",
"jpaMetamodelFiltering": false
}
服务器端源代码
现在让我们看看生成的服务器端代码。
实体领域类
在 src/main/java/com/mycompany/store/domain 文件夹中,你会找到实体领域对象。打开 Product.java:
@ApiModel(description = "Product sold by the Online store")
@Entity
@Table(name = "product")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Column(name = "name", nullable = false)
private String name;
@Column(name = "description")
private String description;
@Lob
@Column(name = "image")
private byte[] image;
@Column(name = "image_content_type")
private String imageContentType;
@NotNull
@DecimalMin(value = "0")
@Column(name = "price", precision=10, scale=2, nullable = false)
private BigDecimal price;
@NotNull
@Enumerated(EnumType.STRING)
@Column(name = "jhi_size", nullable = false)
private Size size;
@ManyToOne
private ProductCategory productCategory;
// jhipster-needle-entity-add-field - JHipster will add fields
here, do not remove
... // getters
public Product name(String name) {
this.name = name;
return this;
}
... // setters
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here, do not remove
... // equals, hashcode and toString methods
}
实体类定义了字段和关系。
@ApiModel(description = "Product sold by the Online store")
这个注解由 Swagger 使用,当实体在端点中使用时显示有用的文档:
@Entity
@Table(name = "product")
这些是 JPA 注解,它们声明 POJO 为实体并将其映射到 SQL 表:
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
这是一个 Hibernate 注解,它允许我们为这个实体启用二级缓存。在我们的案例中,使用 Hazelcast:
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
id 字段是特殊的,它被映射为一个生成值字段。根据数据库的不同,这个字段将使用原生生成技术或 Hibernate 提供的序列。由于我们使用 MySQL,它将使用原生的数据库主键生成技术:
@Column(name = "name", nullable = false)
这个 JPA 注解用于将列映射到字段,并且也可以用于声明字段的属性,如 nullable、precision、scale、unique 等:
@NotNull
@DecimalMin(value = "0")
这些是 Bean 验证注解,用于对字段进行验证:
@Lob
@Column(name = "image")
private byte[] image;
@Column(name = "image_content_type")
private String imageContentType;
图片字段是一个 Blob,由于我们使用 MySQL,它被标记为 Lob 类型。它还有一个额外的字段来存储内容类型信息:
@Enumerated(EnumType.STRING)
使用枚举注解映射枚举字段。这些字段在数据库中存储为简单的varchar字段:
@ManyToOne
private ProductCategory productCategory;
关系使用@ManyToOne、@OneToMany、@OneToOne和@ManyToMany等注解进行映射。
在这里,ProductCategory被映射为ManyToOne;在关系的另一侧,Product被映射为OneToMany,如下所示:
@OneToMany(mappedBy = "productCategory")
@JsonIgnore
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Product> products = new HashSet<>();
如你所见,关系还指定了一个缓存。它告诉 Jackson 在转换为 JSON 时忽略该字段,以避免循环引用,因为ProductCategory已经在Product实体中进行了映射:
public Product name(String name) {
this.name = name;
return this;
}
这是一个默认生成的流畅设置器,与标准设置器一起生成。可以通过在 JDL 中指定实体的noFluentMethod来关闭它。流畅方法很方便,因为它们允许我们像以下这样链式设置设置器,以编写更简洁的代码:
new Product().name("myProduct").price(10);
相应的表定义和约束使用 Liquibase 创建,可以在src/main/resources/config/liquibase/changelog中找到,文件名为<timestamp>_added_entity_Product和<timestamp>_added_entity_constraints_Product.xml,这些文件在重新加载或再次启动应用程序时自动应用于数据库。
实体的仓库接口
在src/main/java/com/mycompany/store/repository文件夹中,你可以找到实体仓库服务。打开ProductRepository.java:
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
}
仓库服务只是一个空的接口,它扩展了JpaRepository类。由于它是一个 Spring Data 仓库,实现是自动创建的,允许我们使用这个简单的接口声明执行所有 CRUD 操作。可以轻松地在此处添加额外的仓库方法。我们将在下一章中看到这一点。
实体的服务类
由于我们选择为我们的实体生成服务类,让我们看看其中一个。在src/main/java/com/mycompany/store/service文件夹中,你可以找到实体仓库服务。打开ProductService.java:
@Service
@Transactional
public class ProductService {
private final Logger log = LoggerFactory.getLogger(ProductService.class);
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
...
}
该服务使用构造函数注入来获取其依赖项,这些依赖项由 Spring 在 bean 实例化期间自动注入。该服务也被标记为@Transactional,以启用数据访问的事务管理。该服务定义了 CRUD 操作方法。例如,findAll方法调用等效的仓库方法,同时向其中添加只读事务规则。你可以看到该方法已经支持分页,并以Page的形式返回结果。Page和Pageable对象由 Spring 提供,使我们能够轻松控制分页:
@Transactional(readOnly = true)
public Page<Product> findAll(Pageable pageable) {
log.debug("Request to get all Products");
return productRepository.findAll(pageable);
}
实体的资源类
在src/main/java/com/mycompany/store/web/rest文件夹中,你可以找到实体资源服务。打开ProductResource.java:
@RestController
@RequestMapping("/api")
public class ProductResource {
...
}
资源充当控制器层,在我们的案例中,它为客户端代码提供 REST 端点。端点有一个基础映射到 "/api":
@GetMapping("/products")
@Timed
public ResponseEntity<List<Product>> getAllProducts(Pageable
pageable) {
log.debug("REST request to get a page of Products");
Page<Product> page = productService.findAll(pageable);
HttpHeaders headers =
PaginationUtil.generatePaginationHttpHeaders(page,
"/api/products");
return new ResponseEntity<>(page.getContent(), headers,
HttpStatus.OK);
}
所有 CRUD 动作都有等效的映射方法,例如,getAllProducts 映射到服务中的 findAll。资源还通过添加适当的分页头处理分页。
客户端
实体的客户端资源在 src/main/webapp/app/entities 文件夹中创建。让我们看看在 product 文件夹中为 Product 实体创建的代码。
实体的 TypeScript 模型类
让我们看看在 product.model.ts 中生成的 TypeScript 模型。它直接映射到域对象:
export class Product implements IProduct {
constructor(
public id?: number,
public name?: string,
public description?: string,
public imageContentType?: string,
public image?: any,
public price?: number,
public size?: Size,
public productCategory?: IProductCategory
) {
}
}
字段都是可选的,这使得在没有任何值的情况下创建对象实例成为可能。你还会看到枚举也在文件中的模型旁边生成。
实体的 Angular 服务
ProductService 是一个与我们的 REST 端点交互的 Angular 服务,并在 product.service.ts 中创建:
@Injectable()
export class ProductService {
private resourceUrl = SERVER_API_URL + 'api/products';
constructor(private http: HttpClient) { }
...
query(req?: any): Observable<HttpResponse<Product[]>> {
const options = createRequestOption(req);
return this.http.get<Product[]>(
this.resourceUrl,
{ params: options, observe: 'response' }
)
.map((res: HttpResponse<Product[]>) => this.convertArrayResponse(res));
}
...
}
如你所见,服务有一个构造函数,其中注入了依赖项,遵循与我们的服务器端代码类似的模式。有方法映射所有 CRUD 动作到后端 REST 资源。HTTP 调用利用 RxJS Observables 提供异步流式 API,这比基于 Promise 的 API 要好得多。
同样在 product-popup.service.ts 中定义了 ProductPopupService,这是一个用于打开弹出对话框进行实体编辑和删除的实用服务。
实体的 Angular 组件
对于一个实体,有六个组件类在四个文件和四个 HTML 文件中生成,这些文件用于组件。
在 product.component.ts 中定义的 ProductComponent 处理主列表屏幕。它使用 product.component.html 作为模板。该组件管理视图及其动作。它还调用多个服务来获取数据以及执行其他动作,如警报和事件广播:
@Component({
selector: 'jhi-product',
templateUrl: './product.component.html'
})
export class ProductComponent implements OnInit, OnDestroy {
...
}
product-dialog.component.ts 定义了 ProductDialogComponent 和 ProductPopupComponent,它们使用 template product-dialog.component.html 处理创建/编辑对话框页面:
@Component({
selector: 'jhi-product-dialog',
templateUrl: './product-dialog.component.html'
})
export class ProductDialogComponent implements OnInit {
...
}
@Component({
selector: 'jhi-product-popup',
template: ''
})
export class ProductPopupComponent implements OnInit, OnDestroy {
...
}
ProductDetailComponent 使用 product-detail.component.html 作为模板来处理详细视图屏幕,并在 product-detail.component.ts 中定义。
在 product-delete-dialog.component.ts 中定义的 ProductDeleteDialogComponent 和 ProductDeletePopupComponent 使用 product-delete-dialog.component.html 作为模板来管理删除弹出对话框。
实体的 Angular 路由
我们需要一个路由声明,以便我们可以访问实体页面。这已在 product.route.ts 中声明。
例如,这声明了实体的详细视图:
{
path: 'product/:id',
component: ProductDetailComponent,
data: {
authorities: ['ROLE_USER'],
pageTitle: 'storeApp.product.home.title'
},
canActivate: [UserRouteAccessService]
}
数据属性用于传递元数据,例如允许的角色和页面标题到组件。在 canActivate 属性中定义的 UserRouteAccessService 决定用户是否有权限查看页面,并使用权限元数据和认证详情进行验证。具有弹出窗口的路由声明了 outlet: 'popup' 属性。
实体的 Angular 模块
最后,我们有一个用于实体的模块。Angular 模块可以用来整合实体的所有组件、指令、管道和服务,以便它们可以轻松地导入到其他模块中。StoreProductModule 模块在 product.module.ts 中定义:
@NgModule({
imports: [
StoreSharedModule,
RouterModule.forChild(ENTITY_STATES)
],
declarations: [
ProductComponent,
ProductDetailComponent,
ProductDialogComponent,
ProductDeleteDialogComponent,
ProductPopupComponent,
ProductDeletePopupComponent,
],
entryComponents: [
ProductComponent,
ProductDialogComponent,
ProductPopupComponent,
ProductDeleteDialogComponent,
ProductDeletePopupComponent,
],
providers: [
ProductService,
ProductPopupService,
ProductResolvePagingParams,
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class StoreProductModule {}
该模块声明了组件并注册了它提供的服务。该模块还导入共享模块,以便它可以访问共享服务和组件。该模块由 entity.module.ts 下的 StoreEntityModule 在 src/main/webapp/app/entities 中导入。
生成的页面
让我们启动应用程序以查看生成的页面。在终端中执行以下 Gradle 命令:
> ./gradlew
这将在本地以开发模式启动服务器。由于 import-jdl 步骤已经编译了前端代码,我们不需要运行 yarn start 只是为了查看新页面,但请注意,为了进一步开发,最好使用 yarn start 与前面的命令一起使用。如果你在生成实体时服务器已经运行,那么不需要运行此命令,而是只需使用 ./gradlew compileJava 命令重新编译源代码。使用你的 IDE 和 Spring devtools 将为你热重载应用程序。如果你有 yarn start 运行,客户端也将进行热重载,否则,它只会刷新页面。我们将在下一章中了解更多关于热重载的信息。
一旦你看到以下消息,服务器就准备好了,我们可以通过我们喜欢的浏览器导航到 URL http://localhost:8080:
----------------------------------------------------------
Application 'store' is running! Access URLs:
Local: http://localhost:8080
External: http://192.168.2.7:8080
Profile(s): [swagger, dev]
----------------------------------------------------------
如果你还没有登录,请使用默认管理员用户 admin 和密码通过点击主页上的登录链接进行登录。登录后,点击菜单中的实体链接,你将看到所有我们的实体都列在那里:

点击产品,你将看到产品列表屏幕。目前还没有任何项目,因为我们还没有创建任何:

让我们创建一个实体,点击屏幕上的创建新产品按钮,你将看到创建或编辑产品的弹出对话框:

输入 名称、描述、价格 和 大小。通过点击选择文件按钮选择一个图片。不用担心产品类别,因为我们还没有创建任何类别。现在点击保存,弹出窗口将消失,并且列表屏幕将刷新并显示成功消息:

产品屏幕现在显示我们的新实体,并带有查看、编辑和删除按钮。底部还有分页链接。通过点击每个按钮来探索查看、编辑和删除按钮。
运行生成的测试
让我们运行所有测试以确保生成的测试代码运行良好。
使用命令行运行服务器端单元/集成测试、客户端 Karma 单元测试和 Protractor 端到端测试。在一个新的终端中,导航到应用程序源文件夹并执行以下命令。它们应该以成功消息结束。确保应用程序正在运行,因为端到端测试需要它。如果应用程序尚未运行,请先通过在终端中运行 ./gradlew 来启动它:
> ./gradlew test && yarn test && yarn e2e
摘要
在本章中,我们看到了如何使用 JDL 来建模和创建实体。我们还探讨了创建源代码的重要方面。我们还浏览了创建的实体模块,并看到了它们在实际中的应用。在下一章中,我们将看到如何利用 JHipster 进一步开发应用程序,并包括特定的业务逻辑和调整。我们还将深入了解所使用的某些技术。
第五章:定制和进一步开发
在上一章中,我们看到了如何使用 JHipster 领域语言来建模和生成我们的领域模型。我们还学习了实体关系和 import-jdl 子生成器。在本章中,我们将看到如何进一步定制和添加业务逻辑到生成的应用程序以满足我们的需求。我们将学习以下内容:
-
使用 Spring DevTools 和 BrowserSync 进行实时重新加载
-
为实体定制 angular 前端
-
编辑使用 JHipster 实体生成器创建的实体
-
使用 Bootstrap 主题更改应用程序的外观和感觉
-
使用 JHipster 语言生成器添加新的 i18n 语言
-
使用 Spring Security 添加基于角色的额外授权来定制生成的 REST API
-
创建新的 Spring Data JPA 查询和方法
开发时的实时重新加载
在开发应用程序时,最令人烦恼和耗时的一部分是重新编译代码和重新启动服务器以查看我们所做的代码更改。传统上,JavaScript 代码更容易,因为它不需要编译,你只需刷新浏览器就能看到更改。然而,尽管当前的 MVVM 堆栈使客户端比以前更重要,但它们也引入了副作用,如客户端代码的转译等。所以,如果你正在重构实体的一个字段,你传统上需要执行以下任务才能在浏览器中看到更改:
-
编译服务器端 Java 代码。
-
将表更改应用到数据库中。
-
重新编译客户端代码。
-
重新启动应用程序服务器。
-
刷新浏览器。
这需要花费很多时间,每次进行小改动时都令人沮丧,导致你在检查之前做出更多更改,从而影响生产力。
如果我告诉你,你不必做任何这些事情,而且所有这些都可以在你使用 IDE 保存更改时自动发生,那会怎么样?那会非常棒,不是吗?
使用 JHipster,你将得到完全相同的功能。JHipster 使用 Spring Boot DevTools、webpack 开发服务器和 BrowserSync 来为端到端代码提供良好的实时重新加载功能。
让我们快速了解一下所使用的这些技术。
Spring Boot DevTools
Spring Boot DevTools (docs.spring.io/spring-boot/docs/current/reference/html/using-boot-devtools.html) 允许 Spring Boot 应用程序在类路径发生变化时重新加载嵌入的服务器。它声明如下——本模块的目的是尝试改善在开发 Spring Boot 应用程序时的开发体验,并且它确实做到了这一点。它使用自定义类加载器在类更新和重新编译时重启应用程序,并且由于服务器是热重载的,所以比冷启动快得多。
它不如 JRebel 或类似技术那样酷,这些技术可以即时重新加载,但它比手动操作要好,并且不需要任何额外配置即可启用。
JHipster DevTools 在 dev 配置文件中自动启用,使用可以自动在保存时重新编译类的 IDE。DevTools 将确保应用程序重新加载并保持最新。由于使用了 Liquibase,任何使用正确变更日志的架构更新也将得到更新。请确保不要更改现有的变更日志,因为这会导致校验和错误。应用程序的重新加载也可以通过简单地使用命令 mvnw compile 或 gradlew compileJava 来触发,具体取决于所使用的构建工具。
如果你选择 NoSQL 数据库,例如 MongoDB、Cassandra 或 Couchbase,JHipster 也为这些数据库提供了数据库迁移工具。
Webpack 开发服务器和 BrowserSync
Webpack 开发服务器 (github.com/webpack/webpack-dev-server) 使用 webpack 开发中间件提供了一个简单的 Express 服务器,并在资源更改时支持实时重新加载。Webpack 开发中间件支持热模块替换和内存文件访问等功能。
在 Webpack 版本 4 及以上版本中,使用了一个名为 webpack-serve (github.com/webpack-contrib/webpack-serve) 的新替代方案,而不是 Webpack 开发服务器。它利用了较新浏览器中的原生 WebSocket 支持。
BrowserSync (browsersync.io/) 是一个 Node.js 工具,它通过同步多个浏览器和设备上网页的文件更改和交互来帮助进行浏览器测试。它提供了诸如文件更改时的自动重新加载、同步 UI 交互、滚动等功能。JHipster 将 BrowserSync 与 Webpack 开发服务器集成,以提供高效的开发环境。这使得在不同的浏览器和设备上测试网页变得非常简单。CSS 的更改无需刷新浏览器即可加载。
要在客户端使用实时重新加载,你需要运行 yarn start,这将启动开发服务器并打开指向 http://localhost:9000 的浏览器。注意端口号 9000。BrowserSync 将使用此端口,而应用程序的后端将在 8080 上提供服务,所有请求将通过 webpack 开发中间件代理。
打开另一个浏览器,例如,如果 BrowserSync 已经打开了 Chrome,则打开 Firefox,反之亦然。现在将它们并排放置,并与应用程序交互。你会看到你的操作被复制,多亏了 BrowserSync。尝试更改一些代码并保存文件,以查看实时重新加载的效果。
为应用程序设置实时重新加载
让我们开始为创建的应用程序设置完美的开发环境。在终端中,通过运行 ./gradlew 以开发模式启动服务器,在另一个终端中,通过运行 yarn start 启动客户端开发服务器。
现在当你在服务器端进行任何更改时,只需运行./gradlew compileJava,或者如果你使用的是 IDE,点击编译按钮。
使用 IntelliJ IDEA,文件会自动保存,因此你可以设置 Ctrl + S 来编译类,从而提供一个良好的工作流程。在 Eclipse 中,保存类会自动编译。
当你在客户端进行更改时,只需保存文件并启动 webpack 开发服务器和 BrowserSync,它将完成剩余的工作。
为实体自定义 Angular 前端
现在我们已经创建了工作着的实体领域模型,让我们让它更易于使用。产品列表屏幕由 JHipster 生成的表格视图;它对于简单的 CRUD 操作来说是足够的,但并不是最适合想要浏览我们的产品列表的最终用户的用户体验。让我们看看我们如何轻松地将其更改为更吸引人的东西。我们还将添加一个不错的客户端筛选选项来筛选列表。我们将使用 Angular 和 Bootstrap 的功能来完成这项工作。
首先,让我们找到我们需要编辑的源代码。在你的首选编辑器/IDE 中导航到src/main/webapp/app/entities/product:

让我们从自定义product.component.html文件开始,以更新产品列表的 UI 视图。当前的 HTML 代码渲染一个表格视图,并使用一些 Angular 指令来增强视图,包括排序和分页。让我们首先将视图从表格更改为列表,但首先通过导航到http://localhost:9000打开 BrowserSync 的开发 web 服务器(如果尚未打开)。登录并导航到实体 | 产品类别,创建一个类别,然后导航到实体 | 产品,创建一些新产品,以便我们有东西可以列出:

我们可以使用 Bootstrap 列表组(getbootstrap.com/docs/4.0/components/list-group/)组件来实现这个目的。让我们使用以下代码片段并更改视图。将div替换为class="table-responsive"的以下代码:
<div *ngIf="products">
<div class="*list-group*">
<a [routerLink]="['../product', product.id ]"
class="*list-group-item list-group-item-action* flex-column
align-items-start"
*ngFor="let product of products; trackBy: trackId">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{product.name}}</h5>
<small *ngIf="product.productCategory">
<a [routerLink]="['../product-category',
product.productCategory?.id ]" >
{{product.productCategory?.id}}
</a>
</small>
</div>
<small class="mb-1">{{product.description}}</small>
<p class="mb-1">Price: {{product.price}}</p>
<small>
Size:
<span jhiTranslate="{{'storeApp.Size.' +
product.size}}">
{{product.size}}
</span>
</small>
</a>
</div>
</div>
如你所见,我们正在使用 Angular 指令*ngFor="let product of products; trackBy: trackId"在锚点元素上迭代产品,以便为列表中的每个产品创建元素。我们用*ngIf="products"指令包裹这个,这样只有在产品对象定义时才会渲染视图。[routerLink]="['../product', product.id ]"指令将使用 Angular 路由为锚点创建一个 href,这样我们就可以导航到特定的产品路由。然后我们使用产品的属性在模板字符串中使用{{product.name}}语法进行渲染。当你保存代码时,你可能会注意到视图会自动刷新,这要归功于 BrowserSync。
在ngFor中使用的trackBy函数让 Angular 决定哪些项目被添加或从集合中删除。这提高了渲染性能,因为现在 Angular 可以精确地找出需要添加或从 DOM 中删除哪些项目,而不必重新创建整个集合。在这里,我们提供trackId作为函数来唯一标识集合中的项目。
这将产生以下结果:

虽然这是一个好的开始,但还不够。所以,让我们进去并让它变得更好。首先,让我们将图片添加到列表中。修改代码以添加 Bootstrap 行和列,如下所示,原始的渲染内容代码被移动到第二列,并且保持不变:
<div *ngIf="products">
<div class="list-group">
<a [routerLink]="['../product', product.id ]" class="list-group-item list-group-item-action flex-column align-items-start"
*ngFor="let product of products; trackBy: trackId">
<div class="row">
<div class="col-2 col-xs-12 justify-content-center">
<img [src]="'data:' + product.imageContentType +
';base64,' + product.image"
style="max-height:150px;" alt="product image"/>
</div>
<div class="col col-xs-12">
<div class="d-flex w-100 justify-content-between">
...
</div>
<small class="mb-1">{{product.description}}</small>
<p class="mb-1">Price: {{product.price}}</p>
<small>
...
</small>
</div>
</div>
</a>
</div>
</div>
看一下加粗的代码。我们添加了一个 Bootstrap 行,其中包含两个列 div,第一个 div 在 12 列网格中占用两个列,指定为col-2,同时我们还说当显示为xs(额外小)时,div标签应该使用col-xs-12占用 12 列。第二个div通过仅指定col来保持响应式,它占用第一个div之后的剩余可用列,当显示为额外小尺寸时,它也占用 12 列。第一个列div中的图片使用数据 URL 作为src来渲染图片。现在我们有一个更好的视图:

我们可以进一步润色它。我们可以使用 Angular 货币管道(angular.io/api/common/CurrencyPipe)来显示价格,并通过将其更改为{{product.price | currency:'USD'}}来移除其冗余标签。我们还可以为列表右侧显示的分类添加一个标签。
最后,我们可以将编辑和删除按钮重新添加回来,但我们只需要为具有ADMIN角色的用户显示它们,这样普通用户就只能查看产品列表。我们可以从原始表格中复制edit和delete按钮的 HTML 代码。最终的代码如下:
<div *ngIf="products">
<div class="list-group">
<a [routerLink]="['../product', product.id ]"
class="list-group-item list-group-item-action flex-column
align-items-start"
*ngFor="let product of products; trackBy: trackId">
<div class="row">
<div class="col-2 col-xs-12 justify-content-center">
<img [src]="'data:' + product.imageContentType +
';base64,' + product.image"
style="max-height:150px;" alt="product image"/>
</div>
<div class="col col-xs-12">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{product.name}}</h5>
<small *ngIf="product.productCategory">
<a [routerLink]="['../product-category',
product.productCategory?.id ]" >
Category: {{product.productCategory?.id}}
</a>
</small>
</div>
<small class="mb-1">{{product.description}}</small>
<p class="mb-1">{{product.price | currency:'USD'}}</p>
<small>
Size:
<span jhiTranslate="{{'storeApp.Size.' +
product.size}}">
{{product.size}}
</span>
</small>
<div *jhiHasAnyAuthority="'ROLE_ADMIN'">
<button type="submit"
[routerLink]="['/',
{ outlets: { popup: 'product/'+
product.id + '/edit'} }]"
replaceUrl="true"
queryParamsHandling="merge"
class="btn btn-primary btn-sm">
<span class="fa fa-pencil"></span>
<span class="d-none d-md-inline"
jhiTranslate="entity.action.edit">Edit</span>
</button>
<button type="submit"
[routerLink]="['/',
{ outlets: { popup: 'product/'+
product.id + '/delete'} }]"
replaceUrl="true"
queryParamsHandling="merge"
class="btn btn-danger btn-sm">
<span class="fa fa-remove"></span>
<span class="d-none d-md-inline"
jhiTranslate="entity.action.delete">Delete</span>
</button>
</div>
</div>
</div>
</a>
</div>
</div>
*jhiHasAnyAuthority="'ROLE_ADMIN'"指令由 JHipster 提供,可以用于根据用户角色控制展示。默认情况下,JHipster 提供ROLE_ADMIN和ROLE_USER,但仅在客户端控制这并不安全,因为它很容易被绕过,所以我们应该在服务器端也进行安全控制。我们将在本章后面讨论这个问题。使用用户账户注销并重新登录以查看指令的作用:

现在,让我们也将*jhiHasAnyAuthority="'ROLE_ADMIN'"指令添加到创建按钮元素中。
现在既然我们的视图已经变得更好,让我们恢复我们最初拥有的排序功能。由于我们没有表头了,我们可以使用一些按钮根据某些重要的字段进行排序。
让我们使用 Bootstrap 按钮组(getbootstrap.com/docs/4.0/components/button-group/) 来实现这个功能。将以下代码片段放置在我们之前创建的 <div class="list-group"> 元素上方:
<div class="mb-2 d-flex justify-content-end align-items-center">
<span class="mx-2 col-1">Sort by</span>
<div class="btn-group" role="group"
jhiSort [(predicate)]="predicate" [(ascending)]="reverse"
[callback]="transition.bind(this)">
<button type="button" class="btn btn-light" jhiSortBy="name">
<span jhiTranslate="storeApp.product.name">Name</span>
<span class="fa fa-sort"></span>
</button>
<button type="button" class="btn btn-light" jhiSortBy="price">
<span jhiTranslate="storeApp.product.price">Price</span>
<span class="fa fa-sort"></span>
</button>
<button type="button" class="btn btn-light" jhiSortBy="size">
<span jhiTranslate="storeApp.product.size">Size</span>
<span class="fa fa-sort"></span>
</button>
<button type="button" class="btn btn-light"
jhiSortBy="productCategory.id">
<span
jhiTranslate="storeApp.product.productCategory">Product Category</span>
<span class="fa fa-sort"></span>
</button>
</div>
</div>
我们可以使用 Bootstrap 外边距和 flexbox 工具类,如 mb-2 d-flex justify-content-end align-items-center 来正确定位和排列项目。我们在一个 div 元素上使用 btn-group 类来将按钮元素组合在一起,并在这些按钮上放置了 jhiSort 指令及其绑定的属性,如 predicate、ascending 和 callback。在按钮本身上,我们使用 jhiSortBy 指令来指定它将使用哪个字段进行排序。现在我们的页面看起来如下,产品按价格排序:

最后,让我们为页面添加一些传统的客户端过滤功能。
JHipster 提供了一个选项,可以使用 JPA 元模型启用服务器端过滤。另一个选项是启用 Elasticsearch,对于每个实体,JHipster 将自动创建全文搜索字段。因此,对于任何严肃的过滤需求,你应该使用这些。
首先,让我们在 product.component.ts 文件中的 ProductComponent 类中添加一个新的字符串类型实例变量 filter:
export class ProductComponent implements OnInit, OnDestroy {
...
filter: string;
constructor(
...
) {
...
}
...
}
现在,让我们在 product.component.html 文件中使用这个变量。将以下代码中的高亮部分添加到我们为排序按钮创建的 div 中:
<div class="mb-2 d-flex justify-content-end align-items-center">
<span class="mr-2 col-2">Filter by name</span>
<input type="search" class="form-control" [(ngModel)]="filter">
<span class="mx-2 col-1">Sort by</span>
<div class="btn-group" role="group"
...
</div>
</div>
我们使用 ngModel 指令将过滤变量绑定到一个输入元素上,使用 [()] 确保变量的双向绑定。
[(ngModel)]="filter" 创建双向绑定,[ngModel]="filter" 从模型到视图创建单向绑定,(ngModel)="filter" 从视图到模型创建单向绑定。
最后,将我们的列表项元素上的 ngFor 指令更新如下。我们使用 JHipster 提供的管道通过产品的名称字段过滤列表:
*ngFor="let product of (products | pureFilter:filter:'name'); trackBy: trackId"
就这样,我们在屏幕上得到了一个闪亮的过滤选项:

与之前相比,用户体验有了很大的提升,但在实际应用中,你可以为面向客户的网站构建一个更好的用户界面,包括添加商品到购物车、在线支付等功能,并将这部分留给后台办公使用。让我们将这个更改提交到 git:这对于以后管理项目更改非常重要。运行以下命令:
> git add --all
> git commit -am "update product listing page UI"
使用 JHipster 实体子生成器编辑实体
在查看生成的实体屏幕时,我们可能会意识到一些影响用户体验的小问题。例如,在产品屏幕上,我们有一个与产品类别的关联,但在创建时从下拉菜单中选择产品类别,或者在列表中显示类别时,我们通过其 ID 显示类别,这对用户来说并不友好。如果我们可以显示产品类别名称,那就更好了。这是 JHipster 的默认行为,但在定义关系时可以进行自定义。让我们看看如何通过编辑 JDL 模型来使我们的生成屏幕更加用户友好。这将覆盖现有文件,但由于我们使用git,我们可以轻松地选择我们所做的更改,我们将在稍后看到这是如何完成的。
在我们的 JDL 中,我们使用以下代码定义了实体之间的关系:
relationship OneToOne {
Customer{user} to User
}
relationship ManyToOne {
OrderItem{product} to Product
}
relationship OneToMany {
Customer{order} to ProductOrder{customer},
ProductOrder{orderItem} to OrderItem{order},
ProductOrder{invoice} to Invoice{order},
Invoice{shipment} to Shipment{invoice},
ProductCategory{product} to Product{productCategory}
}
通过在 JDL 中使用(<字段名>)语法指定用于显示关系的字段,我们可以更改客户端代码显示关系的方式:
relationship OneToOne {
Customer{user(login)} to User
}
relationship ManyToOne {
OrderItem{product(name)} to Product
}
relationship OneToMany {
Customer{order} to ProductOrder{customer(email)},
ProductOrder{orderItem} to OrderItem{order(code)},
ProductOrder{invoice} to Invoice{order(code)},
Invoice{shipment} to Shipment{invoice(code)},
ProductCategory{product} to Product{productCategory(name)}
}
使用import-jdl命令来运行这个操作。该命令仅生成自上次运行以来发生变化的实体。但在运行之前,我们也要切换到一个新的分支,因为将主要更改放在单独的分支上并合并回来是一种良好的实践,这样你可以有更多的控制权:
> git checkout -b entity-update-display-name
> jhipster import-jdl online-store.jh
在这里了解更多关于 Git flow 的信息:guides.github.com/introduction/flow/。
接受文件更改,等待构建完成。现在,让我们查看实体页面,以验证显示名称是否被正确使用,并创建一些实体来尝试一下。现在我们意识到Invoice实体有空的下拉菜单,这是因为Invoice实体没有名为code的字段。由于我们在模板中使用{{invoice.order?.code}},符号?使 Angular 跳过未定义的值,从而防止渲染错误。
这很容易修复。有时我们可能想在创建实体后使用 JDL 和import-jdl命令进行一些小的更改。最好的方法是在 JDL 中进行更改,并使用导入 JDL 命令重新生成,就像我们在前面的代码中看到的那样。现在还有一个选项,即实体子生成器,它可以产生相同的结果。为了熟悉这个选项,让我们使用它来向我们的Invoice实体添加字段:
- 运行以下命令:
> jhipster entity Invoice
- 从选项中选择“是”,添加更多字段和关系:
Using JHipster version installed globally
Executing jhipster:entity Invoice
Options:
Found the .jhipster/Invoice.json configuration file, entity can be automatically generated!
The entity Invoice is being updated.
? Do you want to update the entity? This will replace the existing files for this entity, all your custom code will be overwritten
Yes, re generate the entity
❯ Yes, add more fields and relationships
Yes, remove fields and relationships
No, exit
- 对于下一个问题选择“是”,并提供后续问题中的字段名、类型和验证:
Generating field #7
? Do you want to add a field to your entity? Yes
? What is the name of your field? code
? What is the type of your field? String
? Do you want to add validation rules to your field? Yes
? Which validation rules do you want to add? Required
================= Invoice =================
Fields
date (Instant) required
details (String)
status (InvoiceStatus) required
paymentMethod (PaymentMethod) required
paymentDate (Instant) required
paymentAmount (BigDecimal) required
code (String) required
Relationships
shipment (Shipment) one-to-many
order (ProductOrder) many-to-one
Generating field #8
? Do you want to add a field to your entity? (Y/n)
-
对于后续的提示选择“n”以添加更多字段和关系。接受提议的文件更改,这样就完成了。
-
现在,请确保更新 JDL,使实体
Invoice具有code String required字段。
你也可以运行jhipster export-jdl online-store.jh来将当前模型导回到 JDL。
现在我们已经正确显示了实体关系,我们还需要确保某些实体具有必填的关系值。例如,对于客户来说,必须有一个用户,ProductOrder 必须有一个客户,订单项必须有一个订单,发票必须有一个订单,最后,运输必须有一个发票。由于 JHipster 支持使关系成为必填项,我们可以使用 JDL 进行这些更改。在 online-store.jh 中更新关系到以下片段:
relationship OneToOne {
Customer{user(login) required} to User
}
relationship ManyToOne {
OrderItem{product(name) required} to Product
}
relationship OneToMany {
Customer{order} to ProductOrder{customer(email) required},
ProductOrder{orderItem} to OrderItem{order(code) required},
ProductOrder{invoice} to Invoice{order(code) required},
Invoice{shipment} to Shipment{invoice(code) required},
ProductCategory{product} to Product{productCategory(name)}
}
现在,运行 jhipster import-jdl online-store.jh 并接受提出的更新。请确保使用 git diff 命令或你的 Git UI 工具检查更改。
让我们提交这一步,以便在需要时可以回滚:
> git add --all
> git commit -am "entity relationships display names and required update"
现在我们遇到了问题,重新生成实体覆盖了所有文件,这意味着我们丢失了我们为产品列表页面所做的所有更改,但因为我们使用了 git,所以很容易恢复。到目前为止,我们的项目只有几个提交,所以将我们为产品列表 UI 更改所做的提交 cherry-pick 并应用到当前代码库上将会很容易。然而,在现实世界的场景中,在可以重新生成 JDL 之前可能会有很多更改,因此需要一些努力来验证和合并所需更改。始终依赖拉取请求,这样你就可以看到更改了什么,其他人可以审查并发现任何问题。
让我们 cherry-pick 我们需要的更改。
请参考文档了解如何在git-scm.com/docs/git-cherry-pick中 cherry-pick 高级选项。
由于我们需要的是 master 分支上的最后一个提交,我们可以简单地使用 git cherry-pick master。我们也可以切换到 master 分支并使用 git log 命令列出提交,然后复制所需提交的提交哈希值,并使用 git cherry-pick <commit-sha>。
现在,这会导致合并冲突,因为我们当前分支的顶端提交中更新了 product.component.html 文件。我们需要从提交中获取传入的更改,但还需要更新产品类别显示名称从 ID 到代码,所以让我们接受传入的更改,并从 {{product.productCategory?.id}} 手动更新到 {{product.productCategory?.name}}。
通过暂存文件并提交来解决冲突。现在我们可以将分支合并到 master:
> git add src/main/webapp/app/entities/product/product.component.html
> git commit -am "cherrypick: update product listing page UI"
> git checkout master
> git merge --no-ff entity-update-display-name
如果你刚开始使用 Git,建议使用 SourceTree 或 GitKraken 等界面工具进行 cherry-pick 和解决合并冲突。IntelliJ 和 VSCode 等 IDE 以及编辑器也提供了良好的选项。
现在我们的页面视图应该很好:

当然,我们也可以通过将产品列表作为主页来使其更加用户友好。但就目前而言,让我们跳过这个步骤。
由于我们一直在处理客户端代码,我们没有注意到在此期间服务器端代码的更改。我们需要编译 Java 代码来重新加载我们的服务器。让我们运行 ./gradlew compileJava。
不幸的是,在重新加载过程中,我们遇到了一个错误,由于校验和错误,Liquibase 无法更新数据库变更日志:
liquibase.AsyncSpringLiquibase : Liquibase could not start correctly, your database is NOT ready: Validation Failed:
5 change sets check sum
config/liquibase/changelog/20180114123500_added_entity_Customer.xml::20180114123500-1::jhipster was: 7:3e0637bae010a31ecb3416d07e41b621 but is now: 7:01f8e1965f0f48d255f613e7fb977628
config/liquibase/changelog/20180114123501_added_entity_ProductOrder.xml::20180114123501-1::jhipster was: 7:0ff4ce77d65d6ab36f27b229b28e0cda but is now: 7:e5093e300c347aacf09284b817dc31f1
config/liquibase/changelog/20180114123502_added_entity_OrderItem.xml::20180114123502-1::jhipster was: 7:2b3d9492d127add80003e2f7723903bf but is now: 7:4beb407d4411d250da2cc2f1d84dc025
config/liquibase/changelog/20180114123503_added_entity_Invoice.xml::20180114123503-1::jhipster was: 7:5afaca031815e037cad23f0a0f5515d6 but is now: 7:fadec7bfabcd82dfc1ed22c0ba6c6406
config/liquibase/changelog/20180114123504_added_entity_Shipment.xml::20180114123504-1::jhipster was: 7:74d9167f5da06d3dc072954b1487e11d but is now: 7:0b1b20dd4e3a38f7410b6b3c81e224fd
这是因为 JHipster 对原始变更日志所做的更改。在一个理想的世界里,新的架构更改应该在新的变更日志中完成,以便 Liquibase 可以应用它们,但 JHipster 目前还没有默认生成。对于使用 H2 数据库的本地开发,我们可以运行 ./gradlew clean 来清除数据库并重新启动应用程序,但在实际使用案例中,你可能会使用实际的数据库,并且希望保留数据,因此我们在这里必须手动使用 Liquibase 提供的 diff 功能来处理这个问题。
JHipster 在 Gradle 和 Maven 构建中提供了 Liquibase 的集成。你可以利用它来创建新的变更日志和创建 diff 变更日志。在这些情况下,当我们希望保留数据并解决冲突时,Liquibase 的 diff 功能是我们的好朋友。使用 Gradle,你可以运行 ./gradlew liquibaseDiffChangeLog 命令来创建更改集和数据库的 diff 变更日志。你可以将这个更改集添加到 src/main/resources/config/liquibase/master.xml 文件中,它将在你下次重新启动服务器时应用。默认情况下,该命令配置为针对开发数据库运行,如果你想针对生产数据库运行,只需更新 gradle/liquibase.gradle 文件中的 liquibaseCommand 命令定义,并包含生产数据库的详细信息。有关更多信息,请参阅 www.jhipster.tech/development/#using-a-database。
如果你想要清除数据库中的校验和,请使用 ./gradlew liquibaseClearChecksums 任务。
改变应用程序的外观和感觉
使用 Bootstrap 的好处是它让我们能够轻松地使用任何可用的 Bootstrap 主题来改变应用程序的外观和感觉。让我们看看我们如何为我们的应用程序安装一个酷炫的主题,然后我们将使用 Bootstrap 提供的 Sass 变量来调整样式以适应我们的需求。
现在市面上有数百种 Bootstrap 主题。由于我们正在使用 Bootstrap 4,因此选择一个为 Bootstrap 4 定制的主题非常重要。
Bootswatch 是 Bootstrap 主题的精美集合;查看所有可用的主题,请访问:bootswatch.com/。
让我们使用一个名为 materia 的 Bootswatch 主题。
在你的终端中,运行 yarn add bootswatch 来安装所有主题。不用担心;我们只会导入我们想要使用的主题,所以你不需要担心安装所有主题。
现在,让我们使用 Sass 导入这个文件。打开src/main/webapp/content/scss/vendor.scss并找到行@import 'node_modules/bootstrap/scss/bootstrap';,然后添加以下加粗的代码:
// Override Boostrap variables
@import "bootstrap-variables";
@import 'node_modules/bootswatch/dist/materia/variables'; // Import Bootstrap source files from node_modules
@import 'node_modules/bootstrap/scss/bootstrap';
@import "node_modules/bootswatch/dist/materia/bootswatch";
这里主题的名称是 materia,你可以在 Bootswatch 中在这里使用任何可用的主题。确保名称全部是小写。同时,注意导入的顺序。在导入 Bootstrap 变量和主题之后导入主题变量和样式非常重要,以确保 SASS 变量和样式被正确覆盖。
我们可以通过覆盖在src/main/webapp/content/scss/_bootstrap-variables.scss中定义的 Bootstrap 变量来进一步自定义主题。
你可以覆盖 Bootstrap 支持的任何变量。支持的变量完整列表可以在node_modules/bootstrap/scss/_variables.scss中找到。
例如,让我们按照以下方式更改一些颜色,在_bootstrap-variables.scss中:
$primary: #032b4e;
$success: #1df54f;
$info: #17a2b8;
$warning: #ffc107;
$danger: #fa1a30;
当你应用一个新的主题时,可能会有一些 UI 错误,你可以通过更新生成的 SASS 文件来解决它们。
例如,将以下 CSS 添加到src/main/webapp/content/scss/global.scss中,以修复主题更改后出现的复选框错误:
.form-check-input {
height: 18px;
width: 18px;
}
我们现在有一个酷炫的新主题:

更多参考信息可以在以下链接找到:getbootstrap.com/docs/4.0/getting-started/theming/
让我们提交这个更改:
> git add --all
> git commit -am "update bootstrap theme using bootswatch"
添加新的 i18n 语言
由于我们为我们的应用程序启用了 i18n 支持,我们可以轻松地随时添加新的 i18n 语言,使用 JHipster 语言生成器添加一个新的语言到我们的应用程序。
在终端中,切换到一个新的分支并运行以下命令:
> git checkout -b add-french-language
> jhipster languages
你现在会看到一个类似这样的提示,你可以选择任何可用的语言:
Using JHipster version installed locally in current project's node_modules
Executing jhipster:languages
Options:
Languages configuration is starting
? Please choose additional languages to install (Press <space> to select, <a> to toggle all, <i> to inverse selection) >◯ Arabic (Libya)
◯ Armenian
◯ Catalan
◯ Chinese (Simplified)
◯ Chinese (Traditional)
◯ Czech
◯ Danish
(Move up and down to reveal more choices)
现在,我们先选择法语。接受文件更改建议,然后我们就可以继续了。一旦应用程序自动刷新,你可以在应用程序菜单的语言下拉菜单中看到新的语言。现在,这不是很容易吗!
现在有一个问题,因为我们有一些实体并且添加了新的语言。我们需要为实体获取 i18n 法语文件。我们可以通过运行jhipster --with-entities命令轻松完成此操作,该命令将重新生成应用程序以及实体。现在请确保仔细地仅从差异中暂存你需要的更改(与 i18n 相关的 JSON 文件),并丢弃其余的更改。以下是需要暂存的文件和文件夹:
.yo-rc.json
src/main/resources/i18n/messages_fr.properties
src/main/webapp/app/shared/language/find-language-from-key.pipe.ts
src/main/webapp/app/shared/language/language.constants.ts
webpack/webpack.common.js
src/main/webapp/i18n/fr
现在,让我们提交这个更改并将其合并回 master 分支。如果我们只选择了与 i18n 相关的更改,那么我们不应该有任何合并冲突:
> git add --all
> git commit -am "add French as additional language"
> git merge --no-ff add-french-language
使用 Spring Security 进行授权
如您可能已注意到,当涉及到生成的代码时,JHipster 在基于角色的安全、授权管理等方面并没有提供很多功能。这是故意的,因为这些功能高度依赖于具体的使用场景,通常与应用程序的业务逻辑相关。因此,最好由开发者手动编码,作为业务代码的一部分来实现。
普通用户在用户管理中分配有ROLE_USER权限,而管理员用户有ROLE_ADMIN权限。对于我们的用例,存在一些安全漏洞需要我们注意:
-
普通用户应仅能访问查看产品列表、产品订单、订单项、发票和装运。
-
普通用户不应通过 CRUD API 创建/编辑/删除实体。
-
普通用户不应能够访问其他用户的商品订单、订单项、发票和装运。
我们可以使用 Spring Security 提供的功能来克服这些问题。
限制实体的访问权限
首先,让我们限制普通用户的访问权限。这可以在 API 级别轻松使用 Spring Security 完成。将以下片段添加到src/main/java/com/mycompany/store/config/SecurityConfiguration.java的 configure 方法中。
在.antMatchers("/api/**").authenticated()行之前添加它。位置非常重要:
.antMatchers("/api/customers").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api/product-categories").hasAuthority(AuthoritiesConstants.ADMIN)
我们指定当请求路径匹配api/customers或api/product-categories时,用户应该有ROLE_ADMIN权限才能访问它们。现在注销并作为user用户登录,尝试访问客户实体页面。查看浏览器开发工具中的控制台,你应该会看到对GET http://localhost:9000/api/customers的调用返回了403 Forbidden错误。
现在既然我们的后端已经正确处理了这个问题,让我们在菜单中隐藏这些条目以供普通用户访问。让我们在src/main/webapp/app/layouts/navbar/navbar.component.html中为客户和产品类别的元素添加一个*jhiHasAnyAuthority="'ROLE_ADMIN'"指令。
现在只有管理员用户会在菜单中看到这些项目。
限制创建/编辑/删除实体的访问权限
现在我们需要确保只有管理员用户可以编辑实体,普通用户只能查看授权给他们的实体。为此,最好在 API 级别使用 Spring Security 的PreAuthorize注解来处理。让我们从订单项开始。转到src/main/java/com/mycompany/store/web/rest/OrderItemResource.java,并将@PreAuthorize("hasAuthority('ROLE_ADMIN')")添加到createOrderItem、updateOrderItem和deleteOrderItem方法中:
@DeleteMapping("/order-items/{id}")
@Timed
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public ResponseEntity<Void> deleteOrderItem(@PathVariable Long id) {
...
}
我们要求 Spring Security 拦截器仅在用户具有ROLE_ADMIN权限时提供对这些方法的访问。PreAuthorize注解会在执行方法之前阻止访问。Spring Security 还提供了PostAuthorize和更通用的Secured注解。更多关于这些内容的信息可以在 Spring Security 文档中找到:projects.spring.io/spring-security/。
使用 ./gradlew compileJava 或 IDE 编译后端。现在转到订单项页面,尝试创建一个订单项。你将在网页控制台上的 API 调用中收到一个 POST http://localhost:9000/api/order-items 403 (Forbidden) 错误。现在让我们将注解添加到所有实体 Resource 类的创建、更新和删除方法中。你可以跳过客户和产品类别实体,因为它们对 ROLE_USER 已经完全禁止访问。
让我们使用 *jhiHasAnyAuthority="'ROLE_ADMIN'" 指令从 Angular 视图中隐藏创建、编辑和删除按钮。
限制对其他用户数据的访问
现在这一点稍微有点棘手,因为它要求我们在后端的服务层更改代码,但这并不难。让我们直接开始。
让我们从产品订单实体开始。让我们修改 src/main/java/com/mycompany/store/service/ProductOrderService.java 中的 findAll 方法,如下所示:
@Transactional(readOnly = true)
public Page<ProductOrder> findAll(Pageable pageable) {
log.debug("Request to get all ProductOrders");
if (SecurityUtils.isCurrentUserInRole(AuthoritiesConstants.ADMIN)) {
return productOrderRepository.findAll(pageable);
} else
return productOrderRepository.findAllByCustomerUserLogin(
SecurityUtils.getCurrentUserLogin().get(),
pageable
);
}
如您所见,我们修改了原始的 productOrderRepository.findAll(pageable) 调用,使其仅在当前用户具有 Admin 角色时调用,否则调用 findAllByCustomerUserLogin,但我们的生成的 ProductOrderRepository 接口还没有这个方法,所以让我们添加它。在 src/main/java/com/mycompany/store/repository/ProductOrderRepository.java 中,让我们添加以下新方法。目前,该接口没有任何方法,只使用从 JpaRepository 继承的方法:
Page<ProductOrder> findAllByCustomerUserLogin(String login, Pageable pageable);
这里正在进行很多魔法操作。这是一个 Spring Data 接口,因此我们可以简单地编写一个新方法,并期望 Spring Data 自动创建实现;我们只需要遵循命名约定。在我们的用例中,我们需要找到所有用户关系为客户的订单,其登录信息与当前登录用户相同。在 SQL 中,这将是以下这样:
select * from product_order po cross join customer c cross join jhi_user u where po.customer_id=c.id and c.user_id=u.id and u.login=:login
简单来说,我们可以说 查找所有产品订单,其中 customer.user.login 等于 login,这正是我们作为 findAllByCustomerUserLogin 方法所写的。正在操作的实体是隐含的,因此省略了产品订单。通过提供 Pageable 参数,我们告诉 Spring Data 从分页实体列表中提供给我们一个页面。您可以参考 Spring Data 文档 (docs.spring.io/spring-data/jpa/docs/current/reference/html/) 获取更多信息。
在调用 productOrderRepository.findAllByCustomerUserLogin 方法时,我们可以使用 SecurityUtils.getCurrentUserLogin() 方法传递当前用户登录信息。SecurityUtils 类也是由 JHipster 生成的,它包含一些有用的方法,例如 getCurrentUserLogin、getCurrentUserJWT、isAuthenticated 和 isCurrentUserInRole。
就这些了。现在以管理员身份登录,创建两个新用户,创建两个客户,并为每个客户创建产品订单。然后注销并再次以默认用户身份登录,看看您是否可以看到新创建用户的产品订单。
现在我们为其他服务进行类似的更新。这些服务的存储库方法如下:
对于 src/main/java/com/mycompany/store/repository/InvoiceRepository:
Page<Invoice> findAllByOrderCustomerUserLogin(String login, Pageable pageable);
对于 src/main/java/com/mycompany/store/repository/OrderItemRepository:
Page<OrderItem> findAllByOrderCustomerUserLogin(String login, Pageable pageable);
对于 src/main/java/com/mycompany/store/repository/ShipmentRepository:
Page<Shipment> findAllByInvoiceOrderCustomerUserLogin(String login, Pageable pageable);
现在,我们需要对服务上的 findOne 方法进行类似的更改。
对于 ProductOrderService,如下所示:
@Transactional(readOnly = true)
public ProductOrder findOne(Long id) {
log.debug("Request to get ProductOrder : {}", id);
if (SecurityUtils.isCurrentUserInRole(AuthoritiesConstants.ADMIN)) {
return productOrderRepository.findOne(id);
} else
return productOrderRepository.findOneByIdAndCustomerUserLogin(
id,
SecurityUtils.getCurrentUserLogin().get()
);
}
如您所见,我们将查找一个通过 ID 和客户用户登录的方法进行了更改。相同的存储库方法如下:
ProductOrder findOneByIdAndCustomerUserLogin(Long id, String login);
对于 src/main/java/com/mycompany/store/repository/InvoiceRepository:
Invoice findOneByIdAndOrderCustomerUserLogin(Long id, String login);
对于 src/main/java/com/mycompany/store/repository/OrderItemRepository:
OrderItem findOneByIdAndOrderCustomerUserLogin(Long id, String login);
对于 src/main/java/com/mycompany/store/repository/ShipmentRepository:
Shipment findOneByIdAndInvoiceOrderCustomerUserLogin(Long id, String login);
同样的查询可以使用 Spring Data 提供的 @Query 注解来编写。
就这些了。我们已经为应用程序实现了良好的基于角色的授权逻辑。
让我们提交这个检查点:
> git add --all
> git commit -am "update role based authorization logic"
在现实世界的场景中,我们迄今为止所做的更改对于电子商务网站来说还不够。但鉴于我们的目标是学习 JHipster 及其支持的工具,而不是创建一个功能完美的应用程序,请将此视为一个最小可行产品。为了使这个电子商务应用程序可用,我们需要构建更多功能,例如购物车、发票生成、客户注册等。为什么不把它作为一个作业来尝试,看看您是否可以为这个应用程序构建更多功能?这将是您完成本书后的下一步行动的一部分。用例和说明将在第十四章中详细说明,JHipster 的最佳实践。
摘要
在本章中,我们看到了如何轻松定制使用 JHipster 创建的 Web 应用程序。我们还学习了在定制我们的产品列表页面时关于 Angular 和 Bootstrap 的知识。除此之外,我们还看到了如何使用 Spring Security 通过基于角色的授权来保护我们的应用程序。我们还学习了 Spring Data,并使用 Git 来正确管理我们的源代码。我们看到了我们的应用程序随着业务逻辑的发展而变得更加用户友好。在下一章中,我们将看到如何使用 Jenkins 将持续集成集成到我们的应用程序中。
第六章:测试和持续集成
现在我们已经搭建并开发了我们的电子商务应用程序,是时候让它为部署到我们的生产环境做好准备。在此之前,我们需要关注两个重要的工程方面,质量 和 稳定性。在本章中,我们将探讨如何使用现代 DevOps 实践,如持续集成和自动化测试来实现这一点。
我们还将看到以下内容:
-
修复和运行测试
-
CI/CD(持续集成/持续部署)工具
-
使用 JHipster CI-CD 子生成器设置 CI
DevOps 是一种将软件开发(Dev)和软件运营(Ops)统一在一起的软件工程实践。DevOps 的主要重点是软件工程所有阶段的自动化和监控,如开发、集成、测试、部署和基础设施管理。DevOps 是本十年最受欢迎的工程实践之一,持续集成和持续部署是其核心方面之二。
修复和运行测试
在我们深入研究持续集成工具之前,让我们首先确保我们的测试在上一章所做的更改后仍然可以正常工作并通过。在一个理想的世界里,如果软件开发是使用诸如 TDD(测试驱动开发)之类的实践进行的,那么编写和修复测试是与代码的开发同时进行的,规范是在实际开发代码之前编写的。你应该尝试遵循这一实践,以便首先编写失败的测试以实现预期的结果,然后开发使测试通过的代码。由于我们的测试是由 JHipster 自动生成的,我们至少可以确保在修改生成的代码时它们是正常工作的。
JHipster 还可以使用 Gatling 为实体生成性能测试。这非常有用,如果你正在开发一个高可用性和高流量的网站,这是必须的。这可以在创建应用程序时启用。有关更多详细信息,请参阅 www.jhipster.tech/running-tests/。
让我们运行我们的单元测试和集成测试,看看是否有任何失败的测试:
-
首先转到你的终端,并导航到 online-store 文件夹。
-
首先使用 Gradle 运行服务器端测试:
> ./gradlew test
注意,JHipster 为服务器端生成单元测试和集成测试。单元测试,文件名为 *UnitTest.java,是简单的 JUnit 测试,用于单元测试功能。集成测试,文件名为 *IntTest.java,旨在使用整个 Spring 环境测试 Spring 组件。它们使用 SpringRunner 类运行,通常启动 Spring 环境,配置所有必需的 bean,并运行测试。
一些测试失败,出现以下错误跟踪:
com.mycompany.store.web.rest.ProductOrderResourceIntTest > getProductOrder FAILED
java.lang.AssertionError at ProductOrderResourceIntTest.java:229
com.mycompany.store.web.rest.ProductOrderResourceIntTest > getAllProductOrders FAILED
java.lang.AssertionError at ProductOrderResourceIntTest.java:213
com.mycompany.store.web.rest.ProductOrderResourceIntTest > getNonExistingProductOrder FAILED
java.lang.AssertionError at ProductOrderResourceIntTest.java:242
com.mycompany.store.web.rest.ShipmentResourceIntTest > getAllShipments FAILED
java.lang.AssertionError at ShipmentResourceIntTest.java:176
com.mycompany.store.web.rest.ShipmentResourceIntTest > getShipment FAILED
java.lang.AssertionError at ShipmentResourceIntTest.java:192
com.mycompany.store.web.rest.ShipmentResourceIntTest > getNonExistingShipment FAILED
java.lang.AssertionError at ShipmentResourceIntTest.java:205
com.mycompany.store.web.rest.InvoiceResourceIntTest > getInvoice FAILED
java.lang.AssertionError at InvoiceResourceIntTest.java:309
com.mycompany.store.web.rest.InvoiceResourceIntTest > getNonExistingInvoice FAILED
java.lang.AssertionError at InvoiceResourceIntTest.java:326
com.mycompany.store.web.rest.InvoiceResourceIntTest > getAllInvoices FAILED
java.lang.AssertionError at InvoiceResourceIntTest.java:289
com.mycompany.store.web.rest.OrderItemResourceIntTest > getNonExistingOrderItem FAILED
java.lang.AssertionError at OrderItemResourceIntTest.java:247
com.mycompany.store.web.rest.OrderItemResourceIntTest > getAllOrderItems FAILED
java.lang.AssertionError at OrderItemResourceIntTest.java:218
com.mycompany.store.web.rest.OrderItemResourceIntTest > getOrderItem FAILED
java.lang.AssertionError at OrderItemResourceIntTest.java:234
2018-02-11 13:55:55.693 INFO 27458 --- [ Thread-10] c.m.store.config.CacheConfiguration : Closing Cache Manager
217 tests completed, 12 failed
您也可以从您的 IDE 中运行测试,以便您有更好的错误信息和失败报告。选择整个 src/test 文件夹,右键单击,并选择运行所有测试。
- 这些测试预期会失败,因为我们已经在上一章将这些实体的
Resource类更改为处理授权,失败意味着它运行得非常完美。幸运的是,使用 Spring 修复测试并不困难。我们可以使用 Spring 测试上下文提供的@WithMockUser注解为我们的测试提供一个模拟用户。将以下代码中突出显示的用户详细信息添加到所有失败的测试类中:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = StoreApp.class)
@WithMockUser(username="admin", authorities={"ROLE_ADMIN"}, password = "admin")
public class InvoiceResourceIntTest {
...
}
-
我们在这里提供了一个具有
ADMIN角色的模拟用户。同样添加到OrderItemResourceIntTest、ProductOrderResourceIntTest和ShipmentResourceIntTest中。再次运行测试,它们应该通过。 -
通过运行
git commit -am "fix server side tests with mockUser"提交所做的更改。 -
现在,让我们确保我们的客户端 Karma 单元测试正在运行。由于我们在客户端没有进行任何逻辑更改,因此不应该有任何失败。运行以下命令:
> yarn test
- 所有测试都应该通过。让我们转到
src/test/javascript/spec/app/entities/product/product.component.spec.ts。我们使用 Jasmine 框架进行测试。现有的测试具有以下结构。beforeEach块设置了 AngularTestBed:
...
describe('Component Tests', () => {
describe('Product Management Component', () => {
...
beforeEach(() => {
TestBed.configureTestingModule({
...
})
.overrideTemplate(ProductComponent, '')
.compileComponents();
...
});
it('Should call load all on init', () => {
...
});
...
});
});
- 现在,让我们确保我们的 protractor
e2e测试正在运行。在两个不同的终端中运行以下命令。首先启动服务器。让我们通过运行清理任务来清除数据库,以便测试在全新设置上运行。由于我们正在运行清理任务,我们还需要运行webpackBuildDev任务来重新构建客户端:
> ./gradlew clean webpackBuildDev bootRun
- 现在运行 e2e 测试:
> yarn e2e
如果您不想通过 Yarn 或 NPM 运行脚本,您也可以通过 Gradle 使用 JHipster 提供的节点集成来运行它们。例如,您可以使用 ./gradlew yarn_e2e 代替 yarn e2e,并使用 ./gradlew yarn_test 代替 yarn test。如果您不想安装 NodeJS 和 Yarn,并且希望 Gradle 为您管理一切,这很有用。如果您选择 Maven 而不是 Gradle,该功能也适用于 Maven。
- 所有测试都应该在这里通过。但如果您查看生成的
e2e测试,例如,查看src/test/javascript/e2e/entities/customer.spec.ts,您会看到有一个测试被注释掉了。如果实体有一个必需的关系字段,在生成过程中会注释掉一些测试,因为我们必须首先创建关系并设置其值,以便测试能够运行。让我们只关注Customer页面的测试。取消注释名为 should create and save Customers 的测试,并将测试文件中的describe函数更改为fdescribe,以便只执行此测试文件:
fdescribe('Customer e2e test', () => {
...
});
- 现在执行
yarn e2e,我们应该看到一个失败的测试。首先,让我们通过提供有效的电子邮件格式来修复电子邮件字段:
it('should create and save Customers', () => {
...
customerDialogPage.setEmailInput('email@email.com');
expect(customerDialogPage.getEmailInput()).toMatch('email@email.com');
...
});
- 再次运行
yarn e2e并这次它应该会通过。但由于用户和客户之间存在一对一的关系,如果我们再次运行它,测试将失败,因此我们需要删除它之后创建的行。让我们添加一个删除操作的测试用例。在文件中定义的CustomerComponentsPage类(如果你使用的是 JHipster 5,这个类将在src/test/javascript/e2e/page-objects/customer-page-object.ts下可用)中,添加以下新的属性和方法:
table = element.all(by.css('.table-responsive tbody tr'));
getTable() {
return this.table;
}
deleteFirstItem() {
this.table.first().element(by.css('button.btn-
danger')).click();
}
- 现在将
expect(customerComponentsPage.getTable().isPresent()).toBeTruthy();添加为之前测试的最后一行,以确认是否创建了行。然后添加以下测试以删除该行:
it('should create and save Customers', () => {
...
expect(customerComponentsPage.getTable().isPresent()).toBeTruthy();
});
it('should delete Customers', () => {
customerComponentsPage.deleteFirstItem();
const deleteBtn = element.all(by.css('.modal-footer
.btn.btn-danger'));
deleteBtn.click();
expect(customerComponentsPage.getTable().isPresent()).toBeFalsy();
});
-
再次运行
yarn e2e以验证。不要忘记从文件中移除fdescribe,以便所有测试都能执行。恭喜!你添加了你的第一个 Protractore2e测试。 -
类似地,修复
src/test/javascript/e2e/entities下其他文件中注释掉的e2e测试。这是 下一步操作 的一部分。
持续集成
自动化测试确保我们创建的代码没有错误,同时也确保没有从新代码中引入回归。JHipster 通过为生成的代码创建单元和集成测试在一定程度上有所帮助,但在实际使用案例中,这还不够。我们还需要为引入的业务逻辑添加服务器端单元测试,并为新添加的 API 添加集成测试。你还需要为客户端处理的企业逻辑添加更多单元测试和 e2e 测试,因为 JHipster 只为你生成少量示例测试,并且对您的业务逻辑一无所知。
你拥有的测试越多,你在更改代码时就越有信心,回归的可能性就越小。
测试和持续集成是全栈开发的一个组成部分,也是 DevOps 的重要方面。测试应该被视为与构建高质量产品时开发功能同等重要。持续集成不过是持续地将你的新代码更改合并并测试到隔离环境中,与你的 master/main/stable 代码库进行对比,以识别潜在的错误和回归。这是通过运行针对代码的自动化单元、集成、端到端和其他测试套件来实现的。例如,如果你使用 Git,这些测试通常会在你向 master 分支提交每次更改时以及你创建的每个 pull request 时运行。
一旦我们有了自动化测试,我们可以利用持续集成实践来确保我们引入的新代码不会对我们的稳定代码库造成任何回归。这将给我们合并新代码并将其部署到生产环境的信心。
现代 DevOps 团队通常会更进一步,进行持续交付(持续集成 + 持续部署)。他们通常会定义 CI/CD 管道,以完全自动化的方式持续集成、测试和将代码部署到生产环境中。
拥有良好持续集成和持续部署设置的团队可以更频繁地交付更多功能,同时出现更少的错误。
我是否已经足够强调了持续集成的重要性?
CI/CD 工具
JHipster 为知名的 CI/CD 工具提供了出色的支持。让我们首先看看可用的选项。
Jenkins
Jenkins (jenkins.io/) 是领先的 CI/CD 工具之一。它是免费和开源的。这是一个用 Java 编写的自动化服务器,支持与各种版本控制工具集成,如 Git、CVS、SVN 等。Jenkins 拥有庞大的插件生态系统,这使得它成为最灵活的平台之一。Jenkins 可以用于构建项目、运行自动化测试、自动化部署等。它作为各种平台的可执行二进制文件和 Docker 镜像提供。Blue Ocean 是 Jenkins 的最新 UI 接口,为它带来了急需的新鲜空气。Jenkins 有管道的概念,通过使用多个插件和 Groovy DSL 来定义 CI/CD 管道。Jenkins 管道插件提供了一种基于 DSL 的全面配置,可以在名为 Jenkinsfile 的文件中定义。
Travis CI
Travis CI (travis-ci.org/) 是一个开源的托管 PaaS(平台即服务) 解决方案,用于 CI/CD。对于公共/OSS 项目是免费的,而对于私人/企业项目则需要订阅。它支持用各种语言和平台编写的应用程序,并且被包括 JHipster 在内的开源项目广泛用于持续集成需求。它与版本控制工具有很好的集成,并提供企业版本。它非常容易设置和使用,具有简单的基于 YAML 的配置。高级设置通常使用可以通过钩子由 YAML 配置触发的 shell 脚本来完成。
GitLab CI
GitLab CI (about.gitlab.com/features/gitlab-ci-cd/) 是 GitLab 的一部分 CI/CD 解决方案,GitLab 是 Git 之上的一个 Web UI。它与平台良好集成,当使用 GitLab 时是一个极佳的选择。对于公共项目它是免费和开源的,同时也有企业版本。它既有托管解决方案,也有用于本地部署的二进制文件。
CircleCI
CircleCI (circleci.com/) 是另一个提供托管 PaaS 和本地选项的开源 CI/CD 解决方案。它为小型团队提供免费选项,并为大型团队和企业提供订阅计划。配置简单,基于 YAML,类似于 Travis CI。它提供了选择不同操作系统环境进行构建的选项,并且非常容易设置。
设置 Jenkins
让我们使用 Jenkins 作为我们应用程序的 CI 工具。我们首先需要设置一个本地 Jenkins 实例:
如果你已经熟悉 Docker,你可以使用 Jenkins 提供的官方 Docker 镜像,并可以跳过以下步骤。Docker 镜像将在下一节创建 CD/CI 流水线时由 JHipster 自动生成。有关更多详细信息,请访问www.jhipster.tech/setting-up-ci-jenkins2/。
-
让我们从
mirrors.jenkins.io/war-stable/latest/jenkins.war下载最新的二进制文件。 -
现在打开一个终端,导航到文件下载的文件夹。
-
从终端执行
java -jar jenkins.war --httpPort=8989以启动一个 Jenkins 服务器。端口号不应与我们的应用程序端口冲突。默认密码将在控制台上打印出来。请复制它。 -
导航到
localhost:8989,并粘贴之前复制的密码。 -
点击下一页上的“安装建议插件”按钮,并等待插件安装完成。
-
在下一页创建一个管理员用户并完成。
现在我们已经准备好了 Jenkins 服务器,让我们继续为我们的项目创建一个 Jenkins 流水线。
使用 JHipster 创建 Jenkins 流水线
我们可以使用 JHipster 的ci-cd sub-generator来为我们的项目创建Jenkinsfile:
- 在终端中,首先导航到
online-store文件夹。现在运行以下命令:
> jhipster ci-cd
- 你将需要从以下列表中选择一个选项:
Welcome to the JHipster CI/CD Sub-Generator
? What CI/CD pipeline do you want to generate? (Press <space> to select, <a> to toggle all, <i> to inverse selection) >◯ Jenkins pipeline
◯ Travis CI
◯ GitLab CI
◯ CircleCI
- 让我们从其中选择 Jenkins 流水线。接下来,我们将有一个选项来选择额外的阶段:
? What CI/CD pipeline do you want to generate? Jenkins pipeline
? Jenkins pipeline: what tasks/integrations do you want to include? >◯ Perform the build in a Docker container
◯ Analyze code with Sonar
◯ Send build status to GitLab
◯ Build and publish a Docker image
- 让我们跳过这一步,因为我们现在不需要这些,然后继续。接下来,我们将被询问是否需要从 CI/CD 流水线自动部署到 Heroku:
? What CI/CD pipeline do you want to generate? Jenkins pipeline
? Jenkins pipeline: what tasks/integrations do you want to include?
? Deploy to heroku? >◯ In Jenkins pipeline
- 让我们选择这个选项,因为我们稍后会用到它。一旦选择了选项,JHipster 将生成文件并在控制台上记录以下输出。
create Jenkinsfile
create src/main/docker/jenkins.yml
create src/main/resources/idea.gdsl
Congratulations, JHipster execution is complete!
如果你想使用 Travis 而不是 Jenkins,你可以通过选择 Travis 选项,然后将仓库作为公开仓库发布到 GitHub 来实现。一旦发布,请转到https://github.com/<username>/<repoName>/settings/installations,添加 Travis CI 作为服务并按照说明操作。现在,当你提交时,你可以看到自动构建。有关详细信息,请参阅docs.travis-ci.com/user/getting-started/。
如您所见,我们在根目录中生成了一个Jenkinsfile,在src/main/docker目录中创建了一个 Jenkins 的 Docker 镜像。我们还得到了一个idea.gdsl文件,该文件由 IntelliJ Idea 用于自动完成。
Jenkinsfile 及其阶段
让我们看看生成的Jenkinsfile,它使用 Groovy DSL 定义了我们的流水线:
#!/usr/bin/env groovy
node {
stage('checkout') {
checkout scm
}
stage('check java') {
sh "java -version"
}
stage('clean') {
sh "chmod +x gradlew"
sh "./gradlew clean --no-daemon"
}
stage('install tools') {
sh "./gradlew yarn_install -PnodeInstall --no-daemon"
}
stage('backend tests') {
try {
sh "./gradlew test -PnodeInstall --no-daemon"
} catch(err) {
throw err
} finally {
junit '**/build/**/TEST-*.xml'
}
}
stage('frontend tests') {
try {
sh "./gradlew yarn_test -PnodeInstall --no-daemon"
} catch(err) {
throw err
} finally {
junit '**/build/test-results/karma/TESTS-*.xml'
}
}
stage('packaging') {
sh "./gradlew bootRepackage -x test -Pprod -PnodeInstall --
no-daemon"
archiveArtifacts artifacts: '**/build/libs/*.war',
fingerprint: true
}
stage('deployment') {
sh "./gradlew deployHeroku --no-daemon"
}
}
我们定义了多个按顺序运行的阶段,用粗体突出显示;确切地说有八个。它从从版本控制中检出分支开始,以部署到 Heroku 结束(我们将在下一章中了解更多关于这一点)。
步骤相当直接,因为其中大部分只是触发 Gradle 任务。让我们看看每一个:
stage('checkout') {
checkout scm
}
checkout阶段对触发构建的源代码修订版本进行本地检出:
stage('check java') {
sh "java -version"
}
这个check java阶段只是打印出 Jenkins 环境中安装的 Java 版本:
stage('clean') {
sh "chmod +x gradlew"
sh "./gradlew clean --no-daemon"
}
clean阶段首先在类 Unix 操作系统上为 Gradle wrapper 授予执行权限,然后执行 Gradle clean 任务。--no-daemon标志禁用了 Gradle 守护进程功能,这在 CI 环境中不是必需的:
stage('install tools') {
sh "./gradlew yarn_install -PnodeInstall --no-daemon"
}
install tools阶段通过运行 Gradle 的yarn install确保安装了 NodeJS 和所有 NPM 模块。
-PnodeInstall标志确保如果尚未安装,首先安装 NodeJS:
stage('backend tests') {
try {
sh "./gradlew test -PnodeInstall --no-daemon"
} catch(err) {
throw err
} finally {
junit '**/build/**/TEST-*.xml'
}
}
backend tests阶段通过触发 Gradle 测试任务运行所有服务器端集成和单元测试。如果出现错误,它将使 Jenkins 管道失败,并在测试运行完成后使用 JUnit 插件在 Jenkins web UI 上注册测试报告:
stage('frontend tests') {
try {
sh "./gradlew yarn_test -PnodeInstall --no-daemon"
} catch(err) {
throw err
} finally {
junit '**/build/test-results/karma/TESTS-*.xml'
}
}
与之前类似,frontend tests阶段通过触发Gradle任务中的 yarn test 命令运行客户端单元测试。它也会在出现错误时使管道失败,并在测试运行完成后使用 JUnit 插件在 Jenkins web UI 上注册测试报告:
stage('packaging') {
sh "./gradlew bootRepackage -x test -Pprod -PnodeInstall --no-
daemon"
archiveArtifacts artifacts: '**/build/libs/*.war', fingerprint:
true
}
packaging阶段通过使用prod配置触发Gradle bootRepackage任务,并将创建的 WAR 文件存档,带有唯一的指纹:
stage('deployment') {
sh "./gradlew deployHeroku --no-daemon"
}
最后一个阶段是用于deployment的,它也使用 Gradle 任务来完成。我们将在下一章中详细说明。现在,让我们注释掉这个阶段。我们稍后会重新启用它。
现在让我们通过运行这些命令将所有内容提交到git。确保你处于主分支,否则请将分支与主分支合并:
> git add --all
> git commit -am "add Jenkins pipeline for ci/cd"
在 Jenkins 服务器上设置 Jenkinsfile
既然我们的Jenkinsfile已经准备好了,让我们为我们的应用程序设置 CI/CD。首先,我们需要将我们的应用程序上传到 Git 服务器,例如 GitHub、GitLab 或 BitBucket。让我们使用 GitHub (github.com/) 来做这件事。确保你首先在 GitHub 上创建了一个账户:
- 在 GitHub 中创建一个新的仓库 (
github.com/new);让我们称它为 online-store。不要选择“使用 README 初始化此仓库”选项。一旦创建,你将看到添加代码的说明。让我们选择通过在 online-store 应用程序文件夹内运行以下命令从命令行推送现有仓库的选项。不要忘记将<username>替换为你的实际 GitHub 用户名:
> cd online-store
> git remote add origin https://github.com/<username>/online-store.git
> git push -u origin master
-
现在,通过访问
http://localhost:8989/进入 Jenkins 服务器 web UI,并使用“创建新作业”链接创建一个新的作业。 -
输入一个名称,从列表中选择“Pipeline”,然后点击“确定”:

-
然后,在下一页上,执行以下操作:
-
滚动或点击“构建触发器”部分。
-
选择“Poll SCM”复选框。
-
将 cron 计划值输入为 H/01 ** ** ** **,以便 Jenkins 每分钟轮询我们的仓库,并在有新提交时构建:
![图片]()
-
-
接下来,在同一页面上:
-
滚动页面或点击“管道”部分。
-
从下拉菜单中选择“从 SCM 选择管道脚本”作为定义字段。
-
从下拉菜单中选择“Git”作为 SCM 字段。
-
添加应用的仓库 URL。
-
最后,点击“保存”:
![图片]()
-
点击“立即构建”以触发新的构建来测试我们的管道:
![图片]()
-
我们现在应该能在 Web UI 上看到构建已经开始,其进度如下截图所示:

恭喜!我们已经成功为我们的应用设置了 CI/CD。当您提交新更改时,构建将被触发。
您还可以使用 Jenkins Blue Ocean 插件的新 UI 查看管道状态。从插件管理器安装插件(点击顶部菜单中的 Jenkins,然后转到管理 Jenkins | 管理插件 | 可用插件,搜索Blue Ocean并安装它)。左侧菜单上有“打开 Blue Ocean”链接。构建将如下所示:

点击一个构建来查看管道。您可以在进度指示器上点击每个阶段,列出从该阶段开始的步骤,然后展开列表项以查看该步骤的日志:

摘要
在本章中,我们探讨了 CI/CD 是什么,以及 JHipster 支持的工具。我们还学习了如何设置 Jenkins,并使用 JHipster 和 Jenkins 创建我们的 CI/CD 管道。我们还修复了我们的自动化测试,并使它们在 CI 服务器上运行。
在下一章中,我们将看到如何使用云托管提供商(如 Heroku)将我们的应用部署到生产环境。
第七章:进入生产环境
我们的应用程序几乎准备好了,现在是时候进入生产环境了。由于这是云计算的时代,我们将把我们的应用程序部署到云服务提供商——具体来说,是 Heroku。在我们继续部署我们的应用程序到生产环境之前,我们需要确保我们的应用程序在我们的本地环境中已经准备好。熟悉在这个阶段将会有用的技术和工具也会很有益处。
在本章中,我们将学习以下内容:
-
Docker 简介
-
使用 Docker 启动生产数据库
-
Spring 配置简介
-
打包应用程序以进行本地部署
-
升级到 JHipster 的最新版本
-
JHipster 支持的部署选项简介
-
在 Heroku 云上进行生产部署
Docker 简介
Docker 是近年来在 DevOps 领域占据中心舞台的最具颠覆性的技术之一。Docker 是一种使操作系统级别的虚拟化或容器化成为可能的技术,它也是开源的,并且可以免费使用。Docker 旨在用于 Linux,但可以使用 Docker for Mac 和 Docker for Windows 等工具在 Mac 和 Windows 上使用。
Docker 容器
当我们在 Docker 世界中谈论容器时,从技术上讲,我们是在谈论 Linux 容器。正如红帽在其网站上所述(www.redhat.com/en/topics/containers/whats-a-linux-container):
Linux 容器是一组从系统其余部分隔离出来的进程,它们从一个提供所有必要文件以支持这些进程的独立镜像中运行。通过提供一个包含应用程序所有依赖项的镜像,它在从开发到测试,最后到生产的移动过程中是可移植的和一致的。
虽然这个概念并不新颖,但 Docker 使得创建易于构建、部署、版本控制和共享的容器成为可能。Docker 容器仅包含在宿主操作系统上运行应用程序所需的依赖项;它共享宿主系统硬件的操作系统和其他依赖项。这使得 Docker 容器在大小和资源使用方面比虚拟机(VM)更轻,因为它不需要传输整个操作系统和模拟虚拟硬件。因此,Docker 在很多传统使用场景中使虚拟机变得过时,这些场景原本是通过虚拟机技术处理的。这也意味着,与使用虚拟机相比,使用 Docker 我们可以在相同的硬件上运行更多的应用程序。Docker 容器是 Docker 镜像的实例,Docker 镜像是一组层,它描述了正在容器化的应用程序。它们包含运行应用程序所需的代码、运行时、库、环境变量和配置文件。
Dockerfile
Dockerfile 是一组指令,告诉 Docker 如何构建 Docker 镜像。通过在特定的 Dockerfile 上运行 docker build 命令,我们将生成一个可用于创建 Docker 容器的 Docker 镜像。现有的 Docker 镜像可以用作新 Dockerfile 的基础,因此可以重用和扩展现有镜像。
以下代码来自我们应用程序的 Dockerfile:
FROM openjdk:8-jre-alpine
ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \
JHIPSTER_SLEEP=0 \
JAVA_OPTS=""
CMD echo "The application will start in ${JHIPSTER_SLEEP}s..." && \
sleep ${JHIPSTER_SLEEP} && \
java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar /app.war
EXPOSE 8080 5701/udp
ADD *.war /app.war
FROM 指令指定初始化构建时使用的基镜像。在这里,我们指定 Open JDK 8 作为我们的 Java 运行时。
ENV 指令用于设置环境变量,而 CMD 指令用于指定要执行的命令。
EXPOSE 指令用于指定容器在运行时监听的端口。
访问 docs.docker.com/engine/reference/builder/ 获取完整的参考。
Docker Hub
Docker Hub (hub.docker.com/) 是 Docker 提供的在线注册库。它可以用来发布公共和私有 Docker 镜像。这使得共享和重用 Docker 镜像变得极其容易。
要从注册库获取 Docker 镜像,我们只需运行 docker pull <image-name>。
这使得在不本地安装第三方工具的情况下,只需从注册库拉取并运行容器即可轻松使用第三方工具。
Docker Compose
Docker Compose 是 Docker 平台中的一个工具,用于定义和运行多容器应用程序。它让我们定义容器在生产环境中运行时的行为,还让我们定义它所依赖的其他服务以及服务之间如何协同工作。每个应用程序都是一个服务,因为它定义了容器的行为,例如它运行在哪个端口上,它使用哪些环境变量,等等。使用 YAML 文件进行此操作。单个 docker-compose.yml 文件可以定义多容器应用程序所需的所有服务,然后可以通过单个命令启动。我们将在第十一章 Deploying with Docker Compose 中了解更多关于 Docker 和 docker-compose 的内容。
访问 docs.docker.com/get-started/ 了解更多关于 Docker 的信息。
下表列出了 Docker 和 Docker Compose 的有用命令:
docker build -t myapp:1.0. |
从当前目录的 Dockerfile 构建镜像并标记镜像 |
|---|---|
docker images |
列出本地存储的所有 Docker 镜像 |
docker pull alpine:3.4 |
从注册库拉取镜像 |
docker push myrepo/myalpine:3.4 |
将镜像推送到注册库 |
docker login |
登录到注册库(默认为 Docker Hub) |
| docker run --rm -it -p 5000:80 -v /dev/code alpine:3.4 /bin/sh | 运行 Docker 容器--rm:容器退出后自动删除 -it:将容器连接到终端
-p:外部暴露端口 5000 并映射到端口 80
-v:在容器内创建一个主机挂载卷
alpine:3.4:从该镜像实例化的容器
/bin/sh:在容器内运行的命令
docker stop myApp |
停止一个正在运行的容器 |
|---|---|
docker ps |
列出正在运行的容器 |
docker rm -f $(docker ps -aq) |
删除所有正在运行和已停止的容器 |
docker exec -it web bash |
在容器内创建一个新的 bash 进程并将其连接到终端 |
docker logs --tail 100 web |
打印容器日志的最后 100 行 |
docker-compose up |
启动当前文件夹中 docker-compose.yml 文件定义的服务 |
docker-compose down |
停止当前文件夹中 docker-compose.yml 文件定义的服务 |
使用 Docker 启动生产数据库
JHipster 为应用程序创建一个 Dockerfile,并为我们在 src/main/docker 下选择的所有技术(如数据库、搜索引擎、Jenkins 等)提供 docker-compose 文件:
├── app.yml - Main compose file for the application
├── Dockerfile - The Dockerfile for the application
├── hazelcast-management-center.yml - Compose file hazelcast management center
├── jenkins.yml - Compose file for Jenkins
├── mysql.yml - Compose file for the database that we choose.
└── sonar.yml - COmpose file for SonarQube.
让我们看看如何使用在 src/main/docker/mysql.yml 下提供的组合文件来使用 Docker 启动我们的生产数据库。你将需要使用终端来执行以下指令:
-
运行
docker --version和docker-compose --version以确保这些已安装。 -
运行
docker ps以列出正在运行的容器。如果你还没有运行任何容器,你应该看到一个空列表。 -
让我们通过运行
docker-compose -f src/main/docker/mysql.yml up来启动数据库。
你将看到以下控制台输出:

如果你想在后台运行服务,将 -d 标志传递给命令。docker-compose -f src/main/docker/mysql.yml up -d 将允许你继续使用相同的终端,而无需切换到另一个。
现在如果你再次运行 docker ps,它应该会列出我们启动的数据库服务:

Spring 配置文件的介绍
在我们准备应用程序投入生产之前,让我们简单谈谈 Spring 配置文件。
Spring 配置文件(docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-definition-profiles-java)允许您根据环境更改应用程序的行为。这是通过使用 @Profile 注解和特定配置文件来实现的,可以通过指定 spring.profiles.active 属性来激活。根据我们在这里设置的配置文件,Spring 将选择适当的 application.properties/application.yml 文件,并使用 Java 源代码中的 @Profile 注解包含/排除特定配置文件包含/排除的组件。例如,如果我们设置 spring.profiles.active=prod,所有具有 @Profile("prod") 的 Spring 组件都将被实例化,任何具有 @Profile("!prod") 的组件将被排除。同样,如果 application-prod.yml 或 application-prod.properties 文件在类路径上可用,Spring 将加载并使用它。
JHipster 默认配置了 dev 和 prod 配置文件,并在 src/main/resources/config 文件夹中包含了 application-dev.yml 和 application-prod.yml 文件,以及基本的 application.yml 文件。JHipster 还更进一步,为 Gradle 构建提供了 dev 和 prod 配置文件(同样适用于 Maven),这样我们就可以为特定配置文件构建/运行应用程序,这非常方便。以下是 application-dev.yml 文件中定义的配置文件和数据库配置:
...
spring:
profiles:
active: dev
include: swagger
...
datasource:
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:h2:file:./build/h2db/db/store;DB_CLOSE_DELAY=-1
username: store
password:
...
JHipster 应用程序中可用的配置文件如下:
dev |
专为开发和生产力优化,启用了 Spring 开发工具、内存数据库等 |
|---|---|
prod |
专为生产优化,侧重于性能和稳定性 |
swagger |
启用 API 的 Swagger 文档 |
no-liquibase |
禁用 Liquibase,在不需要 Liquibase 运行的生产环境中很有用 |
打包应用程序以进行本地部署
现在,让我们构建我们的应用程序并将其本地部署。这可以通过两种方式完成,要么使用 Docker,要么构建并执行一个 WAR 文件。
使用 Docker 构建和部署
让我们使用 Gradle 任务来构建我们的 Docker 镜像。
使用 ./gradlew tasks 命令列出所有可用任务。
-
在您的终端中,进入项目根目录并执行;
./gradlew bootRepackage -Pprod buildDocker:-
bootRepackage:为应用程序构建可执行归档(WAR)文件
-
-Pprod:指定要使用的配置文件
-
buildDocker:基于
src/main/docker文件夹中的 Dockerfile 构建 Docker 镜像
-
如果您使用的是 JHipster 版本 5 或更高版本,请使用 bootWar 命令代替 Gradle 中的 bootRepackage 命令。
- 任务成功完成后,我们可以通过运行以下命令来部署我们的应用程序:
> docker-compose -f src/main/docker/app.yml up
如果您还没有启动 MySQL 数据库,这将也会启动它。如果您已经在之前的步骤中启动了它,那么 docker-compose 将会跳过这一步。
当我们在控制台看到以下输出时,我们的应用程序就准备好了。如您所见,它正在使用 prod 和 swagger 配置运行:

使用您最喜欢的浏览器访问 http://localhost:8080 来查看应用程序的实际运行情况。
构建和部署可执行存档
如果您不想使用 Docker,则可以通过完成以下步骤在本地使用生产配置部署应用程序:
-
首先,请确保 MySQL 数据库在之前的步骤中正在运行;如果不是,请使用
docker-compose -f src/main/docker/mysql.yml up -d启动它。 -
现在让我们通过运行
./gradlew bootRepackage -Pprod来为 prod 配置创建一个可执行的存档。 -
一旦构建成功,
build/libs下将创建两个存档(WAR)。store-0.0.1-SNAPSHOT.war文件是一个可执行存档,可以直接在 JVM 上运行,而store-0.0.1-SNAPSHOT.war.original是一个普通的 WAR 文件,可以部署到服务器,如 JBoss 或 Tomcat。 -
让我们使用可执行存档。只需运行
./build/libs/store-0.0.1-SNAPSHOT.war来启动应用程序。如果您使用的是 Windows,请运行java -jar build/libs/store-0.0.1-SNAPSHOT.war。
一旦应用程序启动,您将在控制台上看到打印的 URL。使用您最喜欢的浏览器访问 http://localhost:8080 来查看应用程序的实际运行情况。
升级到 JHipster 的最新版本
JHipster 提供了一个升级子生成器 (www.jhipster.tech/upgrading-an-application/),以帮助您使用新的 JHipster 版本升级应用程序。它非常有用,因为它为您自动化了许多手动步骤,您唯一需要做的是在升级完成后解决任何合并冲突。让我们升级我们的应用程序,怎么样?
- 在您的终端中,执行
jhipster upgrade命令。如果有新的 JHipster 版本可用,则升级过程将开始;如果没有,则进程将退出。
一旦进程开始,您将看到详细的控制台日志,显示正在发生的事情。如您所见,这个子生成器使用的是全局 JHipster 版本,而不是本地版本,这与其他子生成器不同:
Using JHipster version installed globally
Executing jhipster:upgrade
Options:
Welcome to the JHipster Upgrade Sub-Generator
This will upgrade your current application codebase to the latest JHipster version
Looking for latest generator-jhipster version...
yarn info v1.5.1
4.14.1
Done in 0.16s.
New generator-jhipster version found: 4.14.1
info git rev-parse -q --is-inside-work-tree
Git repository detected
info git status --porcelain
info git rev-parse -q --abbrev-ref HEAD
info git rev-parse -q --verify jhipster_upgrade
info git checkout --orphan jhipster_upgrade
Created branch jhipster_upgrade
info Removing .angular-cli.json
...
Cleaned up project directory
Installing JHipster 4.13.3 locally
info yarn add generator-jhipster@4.13.3 --dev --no-lockfile --ignore-scripts
yarn add v1.5.1
...
Done in 6.16s.
Installed generator-jhipster@4.13.3
Regenerating application with JHipster 4.13.3...
warning package.json: No license field
/home/deepu/Documents/jhipster-book/online-store/node_modules/.bin
info "/home/deepu/Documents/jhipster-book/online-store/node_modules/.bin/jhipster" --with-entities --force --skip-install
Using JHipster version installed globally
Running default command
Executing jhipster:app
Options: withEntities: true, force: true, skipInstall: true, with-entities: true, skip-install: true
...
Server application generated successfully.
...
Client application generated successfully.
...
Entity generation completed
...
Congratulations, JHipster execution is complete!
Successfully regenerated application with JHipster 4.13.3
info Removing src/main/resources/keystore.jks
info git add -A
info git commit -q -m "Generated with JHipster 4.13.3" -a --allow-empty
Committed with message "Generated with JHipster 4.13.3"
info git checkout -q master
Checked out branch "master"
info git --version
info git merge --strategy=ours -q --no-edit --allow-unrelated-histories jhipster_upgrade
Current code has been generated with version 4.13.3
info git checkout -q jhipster_upgrade
Checked out branch "jhipster_upgrade"
Updating generator-jhipster to 4.14.1 . This might take some time...
info yarn add generator-jhipster@4.14.1 --dev --no-lockfile --ignore-scripts
...
Done in 30.40s.
Updated generator-jhipster to version 4.14.1
info Removing .angular-cli.json
...
Cleaned up project directory
Regenerating application with JHipster 4.14.1...
/home/deepu/Documents/jhipster-book/online-store/node_modules/.bin
info "/home/deepu/Documents/jhipster-book/online-store/node_modules/.bin/jhipster" --with-entities --force --skip-install
Using JHipster version installed globally
Running default command
Executing jhipster:app
Options: withEntities: true, force: true, skipInstall: true, with-entities: true, skip-install: true
...
Entity generation completed
Congratulations, JHipster execution is complete!
Successfully regenerated application with JHipster 4.14.1
info Removing src/main/resources/keystore.jks
info git add -A
info git commit -q -m "Generated with JHipster 4.14.1" -a --allow-empty
Committed with message "Generated with JHipster 4.14.1"
info git checkout -q master
Checked out branch "master"
Merging changes back to master...
info git merge -q jhipster_upgrade
Merge done!
info git diff --name-only --diff-filter=U package.json
WARNING! There are conflicts in package.json, please fix them and then run yarn
Start your Webpack development server with:
yarn start
info git diff --name-only --diff-filter=U
Upgraded successfully.
WARNING! Please fix conflicts listed below and commit!
gradle/wrapper/gradle-wrapper.properties
package.json
src/test/java/com/mycompany/store/web/rest/CustomerResourceIntTest.java
Congratulations, JHipster execution is complete!
子生成器按以下顺序执行:
-
检查是否有新的 JHipster 版本可用(如果使用
--force则不适用)。 -
检查应用程序是否已经初始化为 GIT 仓库;如果不是,JHipster 将为您初始化一个,并将当前代码库提交到 master 分支。
-
检查确保仓库中没有未提交的本地更改。如果发现任何未提交的更改,则进程将退出。
-
检查是否存在
jhipster_upgrade分支。如果不存在,则创建一个分支。 -
检出
jhipster_upgrade分支。 -
将 JHipster 升级到最新版本。
-
清理当前项目目录。
-
使用
jhipster --force --with-entities命令重新生成应用程序。 -
将生成的代码提交到
jhipster_upgrade分支。 -
将
jhipster_upgrade分支合并回启动jhipster upgrade命令的原始分支。
在解决合并冲突之前,先看看升级后有什么变化。查看已暂存的更改。仔细检查更改,确保一切正常,特别是在我们之前进行过自定义的文件中。我的变更日志如下;注意,由于有 147 个文件被更新,所以我截断了底部:

幸运的是,我们只有三个冲突,因此它们应该很容易解决。package.json中的冲突源于我们为了集成 Bootswatch 所做的更改。仔细解决文件中的冲突暂存,然后继续下一个文件。
一旦所有冲突都得到解决,暂存文件并将它们提交:
> git add --all
> git commit -am "update to latest JHipster version"
确保一切正常工作。使用./gradlew test && yarn && yarn test运行服务器端和客户端测试,并通过运行./gradlew clean webpackBuildDev bootRun命令启动应用程序来验证这一点。
JHipster 支持的部署选项简介
现在我们已经通过本地部署验证了我们的生产构建,让我们看看如何通过使用云服务将其推向实际生产。JHipster 默认支持大多数云平台,并为流行的平台如 Heroku、Cloudfoundry 和 AWS 提供了特殊的子生成器命令。
JHipster 还支持如 OpenShift、Google Cloud(使用 Kubernetes)和 Rancher 等平台,但让我们在接下来的章节中了解它们,因为它们更适合微服务。尽管如此,从理论上讲,您也可以将它们用于单体部署。
Heroku
Heroku (www.heroku.com/)是 Salesforce 的云平台。它允许您在云上部署、管理和监控应用程序。Heroku 专注于应用程序而不是容器,并支持从 NodeJS 到 Java 再到 Go 的各种语言。JHipster 提供了 Heroku 子生成器,该生成器由 Heroku 构建和维护,使得将 JHipster 应用程序部署到 Heroku 云变得容易。它使用 Heroku CLI,您需要一个 Heroku 账户才能使用它。子生成器可用于部署和更新您的应用程序到 Heroku。
访问www.jhipster.tech/heroku/获取更多信息。
Cloud Foundry
Cloud Foundry 是由 Cloud Foundry 基金会管理的多云计算平台。它最初由 VMWare 创建,现在是 Spring 框架背后的公司 Pivotal 的旗下。它提供了一种多云解决方案,目前由 Pivotal Cloud Foundry (PCF), Pivotal Web Services (PWS), Atos Canary, SAP 云平台和 IBM Bluemix 等支持。该平台是开源的,因此可以用来设置自己的私有实例。JHipster 提供了一个子生成器,可以轻松地将 JHipster 应用程序部署到任何 Cloud Foundry 提供商。它使用 Cloud Foundry 命令行工具。
访问 www.jhipster.tech/cloudfoundry/ 获取更多信息。
Amazon Web Services
Amazon Web Services (AWS) 是领先的云计算平台,提供平台、软件和基础设施作为服务。AWS 提供了 Elastic Beanstalk 作为在云上部署和管理应用程序的简单平台。JHipster 提供了一个子生成器,可以将 JHipster 应用程序部署到 AWS 或 Boxfuse (www.jhipster.tech/boxfuse/),一个替代服务。
访问 www.jhipster.tech/aws/ 获取更多信息。
将生产部署到 Heroku 云平台
我们需要选择一个云提供商。对于这个演示,让我们选择 Heroku。
虽然 Heroku 账户是免费的,并且你可以获得免费额度,但为了使用 MySQL 和其他附加组件,你将需要提供信用卡信息。只有当你超出免费配额时,你才会被收费。
让我们通过以下步骤将我们的应用程序部署到 Heroku:
-
首先,你需要在 Heroku 中创建一个账户 (
signup.heroku.com/)。这是免费的,你也会获得免费额度。 -
按照以下链接
devcenter.heroku.com/articles/heroku-cli安装 Heroku CLI 工具。 -
通过运行
heroku --version验证 Heroku CLI 是否安装正常。 -
通过运行
heroku login登录到 Heroku。当提示时,输入你的 Heroku 电子邮件和密码。 -
现在运行
jhipster heroku命令。你将开始看到问题。 -
当被问及部署名称时,选择你喜欢的名称:(存储)。默认情况下,它将使用应用程序名称。尽量选择一个独特的名称,因为 Heroku 命名空间是共享的。
-
接下来,你将被要求选择一个区域——你希望在哪个区域部署?选择美国或欧盟,然后继续。
-
生成器将创建所需的文件,并接受 Gradle 构建文件建议的更改。
控制台输出将看起来像这样:

生成的 .yml 文件为应用程序添加了 Heroku 特定的配置。Procfile 包含了在 Heroku 上执行的应用程序特定命令。Gradle 构建也被修改以包含 Heroku 所需的依赖项。
生成文件后,它将构建应用程序并开始上传工件。这可能会根据你的网络延迟需要几分钟。一旦成功完成,你应该会看到以下屏幕:

现在运行 heroku open 命令以在浏览器中打开已部署的应用程序。就这样,你已成功使用几个命令将应用程序部署到 Heroku。
当你进一步更新应用程序时,你可以使用 ./gradlew -Pprod bootRepackage 命令重新构建包,然后使用 heroku deploy:jar --jar build/libs/*war 命令重新部署它。
不要忘记通过执行以下命令将所做的更改提交到 git:
> git add --all
> git commit -am "add heroku configuration"
摘要
部署到生产是应用程序开发中最重要阶段之一,也是最重要的一个。借助 JHipster,我们轻松地将应用程序部署到了云服务提供商。我们还了解了 Docker 和其他可用的各种部署选项。我们还使用了升级子生成器来确保我们的应用程序与 JHipster 保持最新。
到目前为止,我们看到了如何使用 JHipster 开发和部署单体电子商务应用程序。我们从一个单体开始,在接下来的章节中,我们将看到如何在 JHipster 的帮助下将我们的应用程序扩展到微服务架构。在下一章中,我们将学习关于不同的微服务技术和工具。所以,请保持关注!
第八章:微服务服务器端技术简介
使用 JHipster 开发一个生产就绪的单体应用不是很容易吗?到目前为止,我们已经从头开始创建了一个应用,使用 JDL Studio 添加了一些实体,然后将其与测试一起部署到生产环境中。我们还添加了持续集成和持续交付管道。这种体验不是更快、更简单、更好吗?
那么,接下来是什么?是的,你猜对了——微服务!
微服务现在是到处都在谈论的热门词汇。许多公司都在尝试用微服务来解决他们的问题。我们已经在第一章“现代 Web 应用开发简介”中看到了微服务的好处概述。
在本章中,我们将探讨以下内容:
-
微服务相对于单体应用的好处
-
我们需要构建完整的微服务架构的组件
在本章中,我们将看到我们之前创建的单体应用如何被转换成一个微服务应用。
之后,我们将看到使用 JHipster 提供的选项创建微服务架构是多么容易。
微服务应用与单体应用对比
通过与单体架构的比较,可以更好地理解微服务架构的好处。
当它们被正确设计和部署时,微服务相对于单体应用的好处是惊人的。
这并不像根据结构、组件或功能将单体应用拆分,然后作为独立服务部署那么简单。这行不通。将单体应用或甚至单体设计转换为微服务需要一个清晰的产品愿景。这包括了解项目的哪些部分会改变,哪些部分会保持一致。我们必须有低级别的细节,比如我们应该将哪些实体分组在一起,哪些可以分离。
这清楚地说明了需要一个不断发展的模型的需求。拆分应用中使用的技术比拆分相互依赖的模型或应用的业务逻辑要容易得多。因此,将项目的主要重点放在核心领域及其逻辑上至关重要。
微服务应该是独立的。当一个组件与另一个组件紧密耦合时,它们会失败。最棘手的部分是识别和隔离组件。
当我们完成这个任务后,它相对于单体应用有以下好处。
单体代码是一个单一单元。因此,应用的所有部分共享相同的内存。对于更大的系统,我们需要更大的基础设施。当应用增长时,我们需要根据需要扩展基础设施。对已经较大的基础设施进行扩展总是对运营来说既困难又昂贵的任务。
尽管它们在单个地方拥有处理产品中任何事物的所有必要代码(无需担心延迟或可用性),但处理其运行所消耗的资源却很困难,而且肯定不可扩展。如果应用程序的任何一部分失败,整个产品都会受到影响。当产品的任何线程或查询粘附在内存上时,影响将会被数百万客户看到。
相反,微服务由于我们将应用程序拆分为更小的组件,因此运行时所需的内存更少。这反过来又降低了基础设施的成本。例如,运行 10 个 2GB 实例(在 AWS 上的成本约为每月 170 美元)比运行单个 16GB 实例(在 AWS 上的成本约为每月 570 美元)更便宜。每个组件都在自己的环境中运行,这使得微服务对开发者和云原生更加友好。同样,微服务也增加了服务之间的吞吐量。一个服务上的内存密集型操作不会影响任何其他服务。
随着时间的推移,单体架构将消除团队的敏捷性,从而延迟应用程序的发布。这意味着当添加新功能或现有功能中的某些内容出现问题时,人们往往会花更多的时间来寻找解决方案以解决问题。单体架构将带来更多的低效,从而增加技术债务。
另一方面,微服务在架构方面减少了技术债务,因为一切都被简化为单个组件。团队往往更加敏捷,他们会发现处理变更更容易。
代码越少,错误越少,意味着痛苦更少,修复时间更短。
单体应用程序的工作量更大。想象一下有一个大型的单体应用程序,你需要在服务层中撤销一个if 条件。在更改代码后,通常需要几分钟来构建,然后你必须测试整个应用程序,这将降低团队的表现。
对于微服务架构,你可以几秒钟内重新启动或重新加载应用程序。当你需要撤销一个if 条件时,你不需要等待几分钟来构建和部署应用程序以进行测试,你可以在几秒钟内完成。这将减少执行日常任务所需的时间。
更快的迭代/发布和减少停机时间是提高用户参与度和用户保留率的关键,这反过来又会导致更好的收入。
人类大脑(除非你是超人)只能处理有限的信息量。因此,从认知角度来看,微服务帮助人们减少杂乱,专注于功能。这使生产力更高,部署更快。
采用微服务将:
-
最大化生产力
-
提升敏捷性
-
提升客户体验
-
加速开发/单元测试(如果设计得当)
-
提升收入
微服务架构的构建块
运行微服务架构需要许多组件/功能,并涉及许多高级概念。为了理解这些概念,想象我们有一个基于微服务的应用程序,用于我们的电子商务购物网站。这包括以下服务:
-
定价服务:负责根据需求给出产品的价格
-
需求服务:负责根据销售和剩余库存计算产品的需求
-
库存服务:负责跟踪库存中剩余的数量
-
许多其他服务
我们将在本节中看到的一些概念是:
-
服务注册表
-
服务发现
-
健康检查
-
动态路由和弹性
-
安全性(身份验证和授权)
-
容错和故障转移
服务注册表
微服务是独立的,但许多用例将需要它们相互依赖。这意味着某些服务要正常工作,需要从另一个服务获取数据,而该服务可能或可能不依赖于其他服务或来源。
例如,我们的定价服务将直接依赖于需求服务,而需求服务又依赖于库存服务。但这三个服务是完全独立的,也就是说,它们可以部署在任何主机、端口或位置,并且可以随意扩展。
如果定价服务想要与需求服务通信,它必须知道确切的位置,可以向其发送请求以获取所需信息。同样,需求服务也应该了解库存服务的详细信息,以便进行通信。
因此,我们需要一个服务注册表来注册所有其他服务和它们的位置。所有服务在服务启动时应自行注册到该注册表服务,当服务关闭时应自行注销。
服务注册表应充当服务数据库,记录所有可用的实例及其详细信息。
服务发现
服务注册表有服务的详细信息。但为了找出所需服务的位置以及要连接哪些服务,我们需要进行服务发现。
当定价服务想要与需求服务通信时,它需要知道需求服务的网络位置。在传统架构的情况下,这是一个固定的物理地址,但在微服务世界中,这是一个动态地址,它被动态分配和更新。
定价服务(客户端)必须在服务注册表中定位需求服务,确定位置,然后对可用的需求服务进行负载均衡。反过来,需求服务将响应请求客户端(定价服务)的请求。
服务发现用于发现客户端应连接的确切服务,以获取必要的详细信息。
服务发现帮助 API 网关发现请求的正确端点。
他们还将有一个负载均衡器,它调节流量并确保服务的高可用性。
根据负载均衡发生的地点,服务发现被分为:
- 客户端端发现模式
负载均衡将在客户端服务端发生。客户端服务将确定请求发送到何处,负载均衡的逻辑将位于客户端服务中。例如,Netflix Eureka (github.com/Netflix/eureka) 是一个服务注册表。它提供注册和发现服务的端点。
当定价服务想要调用需求服务时,它将连接到服务注册表,然后找到可用的服务。然后,根据配置的负载均衡逻辑,定价服务(客户端)将确定请求哪个需求服务。
服务将进行智能和特定于应用的负载均衡。缺点是,这为每个服务添加了一个额外的负载均衡层,这增加了开销:
- 服务器端发现模式
定价服务将请求负载均衡器连接到需求服务。然后,负载均衡器将连接到服务注册表以确定可用的实例,然后根据配置的负载均衡路由请求。
例如,在 Kubernetes 中,每个 Pod 都有自己的服务器或代理。所有请求都通过这个代理(它有一个与之关联的专用 IP 和端口)发送。
负载均衡逻辑从服务中移除,并隔离到一个单独的服务中。缺点是,它需要一个额外的、高度可用的服务来处理请求。
健康检查
在微服务世界中,实例可以随机启动、更改、更新和停止。它们也可以根据流量和其他设置进行上下伸缩。这需要一个健康检查服务,它将不断监控服务的可用性。
服务可以定期向这个健康检查服务发送它们的状态,这有助于跟踪服务状态。当一个服务宕机时,健康检查服务将停止从该服务接收心跳。然后,健康检查服务将标记该服务为宕机,并将信息级联到服务注册表。同样,当服务恢复时,心跳将被发送到健康检查服务。在接收到几个积极的心跳后,服务将被标记为 UP,然后信息被发送到服务注册表。
健康检查服务可以通过两种方式检查健康:
-
推送配置:所有服务将定期向健康检查服务发送心跳。
-
拉取配置:单个健康检查服务实例将定期查询系统的可用性。
这也要求有一个高可用性系统。所有服务都应该连接到这个服务以共享它们的心跳,并且这个服务必须连接到服务注册表以告诉它们服务是否可用。
动态路由和弹性
健康检查服务将跟踪可用服务的健康状态,并将有关服务健康状态的详细信息发送到服务注册表。
基于此,服务应智能地将请求路由到健康实例,并关闭不健康实例的流量。
由于服务动态更改其位置(地址/端口),每次客户端想要连接到服务时,它都应该首先检查服务注册表中的服务可用性。每个客户端连接也需要添加一个超时时间,在此时间之后,请求必须被服务或重试(配置)到另一个实例。这样我们可以最小化级联故障。
安全性
当客户端调用可用服务时,我们需要验证请求。为了防止不想要的请求堆积,我们应该有一个额外的安全层。客户端的请求应该经过认证和授权才能调用其他服务,以防止对服务的未授权调用。服务反过来应该解密请求,了解它是否有效或无效,并完成剩余的操作。
为了提供安全的微服务,它应该具有以下特征:
-
机密性:仅允许授权客户端访问和消费信息。
-
完整性:可以保证从客户端接收到的信息的完整性,并确保它不会被第三方修改(例如,当网关和服务相互通信时,任何一方都不能篡改或更改它们之间发送的消息。这是一种经典的中间人攻击)。
-
可用性:安全的 API 服务应该具有高度的可用性。
-
可靠性:应该可靠地处理请求并处理它们。
关于中间人攻击(MITM)的更多信息,请查看以下链接:www.owasp.org/index.php/Man-in-the-middle_attack.
容错和故障转移
在微服务架构中,可能有许多故障原因。优雅地处理故障或故障转移非常重要,如下所示:
-
当请求需要很长时间才能完成时,应设置一个预定的超时时间,而不是等待服务响应。
-
当请求失败时,识别服务器,通知服务注册表,并停止连接到该服务器。这样,我们可以防止其他请求发送到该服务器。
-
当服务没有响应时,关闭服务并启动一个新的服务以确保服务按预期工作。
这可以通过以下方式实现:
-
容错库,通过隔离不响应或响应时间超过服务等级协议(SLA)的远程实例和服务来防止级联故障。这防止其他服务调用失败的或不健康的实例。
-
分布式跟踪系统库有助于跟踪服务或系统的时序和延迟,并突出显示与协议 SLA 的任何差异。它们还帮助您了解性能瓶颈在哪里,以便您可以采取行动。
JHipster 提供了实现许多先前概念的选项。其中最重要的包括以下内容:
-
JHipster 注册表
-
HashiCorp Consul
-
JHipster 网关
-
JHipster 控制台
-
Prometheus
-
JHipster UAA 服务器
JHipster 注册表
JHipster 提供了 JHipster Registry (www.jhipster.tech/jhipster-registry/) 作为默认的服务注册表。JHipster Registry 是一个运行时应用程序,所有微服务应用程序都会注册并从中获取其配置。它还提供了监控和健康检查仪表板等附加功能。
JHipster 注册表由以下内容组成:
-
Netflix Eureka 服务器
-
Spring Cloud 配置服务器
Netflix Eureka 服务器
Eureka (github.com/Netflix/eureka) 包括以下内容:
- Eureka 服务器
Eureka 是一个基于 REST 的服务。它用于定位用于负载均衡和故障转移中间层的服务。
Eureka 服务器帮助在实例之间进行负载均衡。它们在可用性间歇的云环境中更有用。另一方面,传统的负载均衡器有助于在已知和固定实例之间进行流量负载均衡。
- Eureka 客户端
Eureka 提供了一个 Eureka 客户端,使得服务器之间的交互无缝。它是一个基于 Java 的客户端。
Eureka 充当一个中间层负载均衡器,帮助负载均衡中间层服务的宿主。它们默认提供基于轮询的简单负载均衡。可以根据需要使用包装器自定义负载均衡算法。
它们不能提供粘性会话。它们也非常适合基于客户端的负载均衡场景(如前所述)。
Eureka 对通信技术没有限制。我们可以使用任何东西,例如 Thrift、HTTP 或任何 RPC 机制进行通信。
假设我们的应用程序位于不同的 AWS 可用区。我们在每个区域注册一个 Eureka 集群,该集群只包含该区域可用的服务信息,并在每个区域启动 Eureka 服务器以处理区域故障。
所有服务都将注册到 Eureka 服务器并发送心跳。当客户端不再发送心跳时,服务将从注册表中移除,并且信息将在集群中的 Eureka 节点之间传递。然后,任何区域的任何客户端都可以查找注册信息以定位它,然后进行任何远程调用。此外,我们还需要确保区域之间的 Eureka 集群之间不相互通信。
Eureka 更倾向于可用性而不是一致性。这就是当服务连接到 Eureka 服务器并共享服务之间的完整配置时。这使得服务即使在 Eureka 服务器关闭的情况下也能运行。在生产中,我们必须在高度可用集群中运行 Eureka 以获得更好的一致性。
Eureka 还具有动态添加或删除服务器的能力。这使得它成为服务注册和服务发现的正确选择。
Spring 云配置服务器
在微服务架构中,服务本质上是动态的。它们将根据流量或任何其他配置进行上下文切换。由于这种动态特性,应该有一个单独的、 高度可用 的服务器来保存所有服务器都需要知道的基本配置细节。
例如,我们的定价服务需要知道注册服务在哪里以及它如何与注册服务通信。另一方面,注册服务应该是高度可用的。如果出于任何原因服务器必须关闭,我们将启动一个新的服务器。定价服务需要与配置服务通信,以了解注册服务。另一方面,当注册服务发生变化时,它必须将更改通知给配置服务器,然后配置服务器将信息级联到所有必要的服务中。
Spring 云配置服务器 (github.com/spring-cloud/spring-cloud-config) 为外部配置提供了服务器和客户端支持。
使用云配置服务器,我们有一个中心位置来管理所有环境中的外部属性。这个概念类似于客户端和服务器上基于 Spring 的环境属性源抽象。它们适用于任何用任何语言运行的应用程序。
它们还有助于在各个(开发/测试/生产)环境之间传输配置数据,并有助于更容易地进行迁移。
Spring 配置服务器提供了一个基于 HTTP、资源型的 API 用于外部配置。它们将加密和解密属性值。它们绑定到配置服务器并使用远程属性源初始化 Spring 环境。配置可以存储在 Git 仓库或文件系统中。
HashiCorp Consul
Consul (www.consul.io/) 主要是由 Hashicorp 提供的服务发现客户端。它侧重于一致性。Consul 完全是用 Go 编写的。
这意味着它将具有更小的内存占用。除此之外,我们还可以使用 Consul 与用任何编程语言编写的服务一起使用。
使用 Consul 的主要优势如下:
-
它具有更小的内存占用
-
它可以与用任何编程语言编写的服务一起使用
-
它侧重于一致性而非可用性
Consul 还提供服务发现、故障检测、多数据中心配置和存储。
这是 JHipster 注册的一个替代选项。在应用程序创建期间,可以选择使用 JHipster 注册或 Consul。
Eureka(JHipster 注册)要求每个应用程序使用其 API 进行注册和发现自身。它侧重于可用性而非一致性。它仅支持用 Spring Boot 编写的应用程序或服务。
另一方面,Consul 作为服务中的一个代理运行,检查健康信息和之前列出的其他一些额外操作。
服务发现
Consul 可以提供服务,其他客户端可以使用 Consul 来发现特定服务的提供者。通过 DNS 或 HTTP,应用程序可以轻松找到它们所依赖的服务。
健康发现
Consul 客户端可以提供任意数量的健康检查,这些检查可以与特定的服务相关联,也可以与本地节点相关联。这些信息可以被健康检查服务用来监控服务的健康状态,反过来,这些信息也被用来发现服务组件并将流量从不健康的节点路由到健康的节点。
K/V 存储
Consul 提供了一个易于使用的 HTTP API,这使得应用程序能够简单地使用 Consul 的键/值存储来动态配置服务、在当前领导者宕机时选举领导者,以及根据功能隔离容器。
多数据中心
Consul 支持开箱即用的多数据中心。这意味着你不必担心构建额外的抽象层来扩展到多个区域。
Consul 应该是一个分布式且高度可用的服务。为 Consul 提供服务的每个节点都运行一个 consul 代理,主要负责健康检查。这些代理将与一个或多个 Consul 服务器通信,这些服务器收集并添加这些信息。这些服务器之间也会选举出一个领导者。
因此,Consul 充当服务注册、服务发现、健康检查和 K/V 存储。
JHipster 网关
在微服务架构中,我们需要一个入口点来访问所有运行中的服务。因此,我们需要一个充当网关的服务。这将代理或路由客户端的请求到相应的服务。在 JHipster 中,我们为此提供了 JHipster 网关。
JHipster 网关是一个可以生成的微服务应用程序。它集成了 Netflix Zuul 和 Hystrix,以提供路由、过滤、安全、断路器等功能。
Netflix Zuul
在微服务架构中,Zuul 是所有请求的前门(门卫)。它充当边缘服务应用。Zuul 是为了在服务之间实现动态路由、监控、弹性和安全性而构建的。它还具有根据需要动态路由请求的能力。
冷知识:在 Ghostbusters 中,Zuul 是门卫。
Zuul 的工作基于不同类型的过滤器,使我们能够快速灵活地将功能应用于我们的边缘服务。
这些过滤器帮助我们执行以下功能:
-
身份验证和安全:识别每个资源的身份验证要求,并拒绝不符合要求的请求
-
洞察和监控:在边缘跟踪数据和统计信息,并深入了解生产应用
-
动态路由:根据健康和其他因素,根据需要动态地将请求路由到不同的后端集群
-
多区域弹性(AWS):为了多样化我们的弹性负载均衡器使用,并将我们的边缘更靠近我们的成员,跨 AWS 区域路由请求
如需更多关于 Zuul 的信息,请查看 github.com/Netflix/zuul/wiki.
Hystrix
Hystrix (github.com/Netflix/Hystrix) 是一个用于隔离远程系统、服务和第三方库访问点的延迟和容错库,旨在停止级联故障;并在不可避免失败的复杂分布式系统中实现弹性。
Hystrix 设计来完成以下任务:
-
在复杂的分布式系统中停止故障级联
-
保护系统免受网络依赖项故障的影响
-
控制系统的延迟
-
快速恢复并快速失败以防止级联
-
在可能的情况下进行回退和优雅降级
-
启用近乎实时的监控、警报和操作控制
在复杂的分布式架构中,应用程序有许多依赖项,每个依赖项最终都会在某些时候失败。如果主机应用程序没有从这些外部故障中隔离,它可能会被它们拖垮。
JHipster 控制台
JHipster 控制台 (github.com/jhipster/jhipster-console) 是一个用于微服务的监控解决方案,它使用 ELK Stack 构建。它附带预设的仪表板和配置。它以 Docker 镜像的形式提供作为运行时组件。
ELK Stack 由 Elasticsearch、Logstash 和 Kibana 组成。
Logstash 可以用来标准化数据(通常来自日志),然后使用 Elasticsearch 来更快地处理相同的数据。最后,使用 Kibana 来可视化数据。
Elasticsearch
Elasticsearch 是数据分析中广泛使用的搜索引擎。它帮助您从数据堆中快速提取数据。它还帮助提供实时分析和数据提取。它具有高度可扩展性、可用性和多租户性。
它还提供了基于全文搜索的文档保存选项。这些文档将根据数据的任何更改进行更新和修改。这反过来将提供更快的搜索并分析数据。
Logstash
Logstash (www.elastic.co/products/logstash) 会接收日志,处理它们,并将它们转换为输出。它们可以读取任何类型的日志,如系统日志、错误日志和应用程序日志。它们是这个堆栈的重工作组件,有助于存储、查询和分析日志。
它们作为事件处理的管道,能够通过过滤器处理大量数据,并与 Elasticsearch 一起快速交付结果。JHipster 确保日志以正确的格式存储,以便可以正确地分组和可视化。
Kibana
Kibana (www.elastic.co/products/kibana) 构成了 ELK 堆栈的前端。它用于数据可视化。它仅仅是一个日志数据仪表板。它有助于可视化那些否则难以阅读和解释的数据趋势和模式。它还提供了一个分享/保存的选项,这使得数据可视化更有用。
Zipkin
Zipkin (zipkin.io/) 是一个分布式跟踪系统。微服务架构总是存在延迟问题,需要一个系统来排查延迟问题。Zipkin 通过收集时间数据来帮助解决问题。Zipkin 还帮助搜索数据。
所有注册的服务都将报告时间数据到 Zipkin。Zipkin 根据接收到的跟踪请求为每个应用程序或服务创建一个依赖关系图。然后,它可以用来分析、找出处理时间较长的应用程序,并根据需要进行修复。
当发起请求时,跟踪度量将记录标签,将跟踪头添加到请求中,并最终记录时间戳。然后,请求被发送到原始目的地,响应被发送回跟踪度量,然后记录持续时间并与 Zipkin 收集器共享结果,Zipkin 负责存储信息。
默认情况下,JHipster 会生成一个禁用了 Zipkin 的应用程序,但可以在应用程序的 <env>.yml 文件中启用它。
Prometheus
在微服务架构中,我们需要持续监控我们的服务,任何问题都应立即触发警报。我们需要一个独立的服务,它可以持续监控并在发生任何异常时立即通知我们。
Prometheus 由以下部分组成:
-
Prometheus 服务器,负责抓取和存储时间序列数据
-
用于对应用程序代码进行度量的库
-
一个用于支持短期作业的推送网关
-
一个用于在 Grafana 中可视化数据的导出器
-
一个警报管理器
-
其他支持工具
Prometheus 是 JHipster 控制台的替代品。它提供监控和警报支持。这需要单独运行 Prometheus 服务器以获取更多信息。要开始使用 Prometheus,请访问 prometheus.io/。
它提供多维数据模型,这些模型是时间序列的,由指标名称和键值对标识。它具有灵活的动态查询语言。它支持开箱即用的时间序列提取,并通过中介网关推送时间序列。它具有多种图表和仪表板支持模式。
当出现故障时,它有助于发现问题。由于它是自主的,不依赖于任何远程服务,因此数据足以找到基础设施损坏的位置。
它有助于记录时间序列数据并通过机器或高度动态的服务导向架构进行监控。
在选择 Prometheus 而不是 JHipster 控制台时,需要考虑以下事项:
-
Prometheus 非常擅长利用应用程序的指标,而不会监控日志或跟踪。另一方面,JHipster 控制台使用 ELK 堆栈,并监控应用程序的日志、跟踪和指标。
-
Prometheus 可以用于查询大量时间序列数据。在 JHipster 控制台上,ELK 在跟踪和搜索指标和日志方面更加灵活。
-
JHipster 控制台使用 Kibana 可视化数据,而 Prometheus 使用 Grafana (
grafana.com/) 可视化指标。
JHipster UAA 服务器
JHipster 用户账户和授权(UAA)服务仅是一个 OAuth2 服务器,可用于集中式身份管理。为了访问受保护资源并避免对 API 的未授权访问,必须有一个授权服务器来授权请求并提供对资源的访问。
OAuth2 是一个授权框架,它根据令牌提供对请求的访问。客户端请求访问服务;如果用户被授权,应用程序将收到授权许可。在收到许可后,客户端从授权服务器请求令牌。一旦收到令牌,客户端将请求资源服务器获取必要的信息。
JHipster 支持标准 LDAP 协议,并通过 JSON API 调用。
JHipster UAA 是一个用户账户和授权服务,用于通过 OAuth2 授权协议确保 JHipster 微服务的安全性。
JHipster UAA 是一个由 JHipster 生成的应用程序,包括用户和角色管理。它还拥有一个完整的 OAuth2 授权服务器。这是灵活的,并且可以完全自定义。
在微服务架构中,安全性至关重要。以下是为确保微服务安全的基本要求。
它们应该在同一个地方进行身份验证。用户应该将整个体验视为一个单一单元。一旦最终用户登录到应用程序,他们应该能够访问他们有权访问的内容。他们应该在登录到系统的整个时间内持有会话相关信息。
安全服务应该是无状态的。无论服务如何,安全服务都应该能够为请求提供身份验证。
它们还需要有能力为机器和用户提供身份验证。它们应该能够区分它们并追踪它们。它们的功能应该是授权传入的请求,而不是识别最终用户。
由于底层服务是可扩展的,安全服务也应该有能力根据需求进行扩展和缩减。
当然,它们应该免受攻击。任何已知的漏洞都应该在需要时得到修复和更新。
之前的要求是通过使用 OAuth2 协议来满足的。
JHipster UAA 是一个集中式服务器,有助于对用户进行身份验证和授权。它们还有会话相关信息,以及系统内部可用的用户和角色管理帮助下的基于角色的访问控制。
OAuth2 协议通常提供基于提供的详细信息的令牌进行身份验证,这使得它们无状态并且能够从任何来源验证请求。
摘要
到目前为止,我们已经看到了微服务架构相对于单体应用程序的优势,以及我们需要运行微服务应用程序(如 JHipster Registry、Consul、Zuul、Zipkin、ELK 堆栈、Hystrix、Prometheus 和 JHipster UAA 服务器)所需的组件。在我们下一章中,我们将看到如何使用 JHipster 构建微服务。我们还将了解我们如何选择之前的组件,以及使用 JHipster 设置它们有多容易。
第九章:使用 JHipster 构建微服务
现在,是时候构建一个完整的微服务堆栈了。到目前为止,我们已经使用 JHipster 生成了、开发了和部署了一个单体应用程序,在前一章中,我们看到了微服务堆栈提供的优势。在这一章中,我们将探讨如何使用 JHipster 构建微服务。
我们将首先将我们的单体商店应用程序转换为微服务网关应用程序。接下来,我们将作为独立的微服务应用程序向我们的电子商务商店添加新的功能。然后我们将看到这些应用程序如何相互通信,并作为一个单一的应用程序为我们的最终用户提供服务。
在这一章中,我们将:
-
生成网关应用程序:
-
检查生成的代码
-
简要介绍 JWT
-
-
生成微服务应用程序:
-
发票服务
-
通知服务
-
应用程序架构
我们在第三章中使用了 JHipster 构建了一个在线电子商务商店,标题为使用 JHipster 构建单体 Web 应用程序。第三章。由于范围较小,这是一个更容易的选择。假设我们的电子商务商店在用户和范围方面增长巨大,导致了一种更加苛刻的情况。团队发现很难以单体架构快速推出功能,并希望对应用程序的各个部分有更多的控制。
解决这个问题的方法之一是采用微服务架构。该应用程序是使用 JHipster 创建的;迁移到微服务的选项更容易实现。JHipster 遵循代理微服务模式,其中在服务前面有一个聚合器/代理,它作为最终用户的网关。用更简单的话说,JHipster 创建了一个网关(处理所有用户请求)和通过网关与用户通信的各个服务。
也就是说,我们需要有一个网关服务,以及一个或几个可以独立运行的微服务应用程序。
我们的客户在发票方面遇到了一些问题,因为系统响应时间变长了。客户还抱怨他们没有收到通知,无法跟踪他们的订单。为了解决这个问题,我们将从我们的单体应用程序中移除发票服务,并使其成为一个独立的服务,然后创建一个独立的通知服务来处理通知。对于前者,我们将继续使用相同的 SQL 数据库。对于后者,我们将使用 NoSQL 数据库。
让我们看看我们将要生成的应用程序架构:

网关应用程序生成
我们将首先将我们生成的单体应用程序转换为微服务网关应用程序。
尽管微服务由内部的不同服务组成,但对于最终用户来说,它应该是一个单一、统一的产品。有许多服务被设计成以多种不同的方式工作,但应该有一个单一的入口点供用户使用。因此,我们需要一个网关应用程序,因为它们构成了应用程序的前端。
将内部合约和服务与外部用户分离。我们可能有一些应用级别的内部服务,我们不应该将其暴露给外部用户,因此这些可以被隐藏起来。这也为应用程序增加了另一个安全层。
更容易模拟服务进行测试,有助于在集成测试中独立验证服务。
将单体应用转换为微服务网关
我们已经生成了我们的单体应用程序以及我们的实体。作为单体应用程序生成的一部分,我们已经通过 JHipster CLI 选择了一些选项。当我们生成微服务网关应用程序时,我们将坚持相同的选项(数据库、认证类型、包名、i18n 等)。
注意:我们将在稍后看到如何在单体应用中应用我们应用的定制化。
现在是编码时间了,让我们使用 JHipster CLI 开始构建网关应用程序。
这里的第一步是将单体应用转换为具有几乎相同配置的微服务网关应用,就像我们创建单体应用时使用的配置一样。
现在,让我们转到终端(如果使用 Windows,则为命令提示符),首先导航到我们创建单体应用的文件夹。一旦进入文件夹,创建一个新的 Git 分支,这样我们就可以在完成后干净地合并回 master 分支:
> cd e-commerce-app/online-store
> git checkout -b gateway-conversion
现在,打开您最喜欢的文本编辑器或 IDE 中的.yo-rc.json文件,并更改以下值:

为了将单体应用转换为微服务网关应用,我们只需更改.yo-rc.json文件中的前述值。由于对于单体应用来说,服务发现不是强制性的,我们已经将服务发现类型添加到 Eureka 中。
显然,下一个更改是将应用程序类型从单体更改为网关。
应用程序生成
现在,让我们运行jhipster命令来生成应用程序:

JHipster 会询问您是否想要覆盖冲突的文件或使用您现有的文件,以及一些其他选项。用户可以使用任何想要的选项。
目前,我们将选择选项a。它将覆盖所有其他文件,包括高亮显示的文件。
如果您在应用程序上编写了大量自定义代码,此提示将非常有用。您可以选择适当的选项以获得所需的结果。

这将覆盖我们在单体应用程序中做的所有自定义设置。我们可以通过使用 GIT 从我们的 master 分支中 cherry pick 所需的更改,轻松地将它们带回这个分支。你可以遵循我们在第五章 Customization and Further Development 中看到的类似方法。一旦所有更改都应用了,我们就可以将这个分支合并回 master。你还需要在 第十章 Working with Microservices 中对实体文件做同样的操作。
生成新的网关
如果你不想转换现有的单体应用程序并希望从头开始,请遵循以下步骤。
在终端中,导航到 e-commerce-app 文件夹,创建一个名为 app-gateway 的新文件夹,然后切换到 app-gateway 目录,并运行 jhipster 命令。
因此,显然,第一个问题是,我们想创建哪种 类型 的应用程序?我们将选择微服务网关(第三个选项),然后按 Enter:

然后,我们将输入我们应用程序的基本名称。我们将使用名称 store:

由于我们正在使用微服务,存在很高的端口冲突风险。为了避免这些冲突,JHipster 将要求你为每个微服务应用程序(包括网关和应用程序)选择一个端口。默认情况下,我们将使用 8080 作为端口,但我们可以根据需要更改端口。现在,我们将使用默认端口,因为网关将在 8080 上运行,类似于我们的单体应用程序:

然后,我们输入我们应用程序的包名。我们将使用可用的默认名称,即 com.mycompany.store:

对于下一个问题,JHipster 将要求你配置注册服务。我们将选择要配置、监控和扩展我们的微服务和网关应用程序所需的注册服务。我们可以选择使用 JHipster 注册或 Consul。这也是可选的;我们在这里不需要选择任何注册服务。然后我们可以选择无服务发现。
当你选择无服务发现时,微服务 URL 将硬编码在属性文件中。

对于下一个问题,JHipster 将要求你选择认证类型。JHipster 提供了三种认证类型的选项,分别是 JWT、OAuth2 和基于 UAA 服务器的。JWT 是无状态的,而 UAA 在不同的服务器(和应用程序)上运行。另一方面,OAuth2 将提供授权令牌,而授权是在第三方系统上完成的。
JHipster 提供了一个创建 UAA 服务器应用程序的选项。
我们将在稍后更详细地了解 JWT。现在,我们将选择 JWT 认证。

我们将选择数据库类型。我们有选项选择 SQL 和 NoSQL。在 NoSQL 方面,我们可以选择 MongoDB 或 Cassandra。我们将选择 SQL 数据库:

然后,我们将选择我们将用于生产和开发的数据库。JHipster 提供了一个选项,可以在生产和开发环境中使用不同的数据库。这确实有助于更快、更轻松地启动应用程序开发。
我们将选择 MySQL 数据库用于生产:

然后,我们将选择基于磁盘持久性的 H2 用于开发:

在选择数据库之后,我们将为第二级 Hibernate 缓存选择“是”:

然后,我们将选择 Gradle 用于构建后端。我们有选项选择 Gradle 用于后端开发:

然后,我们可以选择我们需要的任何其他附加技术。JHipster 提供了一个选项来选择 Elasticsearch、使用 Hazelcast 进行集群应用程序、WebSocket 和 Swagger Codegen 用于基于 API 的开发以及基于 Kafka 的异步消息传递。我们将在此选择 WebSocket,类似于我们在单体存储中使用的:

由于我们的网关应用程序需要一个用户界面,对于下一个问题,我们可以选择我们需要的客户端框架。我们将选择Angular 5:

然后,我们将选择是否需要使用基于 SASS 的预处理器用于 CSS。我们将在此使用 SASS,因此我们将选择 y:

然后,我们将选择是否需要启用国际化支持。我们将为此选择“是”:

然后,我们将选择英语作为我们的母语:

然后,选择任何其他附加语言:

然后,选择任何其他测试框架,例如 Gatling、Cucumber 和/或 Protractor,因为这是必需的。我们将选择 Protractor 作为测试工具:

最后,JHipster 要求我们安装来自市场的任何其他生成器;我们将在此选择“否”:

这将创建所有必要的文件并使用 Yarn 安装前端依赖项:

现在,我们的网关应用程序已生成。JHipster 将自动将生成的文件提交到 Git;如果您希望手动执行此步骤,可以在执行期间传递 skip-git 标志,例如,jhipster --skip-git,然后手动执行以下步骤:
> git init
> git add --all
> git commit -am "converted into gateway application"
网关配置
网关应用程序的生成方式与单体应用程序类似,但涉及 Zuul 代理、Eureka 客户端和 Hystrix 的配置:
@ComponentScan
@EnableAutoConfiguration(exclude = {MetricFilterAutoConfiguration.class, MetricRepositoryAutoConfiguration.class, MetricsDropwizardAutoConfiguration.class})
@EnableConfigurationProperties({LiquibaseProperties.class, ApplicationProperties.class})
@EnableDiscoveryClient
@EnableZuulProxy
public class GatewayApp {
...
}
我们为我们的注册表服务选择了 JHipster 注册表。这将是一个独立的注册服务器,其他微服务应用程序和网关将自动注册自己:
-
@EnableDiscoveryClient添加到 Spring Boot 的主类中,这将启用 Netflix Discovery 客户端。微服务应用程序和网关需要将自己注册到注册表服务。它使用 Spring Cloud 的发现客户端抽象来查询其自己的主机和端口,然后将它们添加到注册服务器。 -
另一方面,Zuul 是门卫。这有助于将授权请求路由到相应的端点,限制每个路由的请求,并将必要的令牌中继到微服务应用程序。
-
@EnableZuulProxy帮助微服务网关应用程序根据application.yml中提供的配置将请求路由到相应的微服务应用程序:
zuul: # those values must be configured depending on the application specific needs
host:
max-total-connections: 1000
max-per-route-connections: 100
semaphore:
max-semaphores: 500
在网关应用程序中,我们已经指定了上述 Zuul 配置设置。一个代理可以保持打开的最大总连接数保持在 1000。一个代理可以保持打开的最大路由连接数保持在 100。信号量保持在最大 500。 (信号量类似于一个用于线程和进程之间同步的计数器。)
后端微服务端点的访问由 AccessControlFilter 控制,该过滤器将检查请求是否已授权,并允许请求端点:
public class AccessControlFilter extends ZuulFilter {
...
public boolean shouldFilter() {
...
return !isAuthorizedRequests(serviceUrl, serviceName,
requestUri);
}
...
}
Zuul 作为门卫,还充当速率限制器。在生成的应用程序中添加了一个速率限制过滤器,该过滤器限制了每个客户端发出的 HTTP 调用次数。这可以通过以下条件启用:
@ConditionalOnProperty("jhipster.gateway.rate-limiting.enabled")
public static class RateLimitingConfiguration {
...
}
SwaggerBasePathRewritingFilter 也被使用,这将有助于重写微服务 Swagger URL 基础路径:
@Component
public class SwaggerBasePathRewritingFilter extends SendResponseFilter {
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
if(!context.getResponseGzipped()) {
context.getResponse().setCharacterEncoding("UTF-8");
}
// rewrite the base path and send down the response
}
...
添加了一个 TokenRelayFilter 以从 Zuul 的忽略列表中移除授权。这将有助于传播生成的授权令牌:
@Component
public class TokenRelayFilter extends ZuulFilter {
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
Set<String> headers = (Set<String>) ctx.get("ignoredHeaders");
// JWT tokens should be relayed to the resource servers
headers.remove("authorization");
return null;
}
...
每个应用程序都应该有一个 Eureka 客户端,该客户端帮助在服务之间进行请求负载均衡,并将健康信息发送到 Eureka 服务器或注册表。Eureka 客户端在 application-dev.yml 中配置如下:
eureka:
client:
enabled: true
healthcheck:
enabled: true
fetch-registry: true
register-with-eureka: true
instance-info-replication-interval-seconds: 10
registry-fetch-interval-seconds: 10
instance:
appname: gateway
instanceId: gateway:${spring.application.instance-id:${random.value}}
lease-renewal-interval-in-seconds: 5
lease-expiration-duration-in-seconds: 10
status-page-url-path: ${management.context-path}/info
health-check-url-path: ${management.context-path}/health
metadata-map:
zone: primary # This is needed for the load balancer
profile: ${spring.profiles.active}
version: ${info.project.version}
我们选择启用健康检查,并将注册和复制的间隔设置为 10 秒,以及我们定义的租约更新间隔和过期持续时间。
我们将在 Hystrix 中配置超时,超过这个时间服务器将被认为是关闭的:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 10000
如果服务器在10秒内没有响应,则认为服务器已死亡,并在注册服务中注册。这确保了在服务器变为活动状态之前不会向该服务器发送后续请求。
JWT 身份验证
我们需要在微服务之间安全地传输信息。请求必须经过验证并数字签名,应用程序验证请求的真实性并对其进行响应。
我们需要在 REST 或 HTTP 世界中以紧凑的方式处理这些信息,因为信息需要与每个请求一起发送。JWT 正是为此而来。JWT 基本上是开放网络标准中的 JSON Web Tokens,有助于在各方(应用程序)之间安全地传输信息。JWT 将使用秘密、基于 HMAC 算法或使用公钥/私钥进行签名。它们是紧凑且自包含的。
对于高级用途,我们需要添加 Bouncy Castle(库)。
紧凑型:它们很小,可以发送到每个请求。
自包含:有效载荷包含有关用户的全部必要细节,这防止我们查询数据库进行用户身份验证。
JWT 由头部、有效载荷和签名组成。它们是 base64 编码的字符串,由.(点)分隔:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlNlbmRpbCBLdW1hciBOIiwiYWRtaW4iOnRydWV9.ILwKeJ128TwDZmLGAeeY7qiROxA3kXiXOG4MxTQVk_I
#Algorithm for JWT generation
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
)
JWT 是如何工作的
当用户登录系统时,会根据有效载荷(即用户信息和秘密密钥)生成一个令牌。生成的令牌将存储在本地。对于所有未来的请求,此令牌将添加到请求中,应用程序将在响应请求之前验证令牌:

令牌的格式如下:
Authorization: Bearer <token>
在 JHipster 中,我们使用来自 Okta 的JJWT(基于 Java 的 JSON Web Tokens)。这是一个基于简化构建者模式的库,用于生成和签名令牌作为生产者,以及解析和验证令牌作为消费者。
创建令牌:
public class TokenProvider {
...
public String createToken(Authentication authentication, Boolean rememberMe) {
...
return Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.signWith(SignatureAlgorithm.HS512, secretKey)
.setExpiration(validity)
.compact();
}
}
验证令牌:
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
log.info("Invalid JWT signature.");
log.trace("Invalid JWT signature trace: {}", e);
} catch (MalformedJwtException e) {
log.info("Invalid JWT token.");
log.trace("Invalid JWT token trace: {}", e);
} catch (ExpiredJwtException e) {
log.info("Expired JWT token.");
log.trace("Expired JWT token trace: {}", e);
} catch (UnsupportedJwtException e) {
log.info("Unsupported JWT token.");
log.trace("Unsupported JWT token trace: {}", e);
} catch (IllegalArgumentException e) {
log.info("JWT token compact of handler are invalid.");
log.trace("JWT token compact of handler are invalid trace: {}", e);
}
return false;
}
到目前为止,我们已经创建了一个网关应用程序,它将作为我们应用程序和服务的单一入口点。现在,我们将使用 JHipster 生成一个微服务应用程序。
微服务应用程序是服务。在这本书中,我们将构建两个示例服务,并讨论 JHipster 提供的特点,一个使用 SQL 数据库(MySQL),另一个使用 NoSQL 数据库(MongoDB)。这些服务是独立的,并且松散耦合。
使用 JHipster,你可以构建作为 REST 端点的微服务应用程序。
微服务应用程序 - 带有 MySQL 数据库的发票服务
我们可以从我们的单体应用程序中提取发票服务,将其分离,并使其成为独立的微服务应用程序。让我们称它为Invoice Service。此服务负责创建和跟踪发票。
应用程序生成
首先,让我们看看我们如何生成一个微服务应用程序。在 e-commerce-app 文件夹中,创建一个新的文件夹,用于存放微服务应用程序。让我们将文件夹命名为 invoice。进入目录,通过输入 jhipster 开始创建应用程序:
第一个问题是我们被要求选择我们希望创建的应用程序类型。我们必须选择微服务应用程序,然后点击 Enter:

然后,您需要为您的应用程序提供一个基本名称。我们将使用默认的应用程序名称,invoice(默认情况下,JHipster 选择与应用程序名称相同的文件夹名称):

然后,我们将选择应用程序必须运行的默认端口。默认情况下,JHipster 提示 8081 作为微服务的默认端口,因为我们使用 8080 作为网关应用程序:

然后,我们将选择默认的包名:

由于我们已选择 JHipster Registry 作为网关应用程序,因此在这里我们将选择相同的选项。同样,如果我们选择了 Consul 作为网关应用程序,那么我们也可以选择 Consul。我们甚至可以选择不使用注册表,然后添加任何自定义注册表:

然后,JHipster 会询问我们希望使用的身份验证类型。我们将选择 JWT 身份验证,与网关应用程序中选择的相同:

然后,选择我们需要使用的数据库类型。如突出所示,发票服务将使用 SQL 数据库。我们将选择 SQL 选项。JHipster 提供了一个选项来选择不使用数据库本身。当未选择数据库时,应用程序将生成不带数据库连接:

我们将选择生产数据库为 MySQL:

然后,我们将选择开发数据库为 H2,并使用基于磁盘的持久性:

然后,我们将选择 HazelCast 缓存作为 Spring 缓存抽象。Hazelcast 为所有会话提供共享缓存。可以在集群或 JVM 层面上保持持久数据。我们可以有不同的模式可供选择,包括单节点或多节点。
Ehcache 是一个本地缓存,它适用于在单个节点中存储信息。Infinispan 和 HazelCast 能够创建集群并在多个节点之间共享信息,其中 HazelCast 使用分布式缓存,每个节点相互连接。另一方面,Infinispan 是一个混合缓存:

然后,我们将选择 Hibernate 2 级缓存:

我们将选择 Gradle 作为构建工具:

然后,JHipster 会询问我们是否还有其他技术想要添加。我们这里不会选择任何内容,并使用默认选项:

然后,我们将选择国际化(i18n):

然后,我们将选择默认选项为英语:

选择我们需要的附加语言:

然后,选择我们想要添加到 Gatling 或 Cucumber 的任何其他测试框架。请注意,由于它不会生成前端应用程序,因此选项如 Protractor 未列出:

最后,我们将从 JHipster 市场中选择我们需要的任何其他生成器进行安装。目前,我们将不会选择任何其他生成器(默认选项):

然后,生成服务器应用程序:

我们已生成微服务应用程序。JHipster 将自动将生成的文件提交到 Git。如果您希望手动执行此步骤,可以在执行期间传递 skip-git 标志,例如,jhipster --skip-git,然后手动执行以下步骤:
> git init
> git add --all
> git commit -am "generated invoice microservice application"
微服务配置
生成的应用程序将不包含任何前端。再次强调,发票服务是基于 Spring Boot 的应用程序。安全功能在 MicroserviceSecurityConfiguration.java. 中配置。
忽略所有与前端相关的请求,因此当用户尝试访问任何与前端相关的资源,如 HTML、CSS 和 JS 时,请求将由发票服务忽略:
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/bower_components/**")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/test/**")
.antMatchers("/h2-console/**");
}
由于服务是独立的,它们可以部署并运行在具有不同 IP 地址的另一个服务器上。这要求我们默认禁用 CSRF(跨站请求伪造)。我们还将启用会话管理中的无状态会话策略。这使得我们的应用程序无法创建或维护任何会话。每个请求都是基于令牌进行认证和授权的。
我们还将使用会话管理中的无状态会话策略。这是最严格的会话策略。这不会允许我们的应用程序生成会话,因此我们的请求必须附有每个请求的(时间限制)令牌。这增强了我们服务的安全性。它们的无状态约束是使用 REST API 的另一个优点。
关于会话策略的更多选项和信息,请参阅以下文档:docs.spring.io/autorepo/docs/spring-security/4.2.3.RELEASE/apidocs/org/springframework/security/config/http/SessionCreationPolicy.html。
然后,一旦请求被授权(基于 JWT 令牌),就应该允许所有与 API 相关的请求和 Swagger 资源:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/swagger-resources/configuration/ui").permitAll()
.and()
.apply(securityConfigurerAdapter());
}
在资源方面,在bootstrap.yml中,我们已定义了与注册表相关的信息。
我们当前的微服务应用程序使用 JHipster 注册表作为注册表服务,以便在心跳中注册和注销其存在。我们需要提供注册表服务的密码,以便应用程序可以连接到注册表服务:
jhipster:
registry:
password: admin
此外,Spring Boot 服务的名称和默认的 Spring Cloud Config 参数已在bootstrap.yml中启用。我们还添加了必须连接以获取注册表服务配置的 URI:
spring:
application:
name: invoice
...
cloud:
config:
fail-fast: false # if not in "prod" profile, do not force to use Spring Cloud Config
uri: http://admin:${jhipster.registry.password}@localhost:8761/config
# name of the config server's property source (file.yml) that we want to use
name: invoice
...
与网关类似,其余的服务相关配置都是在application.yml文件中完成的。
Eureka 配置与网关应用程序中的配置完全相同。所有生成的应用程序都将具有类似的 Eureka 配置:
eureka:
client:
enabled: true
healthcheck:
enabled: true
fetch-registry: true
register-with-eureka: true
instance-info-replication-interval-seconds: 10
registry-fetch-interval-seconds: 10
instance:
appname: invoice
instanceId: invoice:${spring.application.instance-id:${random.value}}
lease-renewal-interval-in-seconds: 5
lease-expiration-duration-in-seconds: 10
status-page-url-path: ${management.context-path}/info
health-check-url-path: ${management.context-path}/health
metadata-map:
zone: primary # This is needed for the load balancer
profile: ${spring.profiles.active}
version: ${info.project.version}
数据库和 JPA 配置如下:
spring:
profiles:
active: dev
...
datasource:
type: <connector jar>
url: <db url>
username: <username>
password: <password>
...
jpa:
database-platform: <DB platform>
database: <H2 or MySQL or any SQL database>
show-sql: true
properties:
hibernate.id.new_generator_mappings: true
hibernate.cache.use_second_level_cache: true
hibernate.cache.use_query_cache: false
hibernate.generate_statistics: true
hibernate.cache.region.factory_class: com.hazelcast.hibernate.HazelcastCacheRegionFactory
hibernate.cache.hazelcast.instance_name: invoice
hibernate.cache.use_minimal_puts: true
hibernate.cache.hazelcast.use_lite_member: true
其余的配置与网关应用程序中生成的配置相似,可以根据您的需求进行调整或定制。
现在,我们可以与应用程序和注册表服务一起启动应用程序。由于应用程序首先尝试连接到注册表服务,如果指定的位置没有可用的注册表服务,则应用程序将不知道连接何处以及响应谁。
因此,发票服务已生成。现在,我们可以使用 NoSQL 作为后端数据库生成通知服务。
微服务应用程序 - 使用 NoSQL 数据库的通知服务
对于电子商务网站来说,订单的跟踪和用户在正确的时间收到通知是非常关键的。我们将创建一个通知服务,该服务将在用户的订单状态发生变化时通知用户。
应用程序生成
让我们在e-commerce-app文件夹中生成我们的第二个微服务应用程序(通知服务)。在将微服务应用程序保存在的新文件夹中创建一个新文件夹。让我们将文件夹命名为notification。进入目录,通过运行jhipster开始创建应用程序。
我们被问到的第一个问题是选择我们想要创建的应用程序类型。我们必须选择微服务应用程序,然后点击Enter:

然后,我们将选择默认的应用程序名称,notification:

然后,我们将选择应用程序的端口号。由于我们已将 8080 用于单体应用程序和 8081 用于发票服务,因此我们将使用端口号 8082 用于通知服务:

对于接下来的三个问题,我们将使用之前相同的选项:

然后,我们将选择 MongoDB 作为数据库。在选择了 MongoDB 之后,JHipster 现在将询问您希望用于开发和生产服务器的不同类型的数据库。我们将使用 MongoDB 作为开发和生产数据库:

对于剩余的问题,我们将选择与我们在发票服务中选择的选项类似的选项:

服务器已成功生成:

我们的微服务应用程序已生成。JHipster 将自动将生成的文件提交到 Git。如果您希望手动执行此步骤,可以在执行期间传递 skip-git 标志,例如,jhipster --skip-git,然后按照以下步骤手动执行:
> git init
> git add --all
> git commit -am "generated notification microservice application"
微服务配置
由于我们为两个微服务选择了类似选项,应用程序最终生成。生成的代码将类似,但数据库配置除外:

摘要
好的,在本章中,我们已经生成了一个网关应用程序和两个微服务应用程序。我们已经向您展示了使用 JHipster 生成微服务包是多么容易。现在,在我们运行应用程序之前,我们需要启动我们的注册服务器。
在下一章中,我们将启动注册服务器,我们还将了解如何将实体添加到我们的新服务中。
第十章:与微服务一起工作
在上一章中,我们使用 JHipster 创建了一个网关和两个微服务;现在,让我们看看我们如何进一步开发我们的微服务,包括我们的领域模型和额外的业务逻辑。由于我们将我们的在线商店单体应用转换为微服务架构,我们将看到我们使用 JDL 创建的领域模型如何转换为微服务领域模型。但在我们开始之前,我们需要设置一些工具以便与微服务一起工作。
所以在本章中,我们将看到以下主题:
-
如何设置 JHipster 注册表
-
如何在本地运行微服务设置
-
使用 JDL 创建领域模型
-
在 JHipster 中生成领域模型
让我们开始吧!
在本地设置 JHipster 注册表
我们已经创建了我们的网关和两个微服务应用。这些微服务使用两个不同的数据库。到目前为止,使用 JHipster 创建这些应用既简单又容易。
JHipster 提供了两种不同的选项,我们之前已经见过,即 Consul 和 JHipster 注册表。对于我们的用例,让我们选择 JHipster 注册表。我们已经在第八章中学习了 JHipster 注册表,微服务服务器端技术简介。现在,我们将看到如何在我们的本地开发环境中设置和启动它。
现在,这三个服务基本上充当 Eureka 客户端。我们需要一个服务注册表,它会在应用启动和停止时分别注册和注销应用;这就是 JHipster 注册表。Eureka 服务器(JHipster 注册表服务器)作为所有 Eureka 客户端的权威。
如同其名,JHipster 注册表作为一个注册服务,所有微服务应用和网关在应用启动和停止时都会自动注册/注销。
让我们回顾一下我们已经学到的内容。JHipster 注册表由一个 Eureka 服务器和 Spring Cloud Config 服务器组成,它们在以下方面提供帮助
-
Eureka 服务器帮助进行服务发现和请求负载均衡。
-
Spring Cloud Config 服务器作为一个单一的地方,我们将管理跨环境的应用外部属性。它还提供了一个用户仪表板。通过这个仪表板,用户可以管理和监控应用。
这使得 JHipster 注册表对于单体和微服务架构都是一个理想的选择。
如果你正在开发不同服务用不同语言编写的微服务应用,并且你更倾向于一致性而非服务的可用性,那么你可以选择 Consul。
我们有三种方法可以设置 JHipster 注册表以在本地运行。
我们可以下载 WAR 文件(预包装)并直接运行它,或者克隆我们的 GitHub 仓库并从那里运行。我们还可以使用 Docker 容器来运行它。我们现在将看到如何执行这些操作中的每一个。
您可以选择在生成单体应用程序时使用 JHipster 注册表。在生成过程中,对于问题“您想使用 JHipster 注册表来配置、监控和扩展应用程序吗?”选择是。
使用预打包的 WAR 文件
从注册表的发布页面下载预打包的可执行 WAR 文件的最新版本(github.com/jhipster/jhipster-registry/releases):
- 打开您的终端,然后输入以下命令,将
<version>替换为最新版本。如果您使用 Windows 并且没有设置curl,您也可以通过在浏览器中访问链接来下载文件:
> curl https://github.com/jhipster/jhipster-registry/releases/download/v*<version>*/jhipster-registry-*<version>*.war
- 这将从 JHipster 注册表项目下载最新的 WAR 文件。下载完成后,我们可以使用以下命令运行 JHipster 注册表:
> ./jhipster-registry-<version>.war --security.user.password=admin --jhipster.security.authentication.jwt.secret=secret-key --spring.cloud.config.server.native.search.locations=file:./central-config
注意,我们向我们的注册表服务器传递了一些值;它们是:
--security.user.password=admin
由于 JHipster 注册表建立在 JHipster 应用程序之上,它将具有默认的管理员用户。对于该管理员用户,我们使用 Spring 属性 security.user.password 提供密码:
--jhipster.security.authentication.jwt.secret=secret-key
然后,我们以两种方式定义应用程序的 JWT 令牌。我们可以在环境变量中设置信息并使用它,或者在我们定义密钥时添加此键值。这也使用 spring config 属性来设置属性:
--spring.cloud.config.server.native.search.locations
- 最后,我们告诉 JHipster 注册表在哪里可以找到 Spring Cloud Config 服务器提供的中央配置。
在我们了解在这里传递什么值之前,我们需要了解 spring-cloud-config 上下文中的 Spring 配置文件。Spring Cloud Config 默认支持 native 和 git 配置文件。
在 native 配置文件中,云配置服务器期望其属性在文件中定义,我们必须将文件位置传递给 JHipster 注册表。另一方面,git 配置文件将期望设置 --spring.cloud.config.server.git.uri。
例如,注册表的示例 JHipster 配置文件如下:
configserver:
name: JHipster Registry config server
status: Connected to the JHipster Registry config server using ...
jhipster:
security:
authentication:
jwt:
secret: awesome-my-secret-token-to-change-in-production
这也可以在注册表的 Spring Cloud 配置页面中看到:

就像 JHipster 应用程序提供 dev 和 prod 配置文件一样,JHipster 注册表也支持 dev 和 prod 配置文件。默认情况下,启动时将在 dev 配置文件下启动,但我们可以使用 --spring.profiles.active=prod,git 来使其在 prod 配置文件下运行,传递 git URL,并在那里定义配置属性。对于生产模式,git 是在 Spring Cloud 服务器上使用的首选配置文件。
从源码构建
如果您想从事前沿技术工作并对探索添加到 JHipster 注册表的最新功能感兴趣,那么您可以进一步操作并从 GitHub 克隆存储库:
- 导航到您首选的文件夹并运行以下命令:
> git clone https://github.com/jhipster/jhipster-registry
- 一旦克隆,使用以下命令导航到文件夹:
> cd jhipster-registry
- 按如下方式以开发模式运行应用程序:
> ./mvnw
您也可以按如下方式以生产模式运行:
> ./mvnw -Pprod
- 您还可以打包并运行 WAR 文件:
> ./mvnw -Pprod package
> target/jhipster-registry-<version>.war --spring.profiles.active=prod,git
Docker 模式
您还可以从提供的 Docker 镜像启动 JHipster Registry。我们生成的应用程序已经包含了所需的 docker-compose 文件。
例如,在我们的网关应用程序中,在 src/main/docker/jhipster-registry.yml 下查找 docker-compose 文件。
我们可以通过在终端中键入以下命令来启动 JHipster Registry:
> cd gateway
> docker-compose -f src/main/docker/jhipster-registry.yml up
docker compose 文件(src/main/docker/jhipster-registry.yml)包含:
version: 2
services:
jhipster-registry:
image: jhipster/jhipster-registry:v3.2.3
volumes:
- ./central-server-config:/central-config
environment:
- SPRING_PROFILES_ACTIVE=dev
- SECURITY_USER_PASSWORD=admin
- JHIPSTER_REGISTRY_PASSWORD=admin
- SPRING_CLOUD_CONFIG_SERVER_NATIVE_SEARCH_LOCATION= file:./central-config
ports:
-8761:8761
这将镜像定义为 jhipster-registry 并指定版本(最新版)。它还定义了一个挂载 central-config 的卷,这是 Spring Cloud Config 服务器用于定义微服务应用程序和网关的应用程序属性所必需的。这里还定义了环境变量,如 Spring 配置文件、管理员密码和云配置搜索位置。它还指定了暴露的端口(8761)。
当然,这需要在机器上安装并运行 Docker。
在所有上述情况(如果成功)中,它将在端口 8761 上启动 JHipster Registry,并默认使用原生模式(除非明确更改)。您实际上可以导航到 http://localhost:8761 来访问 JHipster Registry,然后使用我们启动应用程序时使用的密码登录到应用程序。
本地运行生成的应用程序
现在我们已经准备就绪。我们已经生成了一个网关应用程序,我们有一个带有 SQL 数据库的微服务,它在开发配置下使用 H2,在生产配置下使用 MySQL(发票应用程序),我们还有一个带有 MongoDB 的微服务(通知应用程序),最后我们刚刚在本地完成了 JHipster Registry 的设置。现在是我们开始本地运行一切并查看我们的微服务设置如何无缝工作的时候了。
网关应用程序页面
我们现在转到终端,然后进入 e-commerce-app 文件夹。导航到 online-store 文件夹,并以开发模式启动网关应用程序:
> cd online-store
> ./gradlew
这将在端口 8080 上启动我们的网关应用程序。让我们在我们的浏览器中打开 http://localhost:8080:

然后,我们可以点击主页上的登录按钮或从顶部菜单选择 Account/sign in,然后分别输入用户名和密码为 admin 和 admin。
一旦以管理员用户身份登录,您可以看到管理菜单:

在管理菜单中,您可以找到以下页面:

这包括以下内容:
网关:网关页面将显示该应用程序作为网关的微服务应用程序列表。它还将显示路由和处理路由的服务,以及可用于路由的服务器:

目前,没有启动微服务应用程序,所以页面是空的。一旦我们启动通知和发票服务,我们将看到这个页面是如何变化的。
用户管理:这与单体用户管理类似,并包含基本用户信息和管理工作。
指标:指标页面包含有关 JVM 指标和服务的/数据库统计信息。这又与单体应用类似。除此之外,它还显示了已注册微服务应用的指标统计。
健康:健康页面显示了我们在应用程序中拥有的各种服务的基本健康信息:

与单体应用类似,它显示了磁盘空间和数据库。但除此之外,它还显示了发现网络的健康状况(即 discoveryClient 和 Eureka 服务器)。它还显示了微服务配置服务器的健康状况,即 spring-cloud-config-server,然后显示了我们所使用的断路器的健康状况(Hystrix)。
配置、审计、日志和 API 页面与我们之前看到的单体应用类似。
JHipster 注册表页面
由于我们在端口 8761 上启动了注册表服务器,我们可以通过 http://localhost:8761 访问并使用 admin 作为用户名,以及我们在启动应用程序时提供的密码 admin 登录。
登录后,JHipster 注册表以仪表板的形式显示以下信息:

系统状态
这个面板将显示应用程序正在运行的环境以及应用程序运行了多长时间(系统运行时间)。
低于续订阈值
我们的应用程序必须向注册表服务发送心跳信号,以通知注册表应用程序正在运行。注册表服务依赖于这个心跳信号来注册和注销应用程序。也就是说,应用程序的存在是通过这个心跳 ping 来确定的。这就是在续订阶段会发生的事情。
然而,当 Eureka 服务器启动时,它会尝试从附近的服务获取所有关于实例注册的信息。如果附近的服务由于任何原因失败,那么它将尝试连接到所有对等节点以获取信息。如果 Eureka 服务器能够获取所有服务器的信息,那么它将根据接收到的信息设置续订阈值。基于这些信息,JHipster 注册表将保留有关当前级别是否低于指定的续订阈值的信息,并在 UI 中通知用户。
已注册实例
这将显示已注册到注册表中的实例的基本信息。由于我们只启动了网关服务,这里将只看到一个实例。基本上,这将列出所有连接到此注册表服务的实例。
它显示了系统的状态、系统名称,然后是实例 ID。实例 ID 是根据 JHipster Registry 的application.yml配置生成的,并分配一个随机值。
一般信息和健康
它还显示了有关 JHipster Registry 服务的一般信息以及服务集群的健康信息,类似于网关健康信息。这里的数据是在 Spring Actuator 健康和指标端点的帮助下获取的。
注意健康部分中的 UNKNOWN(参见图表)。它告诉我们 Eureka 服务器没有以高可用模式运行,或者只有一个 JHipster Registry 实例正在运行。当您启动另一个注册表实例(即使应用程序高可用)时,它就会消失。
应用程序列表页面
此页面列出了在 JHipster Registry 服务中注册的应用程序。
导航到管理 | 网关:

它显示了以下信息:
-
当前实例 ID 及其名称
-
实例的当前状态
-
部署的版本
-
配置文件
-
部署的区域
版本号是从 Gradle 和 Maven 项目的build.gradle或pom.xml中获取的。
此处的区域通常指的是亚马逊区域。它被 Ribbon 用于将请求路由到最近的服务器。如果您不使用亚马逊,此配置将无济于事,这就是为什么我们将其设置为主要(否则负载均衡算法将不正确)。
管理模块中的所有页面都将有一个下拉菜单,列出已注册的各种实例,我们可以选择该实例以查看其指标、健康、配置和其他信息,具体取决于我们所在的页面。
指标页面
默认情况下,这将显示注册表的 JVM 指标及其服务统计信息:

我们可以从提供的下拉菜单中选择任何实例并查看其统计信息,因此,使 JHipster Registry 成为提供所有必要洞察的单一信息点,了解您的微服务架构。例如,在选择网关应用程序实例后,我们将获得网关相关信息:

健康页面
健康页面将列出注册表本身及其所有连接的实例的健康状况。例如,在选择网关应用程序实例后,我们将获得以下网关相关信息:

配置页面
与健康和指标页面类似,JHipster Registry 将提供所有连接到它的实例的详细配置,我们可以从下拉菜单中选择实例:
以下图像显示了网关应用程序的配置屏幕

日志页面
与前面的页面类似,日志页面也会显示应用程序的实时日志。这在出现故障时进行调试和获取更多信息时非常有用:
日志在应用层面进行格式化。这里的控制台显示了tail -f用于合并日志。
下图显示了网关应用程序的日志:

Swagger API 端点
微服务架构高度依赖于网关和服务、服务和注册表、网关和注册表之间的 API 调用。因此,对于开发者和用户来说,了解他们可以访问的 API 端点以及访问这些端点所需的信息至关重要。
这可能是一项大量工作。幸运的是,像 Swagger 这样的库可以提供帮助。我们只需将标准注释添加到方法中,然后 Swagger API 将执行从这些方法中提取信息并将其转换为美观用户界面的必要工作:

前面的图像显示了默认生成的 Swagger UI 页面。它列出了所有可用的端点,然后提供了它提供的操作列表。它显示了我们可以在这里构建请求并测试输出的游乐场。
通常,Swagger API 文档仅在开发模式下可用。如果您正在开发 API 服务,并且需要将此暴露给最终用户或使用您服务的开发人员,您可以通过设置swagger配置文件以及prod来在生产中启用它,通过设置spring.profiles.active=prod,swagger。
与其他页面类似,此页面也会列出连接到此注册服务器的各种实例,我们可以从下拉菜单(右上角)中选择它们,以查看各种应用程序提供的 API:

网关 API 中列出的操作将提供以下信息:

它列出了AccountResource文件中所有可用的操作。它显示了方法类型(GET / POST / PUT / DELETE),然后是AccountResource文件中存在的端点和方法名称:

点击任何一个端点,它会显示关于响应类、响应错误、响应内容类型以及响应对象结构的详细信息。此外,它还显示了模型对象的构建方式。这对于想要访问这些 API 的最终用户特别有帮助:
UserDTO {
activated (boolean, optional),
authorities (Array[string], optional),
createdBy (string, optional),
createdDate (string, optional),
email (string, optional),
firstName (string, optional),
id (integer, optional),
imageUrl (string, optional),
langKey (string, optional),
lastModifiedDate (string, optional),
lastModifiedBy (string optional),
lastName (string, optional),
login (string)
}
接下来,有一个选项在点击按钮后尝试端点:

它显示了请求及其响应。它还显示了如何构建请求,包括身份验证令牌。它提供了服务器返回的响应代码和响应头信息,这对于 API 程序员来说也非常有用:

在本地运行发票和通知应用程序
我们已经启动了网关和注册服务。然后我们可以进入我们的发票和通知应用程序文件夹,并在本地运行它们:
> cd invoice
> ./gradlew
打开另一个终端并运行以下命令:
> cd notification
> ./gradlew
这将分别在8081和8082端口上运行它们:

应用程序启动时,它也会尝试连接到 JHipster 注册表并注册自己。一旦服务器启动,你可以查看前面的消息,以确保它已连接到 JHipster 注册表。
你也可以通过你的网关应用程序进行测试。登录到网关应用程序,然后导航到管理 | 网关:

在这里,你可以看到两个微服务应用程序,发票和通知,都已启动,并且它们分别在各自的 URL 上可用。
你还可以检查 JHipster 注册表服务以列出已注册的实例:

类似地,JHipster 注册表中的所有其他页面都将开始显示发票和通知作为实例之一,我们可以直接从 JHipster 注册表中获取它们的健康状态、配置、日志和指标。
如果你已经跟随书籍学习,这将是你将拥有的目录结构:

在 JDL 中建模实体
由于我们在设置单体应用时已经使用了 JDL 工作室,现在是时候更新它了。
如前一章所述,我们将实体从单体应用迁移到网关应用,然后,从单体应用中移除与发票相关的实体,在发票微服务中使用它们,然后更新相关的发票引用。最后,我们为通知微服务创建实体。
以下图表显示了我们的新 JDL 实体模型:

发票是迁移到单独服务的理想候选者。我们可以完全解耦发票及其依赖项,但这将在我们的当前应用程序中引起一个问题——ProductOrder实体与Invoice表相关联,我们必须在保持关系(但不是作为外键)的同时,将其作为间接键移除依赖关系,并在ProductOrder中与Invoice实体连接。
这可以通过两种方式实现。我们可以将外键改为 ProductOrder 实体中的另一列,或者创建另一个名为 InvoiceOrder 的实体,它只包含 InvoiceID 和 ProductOrder ID,并将其映射到 ProductOrder 实体。
前者基本上保持了表结构不变,并允许更容易的迁移。后者将以牺牲规范化为代价增加隔离性,并且在高性能应用程序中被大量使用。正如你所见,它们都有各自的优点和缺点。你应该采取的方法完全取决于你的需求。我们将考虑第一种方法。
作为第一步,我们将从 online-store.jh 中定义的 JDL 中删除产品所有者之间的关系,如下所示:
relationship OneToMany {
...
ProductOrder{invoice} to Invoice{order},
...
}
删除高亮显示的行,并将所有与发票相关的实体移动到 invoice-jdl.jh 文件。
然后,转到产品订单实体,添加一个 invoiceId 字段,并将其标记为 Long 类型。这是一个可选字段,因此不需要必需的关键字:
entity ProductOrder {
placedDate Instant required
status OrderStatus required
invoiceId Long
code String required
}
可以使用 JDL 支持的 microservice 关键字对微服务实体进行标记。这有助于 JHipster 识别属于特定微服务的实体。它遵循我们之前看到的相同的 JDL 选项语法:
<OPTION> <ENTITIES | * | all> [with <VALUE>] [except <ENTITIES>]
-
microservice关键字 -
紧接着是实体名称,如果多个则用逗号分隔
-
紧接着是
with关键字 -
紧接着是微服务的名称
我们应该为微服务的实体使用不同的文件,因此创建两个文件,invoice-jdl.jh 和 notification-jdl.jh,分别包含与发票和通知相关的实体,以及原始文件。
然后,我们在 JDL 中将现有的 Invoice 实体映射到微服务:
microservice Invoice, Shipment with Invoice
entity Invoice {
date Instant required
details String
status InvoiceStatus required
paymentMethod PaymentMethod required
paymentDate Instant required
paymentAmount BigDecimal required
}
enum InvoiceStatus {
PAID, ISSUED, CANCELLED
}
entity Shipment {
trackingCode String
date Instant required
details String
}
enum PaymentMethod {
CREDIT_CARD, CASH_ON_DELIVERY, PAYPAL
}
relationship OneToMany {
Invoice{shipment} to Shipment{invoice}
}
service * with serviceClass
paginate Invoice, Shipment with pagination
microservice * with invoice
然后,是时候创建另一个 JDL 文件来保存通知服务的详细信息。创建一个名为 notification-jdl.jh 的文件,并将通知实体添加到其中:
entity Notification {
date Instant required
details String
sentDate Instant required
format NotificationType required
userId Long required
productId Long required
}
enum NotificationType {
EMAIL, SMS, PARCEL
}
然后,我们将这些实体绑定到 Notification 微服务,如下所示:
microservice * with notification
就这样。我们已经为我们的微服务定义了领域模型。
微服务上的实体生成
我们的 JDL 现在可以使用了。下一步将是生成网关和服务中的实体。首先,我们将从 JDL 工作室下载我们的 JDL 文件。下载后,我们将文件分别移动到我们的网关和微服务应用程序中。
一旦移动到网关应用程序文件夹,请运行以下命令。这将创建网关的实体,并在网关中为微服务实体创建 UI:
> cd e-commerce-app/online-store
> jhipster import-jdl online-store.jh
> jhipster import-jdl ../notification/notification-jdl.jh --skip-ui-
grouping
> jhipster import-jdl ../invoice/invoice-jdl.jh --skip-ui-grouping
--skip-ui-grouping 标志禁用了 JHipster 5.x 中引入的微服务客户端实体组件分组行为。这有助于我们在不发生许多冲突的情况下从单体应用程序中挑选我们的更改。当你在不同的服务中有相同名称的实体时,这种分组行为很有用。
运行以下命令以创建发票服务的后端:
> cd e-commerce-app/notification
> jhipster import-jdl notification-jdl.jh
运行以下命令以创建发票服务的后端:
> cd e-commerce-app/invoice
> jhipster import-jdl invoice-jdl.jh
JHipster 将询问是否覆盖已修改的文件;请选择适用的文件。我们将使用"a" ->,这意味着将覆盖一切。
不要忘记将我们对原始单体中的实体所做的任何更改 cherry-pick 回网关和微服务。
不要忘记在每个服务和网关中提交更改。你也可以通过运行git init将整个e-commerce-app文件夹初始化为git源,如果你喜欢的话:
> cd e-commerce-app
> git init
> git add --all
> git commit -am "entities generated using JDL"
解释生成的代码
在通知服务中,一旦我们生成了应用程序,以下文件被创建:

如你所见,这只会生成后端文件,而不会生成前端文件,因为它们已经在网关服务中生成。
类似地,在发票应用中运行jhipster import-jdl命令将生成类似的 Java 文件:

网关应用
在网关应用中,整个前端(包括微服务中的实体)都将生成。由于 JHipster 生成基于代理的微服务,所有前端代码都将位于网关应用中:

ProductOrder.Java将移除Invoice作为外键,然后使用我们在这里传递的长整数值:
/**
* A Product Owner
*/
...
public class ProductOrder implements Serializable {
...
@Column(name = "invoice_id")
private Long invoiceId;
...
}
因此,应用程序已完全生成。现在,是时候运行它了。
启动三个控制台(因为我们需要运行三个应用程序)。如果我们已经有应用程序在运行,那么我们只需要编译它们,Spring devtools 将自动重新加载应用程序。确保注册表已经运行:
-
在控制台 1 中,导航到网关,如果服务器尚未运行,则使用
./gradlew启动服务器,否则使用./gradlew compileJava进行编译 -
在控制台 2 中,导航到发票,如果服务器尚未运行,则使用
./gradlew启动服务器,否则使用./gradlew compileJava进行编译 -
在控制台 3 中,导航到通知,如果服务器尚未运行,则使用
./gradlew启动服务器,否则使用./gradlew compileJava进行编译
解释生成的页面
应用程序成功启动后,是时候打开你喜欢的浏览器并导航到http://localhost:8080的网关服务器。
登录后,你可以看到实体在网关应用中生成,并且它们在实体导航菜单下可用。
它包括所有网关实体以及微服务实体:

这是网关应用中创建的发票屏幕:

尝试创建一些实体以验证一切是否运行正常。
摘要
由于我们在本章中做了很多事情,让我们回顾一下到目前为止我们已经做了什么。
我们已成功生成一个网关和两个微服务。我们已下载 JHipster Registry 并在本地启动它。我们已成功隔离并生成了通知和发票服务的实体文件。我们最终启动了所有应用程序,并看到了事物是如何生成的,并且能够创建微服务应用程序。最后但同样重要的是,我们还将所有更改提交到了 Git(换句话说,达到了一个检查点)。
第十一章:使用 Docker Compose 进行部署
我们已经生成了应用程序,并且它已准备好投入生产。在本章中,我们将重点介绍如何使用 Docker Compose 部署应用程序。我们还将了解 JHipster 提供的各种部署选项,以及如何将我们的注册表和控制台与应用程序一起部署。
在本章中,我们将探讨:
-
Docker Compose 的简要介绍
-
启动 Kubernetes
-
介绍 OpenShift
-
解释 Rancher
然后我们将讨论使用 JHipster Registry 和 JHipster Console 进行本地讨论
介绍微服务部署选项
应用程序的成功不仅取决于我们设计得有多好。它还取决于我们实施(部署和维护)得有多好。
在低可用性环境中设计良好的微服务应用是无用的。因此,决定一个能增加其成功机会的部署策略同样重要。在部署方面,有大量的工具可供选择。每个工具都有其优缺点,我们必须选择一个适合我们需求的。JHipster 目前提供子生成器来创建配置文件,以便通过以下方式容器化、部署和管理微服务:
-
Docker
-
Kubernetes(也有助于编排您的部署)
-
OpenShift(也提供私有云部署)
-
Rancher(也提供完整的容器管理)
我们将在以下章节中详细说明。
Docker Compose 的简要介绍
将代码发送到服务器总是困难的,尤其是当你想要扩展它时。这主要是因为我们必须手动创建相同的环境,并确保应用程序具有所有必要的连接性(到其他服务),这是必需的。这在团队发送和扩展代码时是一个主要痛点。
将代码发送到服务器是困难的。
容器是这个领域的游戏改变者。它们帮助将整个应用程序及其依赖项捆绑在一个可发送的容器中,而我们所需做的就是提供一个环境,让这些容器可以运行。这简化了将代码发送到服务器以及开发团队之间的过程。这也减少了团队确保应用程序在环境中无缝运行所需的时间。
容器解决了应用程序部署问题,但我们如何扩展它们?
Docker Compose 工具在这里提供了帮助。首先,让我们看看 Docker Compose 是什么,然后看看它解决了什么问题。
Docker Compose 是一个工具,它可以帮助使用单个文件定义和运行多容器 Docker 应用程序。也就是说,我们使用.yaml文件来定义应用程序的要求和/或依赖关系。然后,使用docker-compose,我们可以创建新的部署并启动在docker-compose文件中定义的应用程序。
那么,docker-compose文件中需要什么?
以下是一个示例 docker-compose 文件,它将在端口 5000 上启动 Redis 数据库:
version: '3'
services:
web:
build: .
ports:
- "5000:5000"
redis:
image: "redis:alpine"
docker-compose 文件的第 一行应该是 docker-compose 工具的版本。
然后我们需要指定应用程序运行所需的所有必要服务。它们应该在 services: 部分中定义。
我们也可以在这里定义多个服务,并为每个服务命名(例如 web 和 redis)。
这随后是构建 service 的方法(通过命令构建或引用 Dockerfile)。
如果应用程序需要任何端口访问,我们可以使用 5000:5000(即内部端口:外部端口)进行配置。
然后,我们必须指定卷信息。这基本上告诉 docker-compose 从指定的位置提供文件。
一旦我们指定了应用程序所需的各项服务,就可以通过 docker-compose 启动应用程序。这将启动整个应用程序以及服务,并将 services 暴露在指定的端口上。
使用 docker-compose,我们可以执行以下操作:
-
启动:
docker-compose -f <docker_file> up -
停止:
docker-compose -f <docker_file> down
我们还可以执行以下操作:
-
列出正在运行的服务及其状态:
docker ps -
日志:
docker log <container_id>
在 compose 文件中,我们可以添加项目名称,如下所示:
version: '3'
COMPOSE_PROJECT_NAME: "myapp"
services:
web:
build: .
ports:
- "5000:5000"
redis:
image: "redis:alpine"
这可以用于识别多个环境。借助这个工具,我们可以隔离多个环境。这有助于我们处理跨各种 dev、QA 和 prod 环境的多个实例。
Docker-compose 本身就是一个部署应用程序及其所需所有服务的优秀工具。它提供基础设施即代码。它是开发、QA 和其他环境的绝佳选择,除了生产环境。但为什么?
Docker-compose 确实是一个创建和启动应用程序的好工具。然而,当你想要更新现有的容器时,将会有一个确定性的停机时间,因为 docker-compose 将重新创建整个容器(虽然有一些方法可以使这成为可能,但 docker-compose 在这个领域仍需要一些改进。)
启动 Kubernetes
根据 Kubernetes 网站:
Kubernetes 是一个用于自动化部署、扩展和管理容器化应用程序的开源系统。
它是一个简单而强大的工具,用于自动部署、扩展和管理容器化应用程序。当你推出新应用程序或更新现有应用程序时,它提供零停机时间。你可以根据某些因素自动扩展和缩小。它还提供自我修复功能,即 Kubernetes 会自动检测失败的应用程序并启动一个新的实例。我们还可以定义可以在实例间使用的密钥和配置。
Kubernetes 主要关注零停机时间生产应用程序的升级,并根据需要对其进行扩展。
Kubernetes 中单个可部署的组件被称为 pod。这可以简单到容器中运行的一个进程。一组 pod 可以组合在一起形成一个 deployment。
类似于 docker-compose,我们可以在单个 YAML 文件或多个文件(根据我们的方便)中定义应用程序及其所需的服务。
在这里,我们同样从一个 Kubernetes 部署文件中的 apiVersion 开始。
以下代码是一个示例 Kubernetes 文件,它将启动一个 Nginx 服务器:
apiVersion: v1
kind: Service
metadata:
name: nginxsvc
labels:
app: nginx
spec:
type: NodePort
ports:
- port: 80
protocol: TCP
name: http
- port: 443
protocol: TCP
name: https
selector:
app: nginx
接着是类型,它可以是 pod、deployment、namespace、ingress(负载均衡 pod)、role 以及更多。
Ingress 在服务和互联网之间形成一层,这样在将连接发送到集群上的 Kubernetes 服务之前,所有传入的连接都由 ingress 控制器进行控制或配置。另一方面,egress 控制器控制或配置离开 Kubernetes 集群的服务。
接下来是元数据信息,例如环境类型、应用程序名称(nginxsvc)和标签(Kubernetes 使用这些信息来识别和隔离 pod)。Kubernetes 使用这些元数据信息来识别特定的 pod 或 pod 组,我们可以使用这些元数据来管理实例。这是与 docker-compose 的一大区别,因为 docker-compose 没有定义容器元数据的灵活性。
接下来是 spec,在这里我们定义镜像或应用程序的规范。我们还可以定义镜像的拉取策略,以及定义暴露的端口和环境变量。我们可以在机器(或 VM)上为特定服务定义资源限制。它们提供健康检查,即每个服务都会被监控其健康状态,当某些服务失败时,它们会立即被新的服务替换。它们还提供开箱即用的服务发现,通过为每个 pod 分配一个 IP 地址,这使得服务更容易识别和与之交互。它们还提供了一个更好的仪表板,用于可视化您的架构和应用程序的状态。您可以通过这个仪表板进行大部分管理,例如检查状态、日志、扩展或缩减服务,等等。
由于 Kubernetes 提供了完整的配置选项来编排我们的服务,这使得它最初设置起来非常困难,这意味着它不适合开发环境。我们还需要 kubectl CLI 工具进行管理。尽管我们在内部使用 Docker 镜像,但 Docker CLI 不能使用。
此外还有 Minikube(精简版 Kubernetes),它用于本地开发和测试应用程序。
Kubernetes 不仅负责将您的应用程序容器化,它还帮助扩展、管理和部署您的应用程序。它编排了您的整个应用程序部署。此外,它还提供服务发现和自动健康检查。
我们将在下一章中更多地关注 Kubernetes 子生成器。
介绍 OpenShift
OpenShift 是一个多云、开源的容器应用程序平台。它基于 Kubernetes,用于开发、部署和管理应用程序。它是开发者和运维人员的通用平台。它帮助他们一致地在混合云和多云基础设施上构建、部署和管理应用程序。
对于开发者,它提供了一个自助平台,他们可以在其中配置、构建和部署应用程序及其组件。通过自动工作流程将源代码转换为镜像,它帮助开发者从源代码到可运行的、docker 化的镜像。
对于运维人员,它提供了一个安全的、企业级的 Kubernetes,用于基于策略的控制和自动化应用程序管理,例如集群服务、调度和编排,具有负载均衡和自动扩展功能。
JHipster 还提供了一个独立的子生成器来提供 OpenShift 部署文件。我们可以通过运行 jhipster openshift 并根据需要回答问题来生成它们。这将生成与 OpenShift 相关的部署文件。
解释 Rancher
Rancher 是一个容器管理平台。它也是开源的。它帮助任何组织部署和维护容器。Rancher 只是一个部署服务器,它安装在任意的 Linux 机器或集群上。因此,要使用 Rancher,我们首先应该启动 Rancher 容器,这需要 Docker 可用。
一旦启动,我们就可以登录到 Rancher 并开始部署我们的应用程序。它还具有角色管理功能。Rancher 提供了在 Swarm、Kubernetes 或 Cattle(以及其他集群部署选项)之间进行选择的选项。它还提供了有关已部署的基础设施和应用程序的详细信息。它显示了有关容器、注册表、数据池和其他信息(与容器和基础设施相关的信息)的详细信息。
它还提供了根据需要调整 Kubernetes 或 Swarm 设置的选项,这使得扩展和缩减规模变得更加容易。它还提供了通过其 UI 或使用 docker-compose.yml 和 rancher-compose.yml 启动整个应用程序堆栈的选项。它还具有加载外部服务并使用它们的 capability(例如负载均衡器)。
JHipster 还提供了一个独立的子生成器来提供 Rancher 部署文件。我们可以通过运行 jhipster rancher 并根据需要回答问题来生成它们。这将生成 Rancher 配置文件。
生成的 Docker Compose 文件
默认情况下,JHipster 会生成 Docker Compose 文件,使我们能够在容器化环境中完全运行应用,无论选择了哪些选项。例如,在我们生成的网关应用中,以下文件默认在src/main/docker下生成:
-
sonar.yml:此文件创建并启动一个 SonarQube 服务器 -
mysql.yml:此文件创建并启动一个 MySQL 数据库服务器,并创建一个用户和模式 -
jhipster-registry.yml:此文件创建并启动一个 JHipster 注册表服务 -
app.yml:这是创建并启动应用以及如 JHipster 注册表和数据库等服务的主要文件
此外,JHipster 还创建了一个 Dockerfile,这有助于您将应用容器化。
然后我们可以看到一个名为central-server-config的文件夹。这将被用作 JHipster 注册表的中央配置服务器。
当注册表和应用在 Docker 中运行时,它使用docker-config文件夹中的application.yml作为中央配置服务器。
另一方面,当仅以 Docker 模式运行注册表时,非 Docker 的应用将使用localhost-config文件夹中的application.yml。关键区别在于定义 Eureka 客户端的 URL 不同。
让我们看看生成的 Docker 文件。
查看生成的文件
让我们从位于您的网关应用src/main/docker目录下的app.yml文件开始。
正如我们在本章开头所看到的,文件以它支持的 Docker 版本开始:
version: '2'
这之后是服务部分,我们在这里定义了各种服务、应用或组件,我们将使用此 Docker 文件启动它们。
在服务部分,我们将为服务定义一个名称,在我们的例子中我们使用了gateway-app,然后是我们要用作容器的图像。这个图像是通过我们那个文件夹中的 Docker 文件生成的。
这之后是我们应用将依赖的一系列环境变量,它们包括:
-
SPRING_PROFILES_ACTIVE:告诉应用以生产模式运行并暴露 Swagger 端点。 -
EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE:告诉应用在哪里检查 JHipster 注册表(这是我们使用的 Eureka 客户端。如果我们在这里选择了 Consul,那么应用将指向 Consul URL) -
SPRING_CLOUD_CONFIG_URI:告诉应用在哪里查找应用的config服务。 -
SPRING_DATASOURCE_URL:告诉应用在哪里查找数据源。 -
JHIPSTER_SLEEP:这是一个自定义属性,我们用它来确保 JHipster 注册表在应用启动之前启动。
最后,我们指定应用应在哪个端口上运行并暴露:
services:
gateway-app:
image: 'gateway'
environment:
- SPRING_PROFILES_ACTIVE=prod,swagger
- EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://admin:$${jhipster.registry.password}@jhipster-registry:8761/eureka
- SPRING_CLOUD_CONFIG_URI=http://admin:$${jhipster.registry.password}@jhipster-registry:8761/config
- SPRING_DATASOURCE_URL=jdbc:mysql://gateway-mysql:3306/gateway?.....
- JHIPSTER_SLEEP=30
ports:
8080:8080
我们刚刚使用 docker-compose 文件定义了服务;现在我们必须指定两个其他服务,这些服务对于我们的应用程序运行是必需的。它们是数据库和 JHipster 注册中心。
因此,我们注册了另一个名为 gateway-mysql 的服务,该服务创建并启动 MySQL 服务器。我们可以将 MySQL 定义为单独的 Docker Compose 文件,并将其链接在这里。因此,我们放置一个 extends 关键字,后面跟着 docker-compose 文件和从指定的 docker-compose 文件中启动的服务:
gateway-mysql:
extends:
file: mysql.yml
service: gateway-mysql.yml
然后我们输入以下代码到 mysql.yml 文件中,如下所示:
version: '2'
services:
gateway-mysql:
image: mysql:5.7.20
# volumes:
# - ~/volumes/jhipster/gateway/mysql/:/var/lib/mysql/
environment:
- MYSQL_USER=root
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
- MYSQL_DATABASE=gateway
ports:
- 3306:3306
command: mysqld --lower_case_table_names=1 --skip-ssl --character_set_server=utf8 --explicit_defaults_for_timestamp
我们再次从它支持的版本开始,接着是 services 关键字,然后指定 service 名称,gateway-mysql,这是在 app.yml 文件中使用的。如果您想指定用于持久数据存储的卷,可以取消注释已注释的卷段。这基本上是将本地文件位置映射到 Docker 的内部位置,以便即使 Docker 镜像本身被替换或更新,数据也能保持持久。
这之后是一系列环境变量,例如用户名和密码(我们在这里将其设置为空,但对于真正的生产应用程序,建议设置一个更复杂的密码),然后是数据库模式名称。
我们还指定了启动 MySQL 服务器所需的命令。
然后我们回到 app.yml 文件,并定义 JHipster 注册服务。这将再次扩展 jhipster-registry.yml 和 docker-compose 文件。在此需要注意的是,尽管我们从另一个 Docker 文件扩展了服务,但我们仍然可以覆盖在原始 docker-compose 文件中指定的环境变量。在某些情况下,当我们需要使用不同的或定制的值启动应用程序时,这非常有用。在我们的例子中,我们已经覆盖了 Spring Cloud Config 服务器文件位置,从原始位置更改过来:
jhipster-registry:
extends:
file: jhipster-registry.yml
service: jhipster-registry
environment:
- SPRING_CLOUD_CONFIG_SERVER_NATIVE_SEARCH_LOCATIONS=file:./central-config/docker-config/
Jhipster-registry.yml 文件:
version: '2'
services:
jhipster-registry:
image: jhipster/jhipster-registry:v3.2.3
volumes:
- ./central-server-config:/central-config
# When run with the "dev" Spring profile, the JHipster Registry will
# read the config from the local filesystem (central-server-config directory)
# When run with the "prod" Spring profile, it will read the configuration from a Git repository
# See http://www.jhipster.tech/microservices-architecture/#registry_app_configuration
environment:
- SPRING_PROFILES_ACTIVE=dev
- SECURITY_USER_PASSWORD=admin
- JHIPSTER_REGISTRY_PASSWORD=admin
- SPRING_CLOUD_CONFIG_SERVER_NATIVE_SEARCH_LOCATIONS=file:./central-config/localhost-config/
# - GIT_URI=https://github.com/jhipster/jhipster-registry/
# - GIT_SEARCH_PATHS=central-config
ports:
- 8761:8761
我们已经定义了 JHipster 注册中心的中央配置如下。我们已配置 JWT 的密钥和 Eureka 客户端的 URL。指定的 JWT 令牌用于服务进行授权以及在它们之间以及与注册中心之间的通信:
# Common configuration shared between all applications
configserver:
name: Docker JHipster Registry
status: Connected to the JHipster Registry running in Docker
jhipster:
security:
authentication:
jwt:
secret: my-secret-token-to-change-in-production
eureka:
client:
service-url:
defaultZone: http://admin:${jhipster.registry.password}@localhost:8761/eureka/
除了这些,我们还生成了一个 sonar.yml 文件(此文件对于部署您的应用程序不是很重要):
version: '2'
services:
gateway-sonar:
image: sonarqube:6.5-alpine
ports:
- 9000:9000
- 9092:9092
类似地,在微服务中,即在我们的发票和通知应用程序中,我们将生成类似的文件。它们除了服务名称的变化外,都是相同的。
与 MySQL 不同,MongoDB 也能够在具有不同节点和配置的集群中运行。我们需要在这里不同地指定它们。因此,我们将创建两个 docker-compose 文件。mongodb.yml 用于启动具有单个节点的 MongoDB,而 mongodb-cluster.yml 用于启动作为集群的 MongoDB。
请检查网关和微服务应用程序之间的数据库端口号。如果它们使用相同的数据库,由于 JHipster 为两者生成相同的端口号,因此可能会在端口号上发生冲突。将其更改为任何其他未使用的端口号,否则 Docker Compose 将显示错误。在我们的例子中,我已经将其更改为3307。
在本地构建和部署一切到 Docker
我们可以根据需要使用多种方式使用docker-compose文件。
通常,当我们开发应用程序时,我们可以使用通用的 Maven 或 Gradle 命令来运行应用程序,这样我们就可以调试应用程序并更快地重新加载更改,同时使用 Docker 启动数据库和 JHipster 注册表。
否则,您可以从app.yml文件启动整个应用程序,这将启动数据库、JHipster 注册表,然后是应用程序本身。为此,打开您的终端或命令提示符,转到应用程序文件夹,然后运行以下命令:
> cd gateway
然后,我们必须首先通过以下命令对我们的应用程序进行生产构建以 Docker 化应用程序:
> ./gradlew bootRepackage -Pprod buildDocker
完成后,我们可以通过docker-compose命令启动应用程序:
> docker-compose -f src/main/docker/app.yml up -d
-f指定docker-compose应该启动服务器的文件。-d标志告诉docker-compose以分离模式运行一切。这将启动 Docker 中的应用程序,并在端口8080上公开应用程序,在端口8761上公开注册服务器,在端口3306上公开数据库。
然后,我们可以进入各自的微服务文件夹并执行相同的操作,使用以下命令创建 Docker 镜像:
> ./gradlew bootRepackage -Pprod buildDocker
然后,我们可以通过以下命令使用docker-compose启动应用程序:
> docker-compose -f <filename> -d
我们可以使用以下命令检查正在运行的 Docker 容器:
> docker ps -a
它应该列出所有七个容器:

如您所见,有三个应用程序容器(网关/通知和发票),然后是一个 JHipster-Registry,接着是三个数据库容器(两个 MySQL 和一个 MongoDB。顺序可能有所不同)。
如果您使用 JHipster 版本 5 或更高版本,请使用bootWar而不是 Gradle 中的bootRepackage命令。
为微服务生成 docker-compose 文件
有许多docker-compose文件,维护它们很困难。幸运的是,JHipster 附带了一个docker-compose子生成器。Docker-compose子生成器帮助您将所有应用程序的 Dockerfile 组织在一起。它创建一个单独的 Dockerfile,该文件引用应用程序的 Dockerfile。
让我们转到基本文件夹并创建一个文件夹,并将其命名为docker-compose:
> mkdir docker-compose && cd docker-compose
一旦进入docker-compose文件夹,我们可以运行以下命令:
jhipster docker-compose
这将生成 Dockerfile。
如同往常,在生成文件之前,它将询问我们一系列问题:

首先,它会询问我们想部署哪种类型的应用程序。我们将选择微服务应用程序作为选项。
这之后,它将询问我们希望使用哪种类型的网关;有两个选项可供选择,一个是带有 Zuul 代理的 JHipster-gateway,另一个是更令人兴奋的、带有 Consul 的 Traefik 网关
让我们选择带有 Zuul 代理的 JHipster-gateway:

然后,我们必须选择微服务网关和应用程序的位置。这是我们为什么在单个父文件夹内生成应用程序的主要原因。这将帮助插件和子生成器轻松找到创建的 docker 配置文件。我们将选择默认选项(../)

选择位置后,JHipster 将在给定的文件夹内搜索任何由 JHipster 生成的应用程序,并在下一个问题中列出它们。
在我们的案例中,它列出了通知、发票和网关。我们可以选择所有这些并按Enter键:

它会自动检测我们已经使用了 MongoDB,并询问我们下一个问题;是否希望将 MongoDB 作为集群使用。我们在这里不会选择任何内容:

然后它会询问关于控制台的问题;是否需要为应用程序设置任何控制台。我们将选择带有 JHipster 控制台(基于 ELK 和 Zipkin)的日志和指标:

我们可以选择退出监控选项或选择 Prometheus。这将连接到 Prometheus 并仅显示指标。
然后,JHipster 会询问您是否需要 Curator 或 Zipkin:
-
索引管理员将帮助您管理和维护由 Elasticsearch 创建的索引。
-
Zipkin(如前一章所述)

由于选择了 JHipster 控制台,它将询问控制台支持的其他信息。它们包括 Zipkin 和 Curator。我们已经看到了 Zipkin。另一方面,Curator 将帮助我们管理和维护 Elasticsearch 中的索引。
我们在这里将只选择 Zipkin。

我们也可以在这里选择不选择任何内容,并使用默认选项。
最后,它要求输入 JHipster Registry 的密码;我们将在这里使用默认设置:

就这样;我们刚刚创建了一个更高层次的 Dockerfile,其中包含所有我们需要成功运行应用程序所需的信息。
现在,我们可以使用以下命令运行整个套件:
> docker-compose up -d
这将启动网关、通知、发票和注册表,以及控制台和所有其他所需服务。
部署应用程序的功能
因此,部署的应用程序已准备好启动。我们可以在http://localhost:8761启动 JHipster Registry;它将列出所有已注册的应用程序:

此外,注册表还告诉我们已注册的实例数量。导航到 Eureka | 实例以检查。目前,每种实例都注册了一个:

类似地,网关应用将列出连接到它的微服务。访问 http://localhost:8080。
导航到管理 | 网关,查看连接到此网关应用的微服务应用:

JHipster 控制台演示
JHipster 还提供了一个基于 ELK 堆栈的控制台应用程序,可用于应用程序的日志和指标监控。JHipster 控制台是另一个开源应用程序。它非常有用,并提供了一些很好的仪表板来可视化应用程序。与其他 JHipster 产品一样,使用 JHipster 控制台更容易上手。
让我们回到我们的书籍文件夹,然后从 GitHub 克隆 JHipster 控制台项目(github.com/jhipster/jhipster-console):
> git clone https://github.com/jhipster/jhipster-console
在我们开始控制台之前,我们需要让我们的应用程序记录指标并登录到控制台。为此,我们需要更改应用程序中的几个设置,然后重新启动它们。
让我们转到所有应用程序(网关和微服务应用)中的 application-prod.yml 文件,并启用 logstash 和日志:
metrics: # DropWizard Metrics configuration, used by MetricsConfiguration
...
logs: # Reports Dropwizard metrics in the logs
enabled: true
report-frequency: 60 # in seconds
logging:
logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration
enabled: true
host: localhost
port: 5000
queue-size: 512
在 metrics.logs.enabled 和 logging.logstash.enabled 中将启用设置为 true。这将把日志推送到控制台应用程序。JHipster 控制台将收集这些信息,并借助 Kibana 在漂亮的仪表板中显示它们。
一旦克隆完成,我们可以进入这个文件夹,然后使用docker-compose启动jhipster-console:
> cd jhipster-console
> docker-compose up -d
就这样。您的控制台正在 http://localhost:5601 上运行。
Kibana 提供以下(可自定义)仪表板:

Kibana 还提供应用程序级别的指标,例如 JVM 线程指标和其他详细信息:

此外,控制台还有一个界面,我们可以看到应用程序日志。它显示了所有部署的应用程序的日志。我们可以根据应用程序过滤和搜索日志:

Kibana 还提供了一个机器学习标签,我们可以创建一个作业来跟踪数据,然后选择任何可用的指标来跟踪它,如总和、计数、高计数等。
使用 Docker Swarm 进行扩展
Docker Swarm 是 Docker 的编排层,用于管理容器。它是一个集群管理工具,专注于创建服务的副本、网络以及它可用的存储资源,并管理它们。
Docker Swarm 不过是一组 Docker 节点。它们充当管理节点或工作节点。一个值得注意的有趣特性是,Swarm 内部的 Docker 容器可以是管理节点、工作节点或两者都是。这有助于 Swarm 在管理节点故障时分配新的管理节点。
在高层次上,管理节点负责集群管理任务并执行容器。工作节点只负责执行容器。
JHipster 应用程序为我们提供了使用单个命令扩展整个应用程序的灵活性,通过 JHipster 的docker-compose sub-generator:
> docker-compose scale <app-name>=<scale to>
现在我们可以使用以下命令来扩展实例:
> docker-compose scale invoice-app=2
前面的命令将启动另一个发票实例,我们可以在以下仪表板上看到它:

摘要
到目前为止,我们已经看到了如何生成、设置和启动 JHipster Registry 和 console,以及我们查看它们的特性。这之后是关于如何使用docker-swarm扩展应用程序。
在下一章中,我们将看到如何使用 Kubernetes 将应用程序部署到 Google Cloud。
第十二章:使用 Kubernetes 部署到云端
Kubernetes,也称为容器编排工具。正如我们在上一章所看到的,它附带了许多附加功能,并且配置和管理起来更加容易。这使得 Kubernetes 成为任何容器编排的首选。能够隐藏底层细节并提供开箱即用的服务发现、自我修复和健康检查吸引了众多公司和组织转向 Kubernetes。除此之外,Kubernetes 还是 Google 内部编排工具的演变。
在本章中,我们将涵盖以下主题:
-
使用 JHipster 生成 Kubernetes 配置文件
-
查看生成的文件
-
使用 Kubernetes 将应用程序部署到 Google Cloud
使用 JHipster 生成 Kubernetes 配置文件
了解 Kubernetes 的组件及其工作原理超出了本书的范围。然而,我们将探讨 JHipster 如何简化使用 Kubernetes 的微服务部署。让我们继续生成 Kubernetes 配置文件。
与 docker-compose 子生成器类似,JHipster 也附带了一个 Kubernetes 子生成器。为了使用它,就像使用 docker-compose 一样,我们将创建一个新的文件夹并将其命名为 Kubernetes。然后,我们将进入该文件夹以创建配置文件。
我们可以使用以下命令创建 Kubernetes 配置文件,然后回答子生成器提出的问题:
> mkdir kubernetes && cd kubernetes
要安装 kubectl,请遵循 Kubernetes 网站的说明(kubernetes.io/docs/tasks/tools/install-kubectl/)。
Kubernetes 子生成器需要在您的计算机上安装 kubectl(v1.2 或更高版本)。kubectl 是 Kubernetes 的命令行界面。
我们还可以从 Google Cloud 安装 Cloud SDK,这将也会安装 kubectl。为了设置 gcloud:
-
从
cloud.google.com/sdk/下载基于您操作系统的二进制文件。 -
然后,按照网站上的步骤安装应用程序(确保您已安装 python)。
-
安装完成后,设置 Google Cloud。为了设置 Google Cloud,请运行
gcloud init。 -
这将要求您登录到您的 Google 账户:
> jhipster kubernetes
正如我们已经看到的,Kubernetes 需要单独的工具来在本地运行(即用于开发目的)。因此,如果您需要在本地进行操作,请安装 Kubernetes 的 minikube。
子生成器首先会问我们希望部署哪种类型的应用程序。它提供了两种选项,即单体和微服务。我们将选择微服务选项:

然后,它会要求我们进入根目录。由于我们的目录作为 Kubernetes 文件夹的兄弟存在,我们将选择默认选项:

然后,子生成器将列出所有由 JHipster 生成的应用程序的文件夹。在这里,它将列出我们需要的所有三个应用程序——网关、发票和通知。选择所有三个应用程序并按回车键:

然后,它将要求我们提供注册服务的密码。在我们的情况下,它是 JHipster 注册表。我们现在将选择默认设置,但通常建议在这里使用一个强密码:

之后,它将询问我们需要在 Kubernetes 中使用的命名空间。那么,什么是命名空间?
我们可以将命名空间视为一个组,其中资源应该具有唯一的名称。当集群在不同用户或团队之间共享时,命名空间可以为它们提供资源配额。理想情况下,命名空间应仅用于大型团队。对于小型团队,最好选择默认选项。Kubernetes 默认提供三个命名空间,如下所示:
-
Default:当你启动容器或 pod 而不提供任何命名空间时,它们将最终进入默认命名空间 -
Kube-system:这个命名空间包含基于 Kubernetes 系统的对象 -
Kube-admin:这是一个公开的命名空间,将向所有用户公开显示,无需任何身份验证
我们将在这里选择默认命名空间:

然后,子生成器将要求我们提供 Docker 仓库名称,以便 Kubernetes 可以使用此 Docker 仓库来拉取镜像(Docker 仓库的登录用户名):

然后,子生成器将要求我们提供命令,以便我们可以将镜像推送到 Docker 仓库。我们将在这里选择默认命令:

然后,它将询问我们是否需要 JHipster-console 进行日志聚合,我们将选择是:

JHipster 还提供了 Prometheus 集成,所以下一个问题将是是否希望将我们的服务导出到 Prometheus。通常需要 Prometheus 操作员才能工作。我们将选择否:

然后,生成器将要求我们选择 Kubernetes 服务类型。那么,服务类型是什么?
在 Kubernetes 中,我们部署的每一件事都是一个 pod。这些 pod 由复制控制器管理,可以创建和销毁任何 pod。每个 pod 都需要一个标识符,因此它们被标记为 IP 地址。pod 的这种动态特性将导致许多依赖它们的 pod 出现问题。为了解决这个问题,Kubernetes 引入了服务。服务不过是具有附加策略的唯一 pod 的逻辑分组。这些策略适用于服务内的所有 pod,但我们需要将这些服务发布到外部世界以供访问。
Kubernetes 最强大的功能之一是它们帮助保持 pods 副本数的一致性。副本控制器通过自动关闭和启动 pods 来维护 pods 的数量。
Kubernetes 提供了四种不同的服务类型,如下所示:
-
Cluster IP:这是默认类型。这将分配集群的内部 IP,并在集群内部可见。 -
NodePort:这将使服务在节点的 IP 上的静态端口暴露。端口号将是随机的,将在30000-32767之间选择。 -
LoadBalancer:这将使服务外部暴露。Kubernetes 将自动分配一个 IP。这将创建一个指向 NodePort 和 Cluster IP 的内部路由。 -
Ingress:Ingress 是 Kubernetes 提供的一个特殊选项。这将提供负载均衡、SSL 终止和基于名称的虚拟主机服务。
我们将选择LoadBalancer选项:

就这些。这将为我们生成必要的配置文件,以便我们使用 Kubernetes 部署应用程序。接下来,我们将检查生成的文件。
检查生成的文件
JHipster 生成的文件结构如下。也就是说,每个应用程序都将有自己的文件夹,并且与该服务相关的文件将位于其中。
我们将从网关应用程序开始。将生成三个文件,它们将是gateway-service、gateway-mysql和gateway-deployment.yml文件。
下面的文件是gateway-service.yml:
apiVersion: v1
kind: Service
metadata:
name: gateway
namespace: default
labels:
app: gateway
spec:
selector:
app: gateway
type: LoadBalancer
ports:
- name: web
port: 8080
第一行定义了 Kubernetes 的 API 版本,然后是此模板或对象的类型。此模板将包含一个服务。
然后,我们将有元数据信息。Kubernetes 使用这些元数据信息来将某些服务分组在一起。在元数据中,我们可以定义:
-
服务名称
-
它所属的命名空间
-
标签,它们是键值对
然后,我们将定义 spec。在 Kubernetes 对象中的 spec 将提供服务的状态。在 spec 中,我们可以定义所需的副本数量。我们还可以定义选择器,在其中我们可以指定具有标识符的服务。我们还可以指定服务的类型。Kubernetes 将从此 spec 中获取信息,然后维护应用程序在提供的状态(我们稍后将查看 spec 网关),然后是应用程序应该运行的端口。这与 Dockerfile 类似,因此我们正在暴露网关服务的8080端口。
然后,我们有gateway-mysql.yml文件,其中我们为网关应用程序定义了 MySQL 服务器。这里的区别在于 spec 指向gateway-mysql,它在同一文件中定义,并且暴露在端口3306上:
apiVersion: v1
kind: Service
metadata:
name: gateway-mysql
namespace: default
spec:
selector:
app: gateway-mysql
ports:
- port: 3306
在 gateway-mysql 应用声明中,我们指定了应用程序运行所需的数据库和环境属性。在这里,kind 被提到为 deployment。部署对象的工作是改变服务的状态,使其符合部署对象中定义的状态。
在这里,我们定义了 MySQL 服务器的单个副本,然后是规范,其中我们提到了我们需要的 MySQL 版本(容器)。然后是用户名、密码,然后是数据库模式。我们还可以指定卷信息,使用卷挂载进行持久化存储:
注意:我们也可以在规范对象内部定义一个规范(如下面的代码所示):
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
... // metadata
spec:
replicas: 1
... // metadata related information
spec:
... //volumes and other information
containers:
- name: mysql
image: mysql:5.7.20
env:
... // environment details
ports:
... // port
volumeMounts:
... //Mapping the volume to the external persistent location
类似地,我们有一个 gateway-deployment.yml 文件,其中我们定义了网关应用程序及其环境属性,以及其他细节,如端口、探针等。
对于发票和通知服务,我们采用了类似的方法。您可以在各自的文件夹中找到它们。
在 JHipster-registry 中,除了 Service 和 Deployment 之外,我们还定义了 Secret 和 StatefulSet。
密钥用于处理密码。它将是一个不透明的类型,密码将以 base64 编码。
然后,我们有 StatefulSet,它们类似于 pod,但具有粘性标识符。Pods 是动态的。这些 pod 有一个在整个过程中保持不变的持久标识符。将注册表服务器定义为 StatefulSet 是有意义的,因为注册表服务器应该通过一个持久的标识符来识别。这使得所有服务都可以连接到该单个端点并获取必要的信息。如果注册表服务器宕机,那么服务之间的通信也会出现问题,因为服务通过注册表服务器连接到其他服务。
有各种选项可以设置控制器,如下所示:
-
副本集:在任何时候通过选择器提供 pods 的副本
-
副本控制器:提供没有选择器的 pods 的副本
-
有状态集:通过提供一个持久的标识符使 pod 唯一
-
守护进程集:提供将要运行的 pod 的副本
JHipster 注册表配置在集群中,并具有高可用性。对 JHipster 注册表的 UI 访问也仅限于集群,以增强安全性。
类似地,为 JHipster 控制台生成了配置文件,并将它们放置在控制台文件夹中,该文件夹是 jhipster-console.yml,其中也定义了 JHipster 控制台。
JHipster 控制台运行在 ELK 堆栈上,因此我们需要 Elasticsearch,它在 jhipster-elasticsearch.yml 文件中定义,然后是 Logstash 在 jhipster-logstash.yml 文件中。
使用 Kubernetes 将应用程序部署到 Google Cloud
我们使用 jhipster kubernetes 命令创建了 Kubernetes 配置文件。下一步是构建工件并将它们部署到 Google Cloud。
Kubernetes 将使用 Docker 仓库中的镜像。我们在生成应用程序时配置了 Docker 用户名,因此第一步将是标记这些镜像,然后将它们推送到我们的 Docker 仓库。
要做到这一点,我们将执行以下操作:
我们将打开终端并转到我们生成的 Kubernetes 文件夹:
> docker image tag gateway sendilkumarn/gateway
然后,我们将此镜像推送到 Docker 仓库:
> docker push sendilkumarn/gateway
注意:在推送镜像之前,您必须登录到 Docker Hub。您可以使用 docker login 命令,后跟您的用户名和密码登录 Docker。如果您没有账户,您可以在以下链接创建一个:cloud.docker.com/。
类似地,将发票应用程序推送到 Docker 仓库:
> docker image tag invoice sendilkumarn/invoice
> docker push sendilkumarn/invoice
对通知也进行相同的操作:
> docker image tag notification sendilkumarn/notification
> docker push sendilkumarn/notification
这将推送网关、发票和通知到 Docker 仓库。我们可以在 Docker 控制台中检查这一点:

现在,我们可以连接到 gcloud 并使用 Kubernetes 部署我们的容器。
这假设您已经在您的机器上设置了 gcloud SDK 和 kubectl。
首先,我们将通过终端登录到 gcloud。为了做到这一点,打开您的终端:
> gcloud init // if this is the first time you are using gcloud (Ignore this step if you logged in already)
然后,gcloud 将要求您登录到您的 Google 账户。一旦验证,这将列出您可能已经拥有的项目。
在这里,我们可以通过在创建新项目前输入数字来选择 [31] 创建新项目。然后按回车键。系统将要求您输入项目信息,然后为该项目配置一些 Google 服务。然后,gcloud 将列出所有可用的区域,您可以选择一个适合您的区域。
如果您已经登录到控制台并用于其他项目,则可以使用以下命令切换项目:
> gcloud config set project <project-name>
这将设置项目、区域和选择的设置作为默认值。
然后,您必须在我们的应用程序中启用 Kubernetes。我们可以通过浏览器登录到我们的 Google Cloud 控制台来完成此操作。然后,选择我们刚刚创建的项目,并转到 console.cloud.google.com/kubernetes/list。
这将为您的项目创建一个集群。相反,您可以使用 gcloud 命令创建一个集群:
> gcloud container clusters create online-store
以下是在先前的命令输出:

因此,集群已使用 3 个节点创建。
然后,我们可以进入我们的 Kubernetes 文件夹,并使用 kubectl 开始部署服务:
> kubectl apply -f <project-name>
输出将如下所示:

这将在 Google Cloud 环境中创建所有应用程序,位于您的项目下。
您可以使用以下命令检查 pods 部署过程:
> kubectl get pods --watch
这将列出正在启动和关闭的 pods:

您还可以使用以下命令获取应用程序的日志:
> kubectl logs <name as shown above>
以下是其输出:

你可以使用以下代码片段获取应用程序的外部 IP:
> kubectl get svc gateway
这将列出应用程序的名称、类型、IP 地址、外部地址、端口和运行时间:

我们可以在 Google Cloud 控制台中找到相同的信息:

应用程序可以通过前面的外部 IP 访问:

你也可以使用以下命令来扩展应用程序:
> kubectl scale deployment <app-name> --replicas <number-of-replicas>
JHipster-registry 以无头模式部署。为了检查 JHipster 注册表,我们可以使用以下命令显式暴露服务——**kubectl expose service jhipster-registry --type=NodePort --name=exposed-registry**——然后我们可以通过exposed-registry访问应用程序。
摘要
在微服务环境中,编排你的容器是最难执行的任务。Kubernetes 作为一个容器编排器,在解决这一问题上脱颖而出。我们已经看到了如何使用 JHipster 生成 Kubernetes 的配置文件,随后将应用程序部署到 Google Cloud。
到目前为止,我们已经看到了如何使用 JHipster 开发和部署电子商务应用程序。我们从单体应用开始,并成功将其扩展到微服务架构,这一切都得益于 JHipster 及其支持的各项工具和技术。随着本章的结束,我们希望你在跟随它的过程中有一个愉快的体验。
在下一章中,我们将看到如何进一步使用 JHipster 创建一个具有 React 客户端应用程序的应用程序,所以请保持关注。
第十三章:使用 React 进行客户端开发
到目前为止,我们已经看到了如何使用 Angular 作为客户端框架构建 Web 应用程序和微服务。AngularJS 是最流行的客户端框架,直到新版本的 Angular 2 发布。由于 Angular 2 的向后不兼容架构,它导致了重大的颠覆,并让更多的人转向 React。因此,潮流已经转变,现在 React 是最受欢迎和最被寻求的客户端框架,其次是 Angular。JHipster 从 4.11 版本开始添加了对 React 的实验性支持,并且随着 JHipster 版本 5.0 的发布;React 支持将成为 BETA 并准备好用于主流使用。
在本章中,我们将涵盖以下主题:
-
使用 React 客户端生成应用程序
-
技术栈和源代码
-
使用 React 客户端生成实体
使用 React 客户端生成应用程序
让我们直接深入创建一个 JHipster React 应用程序。您需要打开一个终端来运行命令:
-
创建一个新的文件夹,并通过运行
mkdir jhipster-react && cd jhipster-react来导航到它。 -
现在在终端中运行
jhipster命令。如果您正在运行 JHipster 4.x 版本而不是 5.x,那么您需要通过运行jhipster --experimental来传递实验性标志。 -
JHipster 将从提示开始;让我们为所有问题选择默认选项,除了询问您想为客户端使用哪个 框架 的问题。对于这个问题,请选择 [BETA] React 并继续。
-
一旦完成所有提示,JHipster 将生成应用程序并开始安装依赖项,在格式化代码之前使用 Prettier (
prettier.io/) 并启动 webpack 构建。
您可以随时运行 yarn prettier:format 来格式化客户端代码。每当您使用 git 预提交钩子提交某些内容时,它也会自动运行。
我们选择的选择将如下所示:
? Which *type* of application would you like to create? Monolithic application (recommended for simple projects)
? What is the base name of your application? jhreact
? What is your default Java package name? com.jhipsterbook.demo
? Do you want to use the JHipster Registry to configure, monitor and scale your application? No
? Which *type* of authentication would you like to use? JWT authentication (stateless, with a token)
? Which *type* of database would you like to use? SQL (H2, MySQL, MariaDB, PostgreSQL, Oracle, MSSQL)
? Which *production* database would you like to use? MySQL
? Which *development* database would you like to use? H2 with disk-based persistence
? Do you want to use the Spring cache abstraction? Yes, with the Ehcache implementation (local cache, for a single node)
? Do you want to use Hibernate 2nd level cache? Yes
? Would you like to use Maven or Gradle for building the backend? Maven
? Which other technologies would you like to use?
? Which *Framework* would you like to use for the client? [BETA] React
? Would you like to enable *SASS* support using the LibSass stylesheet preprocessor? No
? Would you like to enable internationalization support? Yes
? Please choose the native language of the application English
? Please choose additional languages to install Chinese (Simplified)
? Besides JUnit and Karma, which testing frameworks would you like to use?
? Would you like to install other generators from the JHipster Marketplace? No
就这样;我们完成了。我们的第一个 JHipster React 应用程序已成功创建。现在让我们启动应用程序来探索一下。
我们将选择默认的 Maven 构建选项,JHipster 已经为它创建了一个包装器,所以让我们通过在终端中运行 ./mvnw 来启动我们的服务器。
Maven 将下载必要的依赖项,并使用嵌入的 Undertow 容器启动 Spring Boot 应用程序。如果您更喜欢 Gradle,可以选择 Maven。一旦应用程序成功启动,我们将在控制台中看到以下内容:
2018-03-04 16:37:48.096 INFO 4730 --- [ restartedMain] com.jhipsterbook.demo.JhreactApp :
----------------------------------------------------------
Application 'jhreact' is running! Access URLs:
Local: http://localhost:8080
External: http://192.168.2.7:8080
Profile(s): [swagger, dev]
----------------------------------------------------------
在您最喜欢的浏览器中访问 URL (http://localhost:8080) 以查看应用程序的实际运行情况:

您将看到主屏幕,上面有一个看起来回头的嬉皮士。注意他脖子上的 React 纹身。
使用默认的管理员用户登录并探索一下。
应用程序看起来与之前构建的 Angular 应用程序完全相同,当然,除了图像之外,并且拥有所有相同的账户和管理模块。这将使我们对查看技术栈和源代码更有兴趣。
技术栈和源代码
在我们深入研究生成的代码之前,让我们谈谈技术栈。我们已经在 第二章 中了解了 React,即 使用 JHipster 入门,但让我们回顾一下。
React 是由 Jordan Walke 在 2011 年创建的一个视图渲染库,并于 2013 年 5 月开源。它由 Facebook 维护并拥有庞大的社区支持。React 遵循 JS 对 HTML 的方法,其中标记代码使用 JavaScript 编写。为了减少冗余,React 使用一种名为 JSX 的 JavaScript 语法糖来描述视图元素。它看起来类似于 HTML,但它并不完全等同于 HTML,例如,一些标准 HTML 属性,如 class,被重命名为 className,并且属性名称使用驼峰式而不是破折号式。
例如,以下是一个 JSX 片段。您始终需要在 JSX 中使用 React 上下文才能使其工作:
const element = <div><strong>Hello there</strong></div>
当涉及到 TypeScript 时,JSX 扩展变为 TSX。
React 使用一个名为虚拟 DOM 的概念来提高渲染效率。虚拟 DOM 是实际 DOM 的轻量级副本,通过比较更新后的虚拟 DOM 与更新前的虚拟 DOM 快照,React 可以决定确切发生了什么变化,并仅在实际 DOM 上渲染这些变化,从而使得渲染周期高效且快速。
React 组件可以有自己的状态,并且您可以向组件传递属性,这些属性作为 props 可用给组件。
与 Angular 不同,React 不是一个完整的 MVVM 框架。它只是一个视图渲染库,因此当构建 React 应用程序时,我们总是需要添加一些额外的库来处理状态管理、路由等。
技术栈
以下是在选择 React 作为客户端框架时,JHipster 使用的技术栈:
-
渲染: 使用 TypeScript 编写的 React
-
状态管理: Redux + React Redux + Redux Promise Middleware + Redux Thunk
-
路由: React router
-
HTTP: Axios
-
响应式设计: Bootstrap 4 + Reactstrap
-
代码检查: Tslint
-
工具库: Lodash
-
单元测试: Karma + Mocha + Chai + Enzyme
-
构建: Webpack
让我们来看看这个技术栈中的一些最重要的组件。
使用 TypeScript
客户端使用 React 构建,但不是使用传统的 JavaScript ES6,而是选择 TypeScript 作为编程语言。这为您提供了使用一些可能已经熟悉的从服务器端背景出发的概念的灵活性。它还提供了静态类型检查,这使得开发更加高效且错误率更低。
访问 github.com/piotrwitek/react-redux-typescript-guide 了解如何充分利用 TypeScript + React。
使用 Redux 及其相关工具进行状态管理
React 在 React 组件内提供了基本的州管理,但有时这并不足够,尤其是当您的应用程序需要在多个组件之间共享状态时。在 React 世界中,像 Flux、Redux 和 MobX 这样的状态管理解决方案非常流行,JHipster 使用 Redux 作为状态管理层。
你应该在何时使用 React 组件状态?
-
如果变量总是可以通过属性计算得出:不要使用组件状态,在渲染期间计算变量
-
如果变量不在渲染中使用但用于存储数据:不要使用组件状态,使用私有类字段
-
如果变量是从 API 获取且需要被多个组件使用:不要使用组件状态,使用 Redux 全局状态并将变量作为属性传递
Redux (redux.js.org/) 是 JavaScript 的一个可预测的状态管理解决方案,它源自 Flux 概念 (facebook.github.io/flux/)。Redux 提供了一个全局不可变存储,只能通过发出或分发动作来更新。一个动作是一个对象,它描述了发生了什么变化,并使用一个纯函数来转换状态。一个 reducer 是一个纯函数,它接受当前状态和动作,并返回一个新的状态。
React Redux 是 Redux 的一个绑定,它为 React 提供了一个名为 connect 的高阶组件,用于将 React 组件连接到 Redux 存储。以 src/main/webapp/app/modules/home/home.tsx 为例:
export class Home extends React.Component<IHomeProp, IHomeState> {
...
}
const mapStateToProps = storeState => ({
account: storeState.authentication.account,
isAuthenticated: storeState.authentication.isAuthenticated
});
const mapDispatchToProps = { getSession };
export default connect(mapStateToProps, mapDispatchToProps)(Home);
mapStateToProps 函数用于将全局 Redux 存储中的属性映射到组件属性。mapDispatchToProps 函数用于将给定的函数包装在 Redux 的 dispatch 调用中。
Redux Promise Middleware (github.com/pburtchaell/redux-promise-middleware) 用于处理异步动作负载。它接受一个 Promise,并根据 Promise 的状态分发挂起、已解决和拒绝的动作。当 Redux 动作进行 HTTP 请求或执行异步操作时,它非常有用。
Redux Thunk (github.com/gaearon/redux-thunk) 是另一种用于链式操作的中间件。当动作需要根据某些条件调用另一个动作或通常处理副作用时,它非常有用。
使用 React Router 进行路由
React Router (reacttraining.com/react-router/web/guides/philosophy) 用于客户端路由。JHipster 的默认设置是使用基于 Hash History 的路由。它提供了一个简单的基于组件的路由,以及一个灵活的 API,用于高级路由设置。路由可以在应用程序的任何位置定义,与正常的 React 渲染代码并列。JHipster 提供了一些自定义包装器,例如 PrivateRoute,以启用基于授权的路由。
例如,让我们看看 src/main/webapp/app/routes.tsx:
const Routes = () => (
<div className="view-routes">
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
<Route path="/logout" component={Logout} />
<Route path="/register" component={Register} />
<Route path="/activate/:key?" component={Activate} />
<Route path="/reset/request" component={PasswordResetInit} />
<Route path="/reset/finish/:key?" component={PasswordResetFinish} />
<PrivateRoute path="/admin" component={Admin} />
<PrivateRoute path="/account" component={Account} />
<Route path="**" component={Entities} />
</div>
);
export default Routes;
使用 Axios 进行 HTTP 请求
Axios (github.com/axios/axios) 是一个基于 Promise 的 HTTP 客户端。它是一个功能强大且灵活的库,具有非常直观的 API。它用于从 Redux 动作中获取 JHipster 应用程序的服务器端 REST 端点数据。结果 Promise 由 Redux Promise 中间件解析,以提供数据给 reducer。
以下展示了一个带有异步负载的 Redux 动作:
export const getRoles = () => ({
type: ACTION_TYPES.FETCH_ROLES,
payload: axios.get(`${apiUrl}/authorities`)
});
使用 Reactstrap 的 Bootstrap 组件
JHipster 使用 Bootstrap 4 作为其 UI 框架,由于我们正在构建一个 React 应用程序,因此使用原生 React 绑定而不是 Bootstrap 的基于 jQuery 的组件是有意义的。Reactstrap (reactstrap.github.io/) 为 Bootstrap 4 提供了纯 React 组件。我们还使用了 Availity reactstrap Validation (availity.github.io/availity-reactstrap-validation/) 库,该库为 Reactstrap 表单元素提供表单验证支持。
例如,让我们看看 src/main/webapp/app/modules/login/login-modal.tsx:
<Modal isOpen={this.props.showModal} toggle={handleClose} backdrop="static" id="login-page">
<AvForm onSubmit={this.handleSubmit}>
<ModalHeader toggle={handleClose} id="login-title">
...
</ModalHeader>
<ModalBody>
<div className="row">
...
<div className="col-md-12">
<AvField
name="username"
label={...}
placeholder={...}
required
errorMessage="Username cannot be empty!"
/>
...
</div>
</div>
<Alert color="warning">
...
</Alert>
...
</ModalBody>
<ModalFooter>
...
</ModalFooter>
</AvForm>
</Modal>
单元测试设置
JHipster 使用 Karma、Mocha、Chai 和 Enzyme 的组合来对客户端组件进行单元测试。
Karma (karma-runner.github.io/2.0/index.html) 被用作测试运行器,Mocha (mochajs.org/) 被用作测试框架。Chai (chaijs.com/) 是一个具有出色插件支持的断言库。我们使用其 BDD(行为驱动开发)风格的断言。Enzyme (airbnb.io/enzyme/) 是一个用于 React 的测试实用工具,它使得对 React 组件进行单元测试变得容易。结合使用,这些库为 React 提供了一个丰富且直观的测试环境。
让我们运行生成的单元测试。在终端中运行 yarn test。
生成源代码
让我们看看生成的代码。由于我们已经在之前的章节中看到了服务器端代码,所以我们在这里只看客户端代码:

结构与我们之前看到的 Angular 非常相似,但 React 代码的组织方式略有不同。我们只关注 src/main/webapp/app 内部的代码,因为其他所有内容都与我们在 Angular 应用程序中看到的一模一样。
让我们看看代码的一些重要部分:
index.tsx:这是我们的应用程序入口点。这是我们将 React 引导到root div并初始化 Redux 存储的地方:
...
const devTools = process.env.NODE_ENV === 'development' ? <DevTools /> : null;
const store = initStore();
registerLocales(store);
const actions = bindActionCreators({ clearAuthentication }, store.dispatch);
setupAxiosInterceptors(
() => actions.clearAuthentication('login.error.unauthorized')
);
const rootEl = document.getElementById('root');
const render = Component =>
ReactDOM.render(
<AppContainer>
<Provider store={store}>
<div>
...
{devTools}
<Component />
</div>
</Provider>
</AppContainer>,
rootEl
);
render(AppComponent);
...
app.tsx:这是我们的主要应用程序组件。我们在这里声明 React 路由和主要应用程序 UI 结构:
...
export class App extends React.Component<IAppProps> {
componentDidMount() {
this.props.getSession();
}
handleLogout = () => {
this.props.logout();
};
render() {
const paddingTop = '60px';
return (
<Router>
<div className="app-container" style={{ paddingTop }}>
<Header
...
/>
<div className="container-fluid view-container" id="app-view-container">
<Card className="jh-card">
<AppRoutes />
</Card>
<Footer />
</div>
<ModalContainer />
</div>
</Router>
);
}
}
...
-
routes.tsx:应用程序的主要父级路由在这里定义,并且它们从这里导入app.tsx。 -
config:这是进行框架级配置的地方:-
axios-interceptor.ts:在这里配置 HTTP 拦截器。这是将 JWT 令牌设置到请求中并处理错误的地方。 -
constants.ts:应用程序常量。 -
*-middleware.ts:在这里配置 Redux 的错误、通知和日志中间件。 -
store.ts:在这里完成 Redux 存储配置。中间件在此阶段注册。
-
中间件在数组中的顺序很重要,因为它们就像一个管道,将动作从一个中间件传递到另一个中间件,如下所示:
const defaultMiddlewares = [
thunkMiddleware,
errorMiddleware,
notificationMiddleware,
promiseMiddleware(),
loadingBarMiddleware(),
loggerMiddleware
];
-
translation.ts:在这里完成 i18n 相关的配置。
-
entities:实体模块在这里。 -
modules:应用程序 UI 模块在这里:-
account:账户页面,如设置、密码重置等在这里 -
administration:如度量、健康、用户管理等管理屏幕在这里 -
home:应用程序的主屏幕 -
login:登录和注销组件
-
-
shared:共享组件和减法器:-
layout:与头部、尾部等相关布局组件。 -
model:实体的 TypeScript 模型。 -
reducers:应用程序使用的共享减法器:authentication.ts:这是与身份验证相关的动作和减法器。让我们使用LOGIN动作。该动作接受用户名、密码和 rememberMe,并通过对 HTTP 调用的异步有效载荷执行ACTION_TYPES.LOGIN来验证我们的凭据。我们使用 ES7 的 async/await 功能来避免这里的复杂回调。从我们提取 JWTbearerToken并根据传递的 remember me 设置将其存储在浏览器的本地或会话存储中的结果,是从我们触发基于承诺状态的ACTION_TYPES.LOGIN的减法器中获得的。ACTION_TYPES.LOGIN的分发将根据承诺的状态触发减法器中的适当情况:
-
...
export const ACTION_TYPES = {
LOGIN: 'authentication/LOGIN',
...
};
const initialState = {
...
};
// Reducer
export default (state = initialState, action) => {
switch (action.type) {
case REQUEST(ACTION_TYPES.LOGIN):
case REQUEST(ACTION_TYPES.GET_SESSION):
return {
...state,
loading: true
};
case FAILURE(ACTION_TYPES.LOGIN):
return {
...initialState,
errorMessage: action.payload,
showModalLogin: true,
loginError: true
};
...
case SUCCESS(ACTION_TYPES.LOGIN):
return {
...state,
loading: false,
loginError: false,
showModalLogin: false,
loginSuccess: true
};
...
default:
return state;
}
};
...
export const login =
(username, password, rememberMe = false) => async (dispatch, getState) => {
const result = await dispatch({
type: ACTION_TYPES.LOGIN,
payload: axios.post('/api/authenticate', { username, password, rememberMe })
});
const bearerToken = result.value.headers.authorization;
if (bearerToken && bearerToken.slice(0, 7) === 'Bearer ') {
const jwt = bearerToken.slice(7, bearerToken.length);
if (rememberMe) {
Storage.local.set('jhi-authenticationToken', jwt);
} else {
Storage.session.set('jhi-authenticationToken', jwt);
}
}
dispatch(getSession());
};
...
-
util:实用函数。
单元测试代码的文件夹结构也非常相似:

使用 React 客户端生成实体
让我们看看如何使用 JHipster 实体生成器以及 React 客户端创建一个实体。我们将创建一个简单的 Employee 实体,包含姓名、年龄和出生日期字段:
-
打开终端并导航到 React 应用程序的文件夹,然后运行
jhipster entity employee。 -
逐个创建字段,对于问题“是否要为实体添加字段?”选择“是”,并开始填写下一个问题的字段名称,“你的字段名称是什么?”
-
对于下一个问题,“你的字段类型是什么?”选择 String。
-
对于问题“你想要添加哪些验证规则?”选择名称字段的“必需”,然后继续。
-
继续处理以下字段。
age和dob的类型为 Integer,dob的类型为 Instant。 -
当再次被问及时,选择“否”,即是否要为实体添加字段。
-
对于下一个问题,“是否要为另一个实体添加关系?”,选择“是”。
-
将
user作为其他实体的名称以及关系的名称,用于以下问题。 -
对于下一个问题,“关系的类型是什么?”让我们创建一个与用户的一对一关系。
-
对于下一个问题选择“否”,当被要求添加另一个关系时再次选择“否”。
-
对于以下问题,选择默认选项并继续。
命令将产生以下控制台输出:
Using JHipster version installed globally
Executing jhipster:entity employee
Options:
The entity employee is being created.
...
================= Employee =================
Fields
name (String) required
age (Integer)
dob (Instant)
Relationships
user (User) one-to-one
? Do you want to use separate service class for your business logic? No, the REST controller should use the repository directly
? Do you want pagination on your entity? No
JHipster 将生成实体并运行 Prettier 和 webpack 构建。
-
如果你的服务器没有运行,请在终端中通过运行
./mvnw启动它。如果它已经运行,那么只需通过运行./mvnw compile编译新代码,Spring DevTools 将自动重启应用程序。 -
在另一个终端中通过运行
yarn start启动 BrowserSync 并检查我们刚刚创建的员工实体:

- 创建一个实体以检查一切是否正常工作:

对于我们创建的实体,JHipster 生成了/更新了以下文件:

在 React 客户端,我们有以下文件:
src/main/webapp/app/entities/employee/employee-delete-dialog.tsx
src/main/webapp/app/entities/employee/employee-detail.tsx
src/main/webapp/app/entities/employee/employee-dialog.tsx
src/main/webapp/app/entities/employee/employee.tsx
src/main/webapp/app/entities/employee/employee.reducer.ts
src/main/webapp/app/shared/model/employee.model.ts
src/main/webapp/app/entities/employee/index.tsx
index.ts 文件声明了实体的路由:
<Switch>
<Route exact path={match.url} component={Employee} />
<ModalRoute exact parentPath={match.url} path={`${match.url}/new`}
component={EmployeeDialog} />
<ModalRoute exact parentPath={match.url} path={`${match.url}/:id/delete`}
component={EmployeeDeleteDialog} />
<ModalRoute exact parentPath={match.url} path={`${match.url}/:id/edit`}
component={EmployeeDialog} />
<Route exact path={`${match.url}/:id`} component={EmployeeDetail} />
</Switch>
employee.reducer.ts 声明了实体的动作和 reducer,例如,让我们使用动作和 reducer 来创建实体。createEntity 动作通过带有 HTTP 有效载荷和通知元数据的 ACTION_TYPES.CREATE_EMPLOYEE 分发。一旦 HTTP 请求解决,我们分发 getEntities 动作以获取更新的实体列表。
Reducer 对于创建和更新动作是通用的。让我们看看创建动作和 reducer:
...
export const ACTION_TYPES = {
...
CREATE_EMPLOYEE: 'employee/CREATE_EMPLOYEE',
...
};
const initialState = {
...
};
// Reducer
export default (state = initialState, action) => {
switch (action.type) {
...
case REQUEST(ACTION_TYPES.CREATE_EMPLOYEE):
...
return {
...
};
...
case FAILURE(ACTION_TYPES.CREATE_EMPLOYEE):
...
return {
...
};
...
case SUCCESS(ACTION_TYPES.CREATE_EMPLOYEE):
case SUCCESS(ACTION_TYPES.UPDATE_EMPLOYEE):
return {
...
};
...
default:
return state;
}
};
const apiUrl = SERVER_API_URL + '/api/employees';
...
export const createEntity: ICrudPutAction = entity => async dispatch => {
const result = await dispatch({
type: ACTION_TYPES.CREATE_EMPLOYEE,
meta: {
successMessage: messages.DATA_CREATE_SUCCESS_ALERT,
errorMessage: messages.DATA_UPDATE_ERROR_ALERT
},
payload: axios.post(apiUrl, entity)
});
dispatch(getEntities());
return result;
};
...
employee.tsx、employee-dialog.tsx、employee-detail.tsx 和 employee-delete-dialog.tsx 分别声明了实体列表、实体模型对话框、实体详情和实体删除对话框。以 employee.tsx 为例,我们使用 TypeScript 接口 IEmployeeProps 定义了 props 的类型,并将其作为 React.Component 类型的泛型传递。我们使用 componentDidMount 生命周期方法在组件挂载时触发获取实体和用户的动作。渲染方法返回 UI 的 JSX。
该组件通过高阶组件连接到 Redux 存储。让我们看一下:
...
export interface IEmployeeProps {
getEntities: ICrudGetAllAction;
employees: any[];
getusers: ICrudGetAllAction;
match: any;
}
export class Employee extends React.Component<IEmployeeProps> {
componentDidMount() {
this.props.getEntities();
this.props.getusers();
}
render() {
...
}
}
const mapStateToProps = storeState => ({
employees: storeState.employee.entities
});
const mapDispatchToProps = { getusers, getEntities };
export default connect(mapStateToProps, mapDispatchToProps)(Employee);
其他组件也遵循类似的方法。与 Angular 相比,React 代码在编码上具有更少的样板代码,并且更加简洁。
摘要
在本章中,我们学习了关于 React、Redux 以及 React 生态系统上其他库的一般概念。我们还学习了如何使用 JHipster 创建 React 应用程序以及为其生成的实体。我们看到了如何利用 TypeScript 与 React 一起使用,并浏览了生成的代码。我们还运行并测试了我们创建的应用程序。在下一章中,我们将通过 JHipster 社区的最佳实践和下一步如何利用你迄今为止所学的内容来结束本书。
第十四章:JHipster 的最佳实践
在本书的前几章中,我们详细学习了 JHipster 以及它所支持的多种工具和技术。以下是到目前为止我们所学到的东西:
-
我们学习了如何开发单体和微服务应用程序。我们还了解了架构上的差异以及选择其中一个而不是另一个的原因。
-
我们使用 JDL 创建了实体,并根据我们的业务需求对生成的应用程序进行了定制。
-
我们使用 Jenkins 创建了一个 CI-CD 设置。
-
我们将单体应用程序部署到了 Heroku 云平台
-
我们使用 Kubernetes 和 Docker 将微服务架构部署到了 Google 云平台。
-
我们学习了 Spring 框架、Spring Boot、Angular、React、Docker 等等。
在本章中,我们将看到下一步要采取的步骤,以及如何使用本书中学到的知识,我们还将讨论一些来自 JHipster 社区的最佳实践、技巧、窍门和建议。作为 JHipster 的核心贡献者,我们还将提供一些我们在本章中学到的见解和经验教训。以下是一些我们将涉及的主题:
-
接下来的步骤
-
需要牢记的最佳实践
-
使用 JHipster 模块
接下来的步骤
JHipster 支持许多技术,了解所有这些技术需要大量的时间和精力;这不可能在一本书中完成。每个支持的技术都需要一本单独的书来学习和掌握。如果你已经熟悉了网络开发的核心概念,那么你现在应该已经相当清楚地了解 JHipster 应用程序是如何工作的了。我们希望这本书为你提供了技术和 JHipster 本身的良好介绍。但仅仅这样是不够的;你需要继续学习才能成为大师。以下是一些你可以追求的任务,以进一步提高你在使用 JHipster 进行网络开发方面的技能。但在那之前,我们建议你更多地了解 Spring 框架和 Angular/React 生态系统,以补充你在本书中学到的内容。
为应用程序添加购物车
在第五章定制和进一步开发中,我们看到了生成的应用程序如何被定制以使其看起来和表现得像一个电子商务网站。正如那里所提到的,仅仅使应用程序真正可用是不够的。以下是一些你可以尝试实现的功能,以使应用程序更加功能完善:
-
在客户端添加一个简单的购物车功能:
-
创建一个
ProductOrder对象来保存OrderItems。ProductOrder与客户相关联,因此使用当前登录用户的详细信息将其标记为客户。 -
在列表中的产品项上添加一个“添加到购物车”按钮。点击按钮时,为产品创建一个新的
OrderItem并将其添加到ProductOrder的OrderItems数组中。如果同一产品被点击多次,则增加现有OrderItem的数量属性。添加一个购物车对话框以列出添加到ProductOrder的所有OrderItems。它可以使用与产品类似的列表 UI,或者一个简单的表格来显示产品、总价和数量。 -
在产品列表页面上添加一个“查看购物车”按钮以查看购物车对话框。
-
-
添加“现在下单”功能:
-
在产品列表页面上添加一个“现在下单”按钮。
-
点击按钮时,将
ProductOrder发送到 REST API 以创建一个新的ProductOrder,使用product-order.service.ts进行此操作。 -
在后端,修改
ProductOrderService.java的保存方法以创建ProductOrder的发票和装运,并将它们全部保存。 -
假设我们接受货到付款,所以现在让我们跳过与支付网关的集成。
-
-
向客户发送订单确认:
-
JHipster 默认提供邮件配置和模板。您可以在
src/main/resources/config/application-*.yml中配置自己的 SMTP 服务器详情。有关如何配置流行 SMTP 服务的说明,请参阅www.jhipster.tech/tips/011_tip_configuring_email_in_jhipster.html。 -
在
src/main/resources/mails中创建一个新的订单确认电子邮件模板。在电子邮件中提供产品详情、总价和数量。 -
使用
MailService.java中提供的sendEmailFromTemplate方法在成功创建发票时发送电子邮件。
-
-
在注册新用户时创建客户档案:
- 在注册页面上添加字段,并从详情中自动为每个用户创建客户实体。
尝试将更改应用到微服务应用程序中。
改进端到端测试
在 第六章,“测试和持续集成”中,我们看到了由于为具有必需关系的实体生成测试的难度,一些端到端测试被注释掉了。尝试使用以下方法修复测试:
-
添加一个在创建后删除实体的方法,类似于我们在 第六章,“测试和持续集成”中看到的客户实体规范。
-
在
src/test/javascript/e2e/entities下的文件中取消注释已注释的端到端测试。 -
使用 Protractor 导航到相关实体页面并创建一个新条目。如果相关实体有必需的关系,则采用相同的方法并嵌套它们,直到所有必需的实体都到位。这也可以在测试的
beforeAll方法中完成。 -
现在回到测试的实体,看看测试是否正常工作。
-
一旦测试完成,请在测试的
afterAll方法中删除创建的实体。 -
探索是否可以自动化在实体页面对象上创建实体项,并在需要时使用它。
改进 CI/CD 管道
在第六章“测试和持续集成”中,当我们使用 CI/CD 子生成器创建Jenkinsfile时,我们注释掉了部署阶段。重新启用它,并在你进行新提交时检查应用程序是否已部署到 Heroku:
-
看看你是否可以向管道中添加端到端测试。
-
如果你的应用程序在 GitHub 上,尝试使用 ci-cd 子生成器将 Travis 添加到项目中。
构建 JHipster 模块
JHipster 有两种机制来扩展其功能:
-
一个模块系统,允许用户构建自己的 Yeoman 生成器(
www.jhipster.tech/modules/creating-a-module/)来补充 JHipster -
JHipster 5 引入的新蓝图机制,用于自定义 JHipster 生成的代码所需的部分
模块和蓝图之间的区别在于,蓝图允许你覆盖生成应用程序的某些部分,而 JHipster 则构建剩余的部分。例如,蓝图可以单独覆盖客户端代码,而服务器端则由 JHipster 生成。另一方面,模块只能更改由 JHipster 生成的部分,因此更适合在 JHipster 创建的部分之上添加补充功能。
尝试构建一个模块来向你的应用程序添加一个简单的页面。
你可以使用 JHipster 模块生成器(github.com/jhipster/generator-jhipster-module)来构建一个新的模块。
需要牢记的最佳实践
几年来,JHipster 社区已经识别并采纳了它支持的技术和工具以及一般技术社区中的许多最佳实践。虽然 JHipster 已经在其创建的代码中尝试遵循这些最佳实践,但以下是一些最佳实践、技巧和窍门,作为用户你应该遵循。
选择客户端框架
当使用 JHipster 时,你可以选择 Angular 或 React 作为客户端框架。不要仅仅因为其炒作而选择,要根据你的需求、团队构成和熟悉程度来选择:
-
如果你来自重量级的 Java/Spring 背景,那么 Angular 将更容易遵循和操作
-
如果你的应用程序需要重型状态管理和共享状态,那么 React 将更适合
-
如果你计划为你的应用程序构建原生移动客户端,那么更成熟的 React 是这个空间的好选择,React Native 允许你在 Web 应用程序和移动应用程序之间重用大量代码
-
如果你的应用程序高度依赖于由设计团队或第三方生成的 HTML 页面,那么与 React 相比,Angular 将更容易集成。
如果你需要很多不属于标准 Bootstrap 的组件,那么请使用现有的组件库,例如 PrimeNG 或 VMware Clarity,而不是从不同来源组装组件。然而,如果你只需要在 Bootstrap 之上添加几个组件,那么请坚持使用 Bootstrap,并为 Angular 或 React 使用兼容 Bootstrap 的组件。
无论你选择什么,都要遵循该项目的社区提供的指南和最佳实践。
选择数据库选项
JHipster 支持许多类型的数据库,从 SQL 到 NoSQL。以下是在选择数据库时的一些考虑因素:
-
对于大多数情况,SQL 数据库就足够了,因此如果你没有理由选择其他 NoSQL 解决方案,请坚持使用 SQL,并从 MySQL、Postgres、Oracle、MariaDB 和 MS SQL 中选择:
-
如果你在一个拥有 Oracle 或 MS SQL 订阅的企业中,那么选择它们是有意义的,因为你将受益于提供的支持和企业功能。
-
如果你需要存储和查询大量的 JSON 数据,那么 Postgres 提供了最佳的 JSON 支持,并具有全文搜索功能。
-
对于大多数简单用例,MySQL 或 MariaDB 就足够了。
-
在与 SQL 数据库一起工作时,始终选择二级 Hibernate 缓存。
-
在选择 SQL 开发数据库时:
-
如果你想要一个简单的开发设置并希望数据持久化,请选择 H2 文件数据库。
-
如果你想要更快的重启速度,并且你的持久数据不需要时不时地被清除,请选择与生产数据库相同的数据库。如果你正在使用提供的 Docker 镜像,那么清除数据将不会成为问题。
-
如果你在开发过程中不需要任何持久数据,并且希望在每次重启时都有一个干净的状态,请选择 H2 内存数据库。
-
-
-
如果你的用例需要大量的重数据读写,并且数据不是非常关系型,那么 Cassandra 将是一个完美的选择,因为它可以分布式工作,并且能够在极端高负载下运行。
-
对于一个正常、非关系型数据结构,MongoDB 可能就足够了。如果需要,你也可以使用 Postgres 作为 NoSQL JSON 存储。
-
如果你需要 NoSQL 的企业级支持,CouchBase 是一个不错的选择。
-
使用 Elasticsearch 与主数据库一起进行全文搜索。如果你只需要简单的过滤,请使用提供的 JPA 过滤选项。请参阅:
www.jhipster.tech/entities-filtering/。
架构考虑
我们已经在第一章“现代 Web 应用开发简介”中讨论了选择微服务或单体架构。以下是关于架构的一些更多要点:
-
如果你是一个小团队,不要使用微服务架构。微服务更多的是关于团队扩展。通常,分解单体架构比从微服务开始更容易。
-
如果你认为你可能需要在将来重构为微服务,请在单体中使用异步消息传递。JHipster 提供了对 Apache Kafka 的支持,这是一个异步消息传递的良好解决方案。
异步消息传递是构建无状态系统的最佳方式。在微服务架构中非常重要,因为你可能经常希望通信是无状态和非阻塞的。一些流行的解决方案包括 Apache Kafka (kafka.apache.org/)、RabbitMQ (www.rabbitmq.com/)和 gRPC (grpc.io)。ReactiveX (reactivex.io/)和 Spring Reactor (projectreactor.io/)是处理异步系统的流行抽象。异步消息传递也使得系统松散耦合。
-
如果你打算向第三方公开 API,请先进行API first开发。我们现在有很好的工作流程使用 Swagger Codegen 来完成它。有关更多信息,请参阅
www.jhipster.tech/doing-api-first-development/。 -
在使用 REST 进行微服务之间的通信时,不要将接口代码放在共享包中;这将使 API 与其客户端紧密耦合,从而导致分布式单体。
-
使用 JHipster,可以分割客户端和服务器。请参阅
www.jhipster.tech/separating-front-end-and-api/。然而,在分离它们之前要三思,因为这将要求你打开 CORS,这会使安全性更加脆弱,并且这种架构有其自身的问题。所以只有在你有充分的理由这样做时才这样做。 -
在服务层使用 DTOs,这样你就可以聚合实体并定义更好的 API,而无需向客户端暴露实体。你将需要为你的实体启用服务层,以便使用 JHipster 来实现这一点。
-
在开始开发之前,了解你应用程序的技术栈。
-
熟悉提供的工具集,例如构建工具(Maven/Gradle/Webpack)、BrowserSync 等。
安全性考虑
安全性是任何应用程序最重要的方面之一,在选择安全机制时应考虑以下因素:
-
对于大多数用例,JWT 认证将足够,所以如果你不确定,就坚持使用它。
-
如果你想在应用程序中实现单点登录,请使用 OAuth 2.0 / OIDC,而不是尝试让 JWT 或会话认证作为单点登录解决方案工作
-
如果你已经在公司中设置了 Keycloak 或 Okta,请选择 OAuth 2.0/OIDC 并将它们连接起来。
-
只有在你想要有状态认证的情况下,才选择基于会话的认证。
部署和维护
这里有很多好的实践;其中一些重要的包括:
-
Docker 是微服务集成测试的必备工具,但使用 Docker 进入生产并不容易,所以使用如 Kubernetes 之类的编排工具。
-
在应用程序生成后立即运行 prod 构建,并在你的应用程序仍然非常简单时立即部署到 prod。这将有助于缓解任何部署问题,因为你可以确信应用程序可以正常工作。
-
当涉及到客户端时,prod 构建与 dev 构建有很大不同,因为资源被压缩和优化。在添加任何前端代码时,库总是验证 prod 构建。
-
总是使用 prod 配置运行端到端 protractor 测试。
-
拥抱嵌入式 servlet 引擎,并忘记部署到 JEE 服务器,如 WebLogic、WebSphere、JBoss 等。产生的工件是可执行的,并包含一个嵌入的 Undertow 服务器。
你知道 Java EE 正在更名为 Jakarta EE 吗?有关更多信息,请参阅www.infoq.com/news/2018/03/java-ee-becomes-jakarta-ee。
-
经常使用 JHipster 升级子生成器进行升级。这将确保你使用的工具和技术都是最新和安全的。
-
从
application-prod.yml中删除所有秘密,并使用占位符从命令行或环境变量中注入值。永远不要在代码或配置文件中放置任何秘密或密码。
一般最佳实践
通常,以下是一些你应该考虑的最佳实践:
-
如果你开始使用实体子生成器创建实体,那么一旦实体数量超过几个,就使用
export-jdl并切换到 JDL。 -
首先不添加任何模块生成应用程序,只在需要时添加所需的模块。
-
在添加模块之前仔细评估模块。确保它支持你选择的堆栈。
-
遵循每个底层技术的最佳实践。Angular 最佳实践、Spring 最佳实践等。只有有充分的理由才改变它们。
-
在客户端和服务器端使用提供的库版本。让它们全部一起工作是一项艰巨的任务,所以坚持使用它们。当 JHipster 更新它们或你真的需要修复一个错误或安全问题时才更新它们。
-
遵循 JHipster 提供的流程。它们在这里是为了帮助你。通常,按照推荐的方式使用它们有非常好的理由。在寻求外部帮助之前,请先阅读 JHipster 文档。
-
你有一个非常好的默认工作环境;不要破坏它。
-
使用实时重载,前端和后端更新是自动和快速的。充分利用它们。
-
使用提供的子生成器进行生产部署很容易。
-
-
使用为你部署到的云平台提供的子生成器。
-
Git 是你的朋友。每次添加模块或实体,或使用子生成器时,都要提交。任何错误(包括数据库中的错误)都应该能够通过 Git 轻松回滚。
使用 JHipster 模块
JHipster 模块和蓝图是向生成的代码添加更多功能和功能的好方法。在撰写本文时,JHipster 市场上有许多模块(55 个)可供选择(www.jhipster.tech/modules/marketplace),您还可以根据自己的需求构建自己的模块。以下是一些值得注意的模块:
-
Ignite JHipster:这为 JHipster 应用程序提供了 React Native 的样板代码。使用 JHipster 作为后端来启动您的 React Native 应用程序是一个理想的方式。
-
实体审计:此模块启用实体审计。它使用 Hibernate 审计钩子为实体 CRUD 操作创建自定义审计。它还提供 Javers 作为审计机制,而不是自定义 Hibernate 审计。它还提供了一个漂亮的 UI,可以在 Angular 应用程序中查看审计。它将为新实体以及现有实体启用审计。
-
Ionic:这为 JHipster 应用程序提供了一个 Ionic 客户端。如果您想使用 JHipster 后端和 Angular 前端创建移动应用程序,这是一个理想的选择。
-
Swagger CLI:此模块为生成 JHipster 应用的 Swagger 客户端提供支持。
-
gRPC:此模块为 JHipster 应用程序生成 gRPC 反应式端点。它也支持实体,如果您想要一个非阻塞的反应式 API 用于您的 JHipster 应用程序,这是一个理想的选择。
-
VueJS:此模块为 JHipster 应用程序提供 VueJS 支持。它创建最小的样板代码以启动使用 VueJS 的 JHipster 应用的客户端开发。
查看以下步骤:
-
要使用 JHipster 模块,首先使用
npm i -g generator-<module-name>或yarn add global generator-<module-name>安装它。 -
安装完成后,进入 JHipster 应用程序目录,并执行
yo <module-name>以启动模块并按照提示操作。
贡献 JHipster
学习 JHipster 及其支持的技术的一个最好的方法是通过直接为 JHipster 做贡献。有关为开发设置 JHipster 的详细信息,请参阅贡献指南(github.com/jhipster/generator-jhipster/blob/master/CONTRIBUTING.md)。
您可以通过多种方式为项目做出贡献;以下是一些方法:
-
如果您发现了一个错误,请在 GitHub 项目中提交一个 issue(
github.com/jhipster/generator-jhipster),遵循 issue 模板中的指南,运行jhipster info,并提供复现步骤。您也可以尝试自己修复问题,并在成功后提交一个 PR。 -
处理开放问题和功能请求。这样,您将学习 JHipster 及其使用的技术。
-
在 Stack Overflow 上回答与 JHipster 相关的问题 (
stackoverflow.com/questions/tagged/jhipster)。
摘要
我们一起通过 JHipster 和全栈开发之旅已经结束。在本章中,我们学习了 JHipster 社区识别出的许多最佳实践。尝试完成“下一步行动”部分中的作业,因为它将帮助你应用所学知识,并帮助你更好地理解这些概念。
我们希望你的学习体验非常棒,并希望你在本书中学到的关于 JHipster 的知识能帮助你完成下一个项目。
关注 Twitter 上的 @java_hipster,这样你就可以看到新版本发布和安全性漏洞披露的情况。
如果你对 JHipster 有任何疑问或问题,请将你的问题发布到 Stack Overflow (stackoverflow.com/questions/tagged/jhipster) 并添加 jhipster 标签。团队将会收到通知,并乐意提供帮助!





浙公网安备 33010602011771号