Azure 上的 Java:完整的云原生微服务





5.00/5 (2投票s)
在本例中,您将创建类似于 Facebook 页面上显示的简单投票。该应用程序提供一个选举标题、投票期间的开始和结束日期、一组投票选项以及一个包含结果的报告。
在前一篇文章中,我们向您展示了如何使用与 GitHub 集成的 Azure DevOps Pipelines 设置自动化的 CI/CD 管道。本文的目标是开发一个简单但功能完整的应用程序,该应用程序使用一个使用三个 Azure Function 应用实现的云原生微服务。您可以使用完整的应用程序来探索 Azure 平台。该应用程序提供类似于 Facebook 页面上看到的简单投票。一个 Azure Function 应用返回一个显示投票单的 HTML 页面,第二个函数应用接受投票,第三个函数应用提供投票报告。每个函数应用都在一个单独的项目中,以展示不同的生命周期如何交付整个应用程序的各个部分。
数据处理方面,我们将使用 Azure Database for MySQL。Microsoft 在“快速入门:将 Java 和 JDBC 与 Azure Database for MySQL 结合使用”中提供了一个类似的数据项目。本文可供参考,但我们将对其进行调整,以使用 Azure Portal、MySQL Workbench 和 Eclipse,因为这些工具您从我们上一篇文章中已经熟悉了。
我们构建了一个无服务器应用程序,因为 Kubernetes 之类的主题超出了本文的范围。Microsoft 提供了一些关于容器化的书籍,涵盖了您在 Azure 上部署和管理容器化应用程序所需的所有知识。
例如:Kubernetes Bundle 和 Hands-On Kubernetes on Azure。
我们依赖于在前一篇文章中构建的基础设施,因此在开始此项目之前,您应该回顾一下。在此项目中,我们将为三个函数应用中的每一个创建单独的资源。这模拟了现实情况,即三个独立的团队构建和部署完整企业应用程序的各个部分。
我们在 Azure DevOps 中创建了三个项目。每个项目都有自己的服务连接器和管道,我们将在下面讨论。
用户界面
我们使用的 HTML 是从 Bootstrap 的“入门页面”改编而来的。Bootstrap 是一个广泛使用的框架,支持响应式 Web 应用程序。我们假设您熟悉它。
应用程序用户界面 (UI) 非常简单。用户打开浏览器访问一个 URL(可能通过其他页面上的链接),该 URL 返回投票页面。链接 https://azureexamplepollingui.azurewebsites.net/api/GetVotingUI 引用了 AzureExamplePollingUI
函数应用,该应用响应 HTTP 触发器。请注意,此应用已注册 Azure Active Directory (AAD)。它指定用户必须使用 AAD 进行身份验证。Azure 在调用函数之前会引导用户完成 OAuth 2 身份验证和授权过程。调用函数时,它会返回以下 HTML 页面。
用户选择一个选项并单击“提交”以录入其投票。表单操作将一个 GET
请求发送到 CastVote
函数应用,地址为:https://azurecastvote.azurewebsites.net/api/CastVote?vote=<voteId>。函数应用会记录投票并返回一个如下所示的确认。
屏幕会显示 OAuth 2 提供的 Principal ID
变量的值。此版本的应用程序不会对该值执行任何操作。显示它是为了让您知道用户的 ID 可用于其他目的,例如授权访问或检查重复投票。
您可以通过访问以下 URL 来随时获取当前的投票结果:https://azureexamplepollingreports.azurewebsites.net/api/RetrieveVoteReport。在身份验证用户后,返回的报告如下所示。
该函数包含获取 Principal ID
值的代码。您可以使用它来确定该用户是否有权检索此报告,尽管我们的实现未使用它。
数据库
Microsoft 提供了快速入门文档来创建服务器和配置防火墙。此处描述的过程跳过了本文档中对我们来说不必要的步骤。
创建服务器
在 Azure 门户中,在搜索字段中键入“Azure Database”。在搜索结果中,单击 Azure Database for MySQL Servers。
单击“创建”以开始服务器创建过程。
在“单服务器”窗格中单击“创建”。
选择您的订阅和资源组。单击“配置服务器”。
本例中,选择一个“基本”、1v CPU 的服务器,具有 5GB 存储空间。选择“基本”选项卡并滑动滑块以设置上述参数。单击“确定”。
返回“创建 MySQL 服务器”页面,设置管理员用户名和密码字段。单击“审核 + 创建”。
在审核面板中,单击“创建”以创建服务器。
定义数据库防火墙规则
Azure 防火墙默认阻止对新数据库的所有访问。我们使用快速入门文档中描述的方法来提供对本地计算机的访问。
在门户中找到资源并打开“概述”页面。找到并单击“连接安全”。
单击“添加当前客户端 IP 地址”。单击“保存”以允许您的当前客户端连接到服务器。
有关防火墙规则的更多信息,请参阅使用 Azure 门户创建和管理 Azure Database for MySQL 防火墙规则。
实现架构和数据
我们假设您熟悉MySQL Workbench(或类似工具),并且您可以使用以下脚本实现数据库。第一个脚本创建架构。您必须知道在上一个部分中创建的数据库名称,因为您会将该值添加到 url
密钥中。
本例中,我们创建了一个名为“votingdb”的数据库。
-- MySQL Workbench Forward Engineering
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
-- -----------------------------------------------------
-- Schema votingDB
-- -----------------------------------------------------
-- -----------------------------------------------------
-- Schema votingDB
-- -----------------------------------------------------
CREATE SCHEMA IF NOT EXISTS `votingDB` ;
USE `votingDB` ;
-- -----------------------------------------------------
-- Table `votingDB`.`electionDetails`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `votingDB`.`electionDetails` ;
CREATE TABLE IF NOT EXISTS `votingDB`.`electionDetails` (
`idElection` INT NOT NULL AUTO_INCREMENT,
`electionTitle` VARCHAR(255) NOT NULL,
`startDate` DATE NOT NULL,
`endDate` DATE NOT NULL,
PRIMARY KEY (`idElection`))
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `votingDB`.`voteTypes`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `votingDB`.`voteTypes` ;
CREATE TABLE IF NOT EXISTS `votingDB`.`voteTypes` (
`idVoteType` INT NOT NULL,
`voteName` VARCHAR(45) NOT NULL,
`electionId` INT NOT NULL,
PRIMARY KEY (`idVoteType`),
INDEX `fk_election_idx` (`electionId` ASC) ,
CONSTRAINT `fk_election`
FOREIGN KEY (`electionId`)
REFERENCES `votingDB`.`electionDetails` (`idElection`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `votingDB`.`votes`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `votingDB`.`votes` ;
CREATE TABLE IF NOT EXISTS `votingDB`.`votes` (
`idVotes` INT NOT NULL AUTO_INCREMENT,
`voter` VARCHAR(255) NOT NULL,
`vote` INT NOT NULL,
`idElection` INT NOT NULL,
PRIMARY KEY (`idVotes`),
INDEX `fk_voteId_idx` (`vote` ASC) ,
UNIQUE INDEX `idVotes_UNIQUE` (`idVotes` ASC) ,
CONSTRAINT `fk_voteId`
FOREIGN KEY (`vote`)
REFERENCES `votingDB`.`voteTypes` (`idVoteType`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
此脚本插入初始数据
INSERT INTO electionDetails (electionTitle, startDate, endDate) VALUES ('Peas Please', '2021/3/1', '2021/4/1');
INSERT INTO votetypes (idVoteType, voteName, electionId) values (1, 'Absolutely', 1);
INSERT INTO votetypes (idVoteType, voteName, electionId) values (2, 'No Thank You', 1);
函数
我们的示例应用程序提供类似于 Facebook 页面上显示的简单投票。该应用程序提供选举标题、投票期间的开始和结束日期、一组投票选项以及一个包含结果的报告。
要构建此应用程序,我们将创建三个函数:一个允许用户投票,一个返回当前投票总数的报告,一个返回实现 UI 的 HTML 页面。我们假设管理员可以在数据库中设置选举标题和投票选项。
所有代码都在 GitHub 上。欢迎您克隆存储库以节省一些步骤。
本文档仅包含与讨论相关的代码。
创建 Azure Function Apps
您必须创建三个函数应用。以下是我们为此实现创建的函数应用。
在所有这些函数应用中,您必须按照如下方式更改 src/main/resources/application.properties 文件中的属性
url =jdbc:mysql://azure-function-voting.mysql.database.azure.com:3306/votingdb?serverTimezone=UTC&verifyServerCertificate=true&useSSL=true&requireSSL=true user=<admin user id>@azure-function-voting password=<admin password>
url
属性包含数据库名称(votingdb
)以及用户 ID 和管理员密码。
投票
此函数应用提供了投票功能。创建 DevOps 项目后,您必须向其中添加服务连接器和管道。您可以使用以下代码替换管道中的标准代码
variables: serviceConnectionToAzure: 'AzureCastVote' appName: 'AzureCastVote' functionAppName: 'castVote' POM_XML_Directory: 'castVote' trigger: - main pool: vmImage: ubuntu-latest steps: - task: Maven@3 inputs: mavenPomFile: '$(POM_XML_Directory)/pom.xml' mavenOptions: '-Xmx3072m' javaHomeOption: 'JDKVersion' jdkVersionOption: '1.11' jdkArchitectureOption: 'x64' publishJUnitResults: true testResultsFiles: '**/surefire-reports/TEST-*.xml' goals: 'package' - task: CopyFiles@2 displayName: Copy Files inputs: SourceFolder: $(system.defaultworkingdirectory)/$(POM_XML_Directory)/target/azure-functions/$(functionAppName)/ Contents: '**' TargetFolder: $(build.artifactstagingdirectory) - task: PublishBuildArtifacts@1 displayName: Publish Artifact inputs: PathtoPublish: $(build.artifactstagingdirectory) - task: AzureFunctionApp@1 displayName: Azure Function App deploy inputs: azureSubscription: $(serviceConnectionToAzure) appType: 'functionAppLinux' appName: $(appName) package: $(build.artifactstagingdirectory) runtimeStack: 'JAVA|11'
创建 Eclipse 项目和您的存储库,如下所示
此函数中有五个类
类文件 | 目的 |
Function.java | 实现 HTTP 触发函数的 run 方法 |
DatabaseConnection.java | 一个包装类,负责加载连接属性和创建到数据库的 JDBC 连接 |
PageBuilder.java | 实现一个 HTML 页面以确认投票 |
VoteManager.java | 提供一个数据库接口来记录投票 |
VoteModel.java | 数据模型对象类 |
Function
类中的 run
方法响应 HTTP GET
和 POST
请求。对于 GET
请求,投票通过查询字符串传递,形式为 vote=<vote id>
,其中 <vote id>
由 votingDB
、voteTypes
和 idVoteType
提供。对于 POST
请求,投票通过 form-urlencoded
主体传递。
VoteModel
类提供了两个主要的构造函数:一个接受 GET 请求的查询字符串,另一个接受 POST 请求的主体。两个构造函数还接受 Principal Name
和 Principal ID
,并将它们连接起来构建 voter id
。
run
方法创建一个 PageBuilder
类的实例以获取当前的 electionID
。然后它实例化 VoteModel
和 VoteManager
,并调用 VoteManager
的 castVote
方法为当前选举投票。之后,它调用几个 PageBuilder
方法来构建它在响应中返回的 HTML 文档。
检索投票报告
与 Cast Vote 函数应用一样,创建 DevOps 资源后,您必须更改管道 YAML
variables: serviceConnectionToAzure: 'Reporting Function App' appName: 'AzureExamplePollingReports' functionAppName: 'retrieveVoteReport' POM_XML_Directory: 'retrieveVoteReport' trigger: - main pool: vmImage: ubuntu-latest steps: - task: Maven@3 inputs: mavenPomFile: '$(POM_XML_Directory)/pom.xml' mavenOptions: '-Xmx3072m' javaHomeOption: 'JDKVersion' jdkVersionOption: '1.11' jdkArchitectureOption: 'x64' publishJUnitResults: true testResultsFiles: '**/surefire-reports/TEST-*.xml' goals: 'package' - task: CopyFiles@2 displayName: Copy Files inputs: SourceFolder: $(system.defaultworkingdirectory)/$(POM_XML_Directory)/target/azure-functions/$(functionAppName)/ Contents: '**' TargetFolder: $(build.artifactstagingdirectory) - task: PublishBuildArtifacts@1 displayName: Publish Artifact inputs: PathtoPublish: $(build.artifactstagingdirectory) - task: AzureFunctionApp@1 displayName: Azure Function App deploy inputs: azureSubscription: $(serviceConnectionToAzure) appType: 'functionAppLinux' appName: $(appName) package: $(build.artifactstagingdirectory) runtimeStack: 'JAVA|11'
创建 Eclipse 项目和您的存储库,如下所示
此函数中有四个类
类文件 | 目的 |
Function.java | 实现 HTTP 触发函数的 run 方法 |
DatabaseConnection.java | 一个包装类,负责加载连接属性和创建到数据库的 JDBC 连接 |
PageBuilder.java | 以 HTML 格式实现报告的框架。 |
Reports.java | 从数据库获取投票结果,并以 HTML 格式构建报告的结果表部分 |
Function
类仅响应 HTTP GET
请求,不带任何参数。它创建 PageBuilder
类的实例。此类从 baseUi.html 资源加载 HTML 模板,构建响应页面的“框架”,确定哪个选举是活动的(使用当前日期),然后调用 Reports
类。getElectionReport
方法返回 HTML 行格式的统计数据,这些数据将被插入到页面框架中。
获取应用程序页面
创建相关资源后,您必须按如下方式更改管道 YAML 文件
variables: serviceConnectionToAzure: 'PollingUIConnection' appName: 'AzureExamplePollingUI' functionAppName: 'AzureExamplePollingUI' POM_XML_Directory: 'AzureExamplePollingUI' trigger: - main pool: vmImage: ubuntu-latest steps: - task: Maven@3 inputs: mavenPomFile: '$(POM_XML_Directory)/pom.xml' mavenOptions: '-Xmx3072m' javaHomeOption: 'JDKVersion' jdkVersionOption: '1.11' jdkArchitectureOption: 'x64' publishJUnitResults: true testResultsFiles: '**/surefire-reports/TEST-*.xml' goals: 'package' - task: CopyFiles@2 displayName: Copy Files inputs: SourceFolder: $(system.defaultworkingdirectory)/$(POM_XML_Directory)/target/azure-functions/$(functionAppName)/ Contents: '**' TargetFolder: $(build.artifactstagingdirectory) - task: PublishBuildArtifacts@1 displayName: Publish Artifact inputs: PathtoPublish: $(build.artifactstagingdirectory) - task: AzureFunctionApp@1 displayName: Azure Function App deploy inputs: azureSubscription: $(serviceConnectionToAzure) appType: 'functionAppLinux' appName: $(appName) package: $(build.artifactstagingdirectory) runtimeStack: 'JAVA|11'
创建 Eclipse 项目和您的存储库,如下所示
此函数中有三个类
类文件 | 目的 |
Function.java | 实现 HTTP 触发函数的 run 方法 |
DatabaseConnection.java | 一个包装类,负责加载连接属性和创建到数据库的 JDBC 连接 |
PageBuilder.java | 以 HTML 格式实现报告的框架 |
Function
类仅支持 HTTP GET
请求,并且不需要请求数据。run
方法实例化 PageBuilder
类。PageBuilder
对象确定当前选举,在页面中设置选举标题,并构建一个用于投票选项的单选按钮列表。之后,Function 的 run
方法返回生成的 HTML。
安全性 - 应用程序注册
将函数应用注册到 Azure Active Directory 会触发 OAuth 2.0 授权,因此您必须注册创建的每个函数应用。Microsoft 在此处提供了此过程的文档:配置您的 App Service 或 Azure Functions 应用以使用 Microsoft 帐户登录。
在 Azure 门户主页上,在搜索字段中键入“Azure Active”。在搜索结果中,单击 Azure Active Directory 链接。
在“概述”页面上,确保您引用的是正确的目录。单击“应用注册”链接。
单击“新建注册”链接。
按以下屏幕截图所示填写注册表单。
我们允许任何拥有 Azure 帐户的人调用这些函数。回调 URI 设置为 https://azurejavademofunction.azurewebsites.net/.auth/login/aad/callback 会将授权重定向回 Azure 为我们的函数应用提供的帮助页面。
在“应用注册”页面上,复制应用程序(客户端)ID 字段的值(稍后会用到)。
您必须选择 OAuth2 返回的令牌类型。
在目录的“概述”页面上,单击您刚刚完成的注册。在侧面板中选择“身份验证”。
当“身份验证”选项卡打开时,向下滚动到“隐式授权和混合流”部分,并选择两种令牌类型(除非您确切知道您的应用程序只需要其中一种)。单击“保存”以更新注册。
现在您必须配置函数应用以使用 Active Directory 身份验证。
在 Azure 门户主页上,在搜索字段中键入“function”。在搜索结果中,单击 Function App。
选择一个函数应用。
单击“身份验证/授权”。
启用 App Service 身份验证。如下所示,设置请求未授权时要执行的操作。单击“未配置”。
单击“高级”。提供客户端 ID(来自应用注册的概述)和颁发者 URI。使用屏幕截图所示的值:(https://login.microsoft.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0)。
当您返回“身份验证/授权”选项卡时,单击“保存”以完成注册。
后续步骤
如用户界面部分所述,您现在应该可以通过 URL https://azureexamplepollingui.azurewebsites.net/api/GetVotingUI 启动应用程序。在此,您可以投票。然后,您可以从 https://azureexamplepollingreports.azurewebsites.net/api/RetrieveVoteReport 检索总数。
诊断超出了本文的范围,但万一出现问题,有三个主要地方可以查找
- 如果管道未运行,请确保您的
push
已更新 GitHub。 - 管道 YAML 中的变量不正确,或者 Azure Function App 不在分配给服务连接器的资源组中,这会导致大多数问题。检查管道中的作业是否存在错误。
- 请按照以下说明设置和使用 Azure Function 监视:使用 Azure Monitor Logs 监视 Azure Functions。
虽然这是一个非常简单的应用程序,但它包含了典型三层 Web 应用使用的所有基本部分。您应该尝试从 Eclipse 项目推送更新,观察管道如何更新函数应用,然后运行更新后的函数。
以下是我们建议的应用程序改进列表
- 将投票实现为 POST 操作。这需要实现 CORS。
请参阅管理您的函数应用。 - 改进端到端身份验证。
请参阅教程:在 Azure App Service 中进行端到端身份验证和授权用户和身份验证流。 - 将 UI 实现为真正的单页应用程序。
请参阅概述文章(场景:单页应用程序)和一个可适应的示例(SPA 快速入门)。 - 实现 Azure Key Vault。
查看此 Java 快速入门文档。
如果您想进一步探索 Azure Functions,我们建议您从这里开始:Azure Functions Java 开发人员指南。
在下一期“Java on Azure”系列中,我们将通过学习如何使用 Azure Functions 实现一个假设的温度监控应用程序来进一步了解 Java on Azure。