65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2021 年 4 月 21 日

CPOL

11分钟阅读

viewsIcon

3854

downloadIcon

172

在本例中,您将创建类似于 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 BundleHands-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 GETPOST 请求。对于 GET 请求,投票通过查询字符串传递,形式为 vote=<vote id>,其中 <vote id>votingDBvoteTypesidVoteType 提供。对于 POST 请求,投票通过 form-urlencoded 主体传递。

VoteModel 类提供了两个主要的构造函数:一个接受 GET 请求的查询字符串,另一个接受 POST 请求的主体。两个构造函数还接受 Principal NamePrincipal ID,并将它们连接起来构建 voter id

run 方法创建一个 PageBuilder 类的实例以获取当前的 electionID。然后它实例化 VoteModelVoteManager,并调用 VoteManagercastVote 方法为当前选举投票。之后,它调用几个 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 项目推送更新,观察管道如何更新函数应用,然后运行更新后的函数。

以下是我们建议的应用程序改进列表

如果您想进一步探索 Azure Functions,我们建议您从这里开始:Azure Functions Java 开发人员指南

在下一期“Java on Azure”系列中,我们将通过学习如何使用 Azure Functions 实现一个假设的温度监控应用程序来进一步了解 Java on Azure。

© . All rights reserved.