Docker 项目
在本节中,我们发布一个 springcloud/spring-cloud-contract Docker 镜像,其中包含一个项目,该项目会生成测试并在 EXPLICIT 模式下针对正在运行的应用程序运行这些测试。
EXPLICIT 模式意味着从合约生成的测试会发送真实的请求,而不是模拟的请求。
我们还发布了一个 spring-cloud/spring-cloud-contract-stub-runner Docker 镜像,用于启动 Stub Runner 的独立版本。
Maven、JAR 与二进制存储简介
由于非 JVM 项目也可以使用 Docker 镜像,因此有必要解释 Spring Cloud Contract 打包默认设置背后的基本术语。
以下定义的部分内容摘自 Maven 术语表:
-
项目(Project): Maven 以项目为单位进行思考。项目是您构建的一切。这些项目遵循一个明确定义的“项目对象模型”。项目可以依赖于其他项目——在这种情况下,后者被称为“依赖项”。一个项目可能由多个子项目组成。然而,这些子项目仍然被视为平等的项目。 -
构件(Artifact): 构件是项目产生或使用的东西。Maven 为项目产生的构件示例包括 JAR 文件以及源代码和二进制分发文件。每个构件都由一个组 ID 和一个在该组内唯一的构件 ID 唯一标识。 -
JAR: JAR 代表 Java 归档文件。其格式基于 ZIP 文件格式。Spring Cloud Contract 将契约和生成的存根打包在 JAR 文件中。 -
GroupId: 组 ID 是项目的全局唯一标识符。虽然这通常只是项目名称(例如commons-collections),但使用完全限定的包名有助于将其与其他具有相似名称的项目区分开来(例如org.apache.maven)。通常,当发布到构件管理器时,GroupId会被斜杠分隔并形成 URL 的一部分。例如,对于组 IDcom.example和构件 IDapplication,结果将是/com/example/application/。 -
分类器(Classifier): Maven 依赖项表示法如下所示:groupId:artifactId:version:classifier。分类器是传递给依赖项的附加后缀——例如stubs或sources。同一个依赖项(例如com.example:application)可以产生多个通过分类器相互区分的构件。 -
构件管理器(Artifact manager): 当您生成二进制文件、源代码或包时,您希望它们可供其他人下载、引用或重用。在 JVM 世界中,这些构件通常是 JAR 文件。对于 Ruby,这些构件是 gems。对于 Docker,这些构件是 Docker 镜像。您可以将这些构件存储在管理器中。此类管理器的示例包括 Artifactory 和 Nexus。
在生产者端生成测试
镜像会搜索 /contracts 文件夹下的合约。运行测试的输出结果可在 /spring-cloud-contract/build 文件夹中找到(便于调试使用)。
你可以挂载你的合约并传递环境变量。镜像随后会:
-
生成合约测试
-
针对提供的 URL 运行测试
-
生成 WireMock 存根
-
将存根发布到 Artifact Manager(可选 — 默认开启)
环境变量
Docker 镜像需要一些环境变量来指向您正在运行的应用程序、Artifact manager 实例等。以下列表描述了这些环境变量:
表 1. Docker 环境变量
| 名称 | 描述 | 默认 |
|---|---|---|
| ADDITIONAL_FLAGS | (仅限 Docker 镜像)传递给 Gradle 构建的额外标志 | |
| DEBUG | (Docker Image only) 适用于 Docker 镜像 - 为 Gradle 构建开启调试模式 | false |
| EXTERNAL_CONTRACTS_ARTIFACT_ID | 包含合约的项目的 Artifact ID | |
| EXTERNAL_CONTRACTS_CLASSIFIER | 具有合约的项目分类器 | |
| EXTERNAL_CONTRACTS_GROUP_ID | 包含合约的项目组 ID | com.example |
| EXTERNAL_CONTRACTS_PATH | 给定项目的合约路径,位于包含合约的项目内部。默认为以斜杠分隔的 EXTERNAL_CONTRACTS_GROUP_ID 与 / 和 EXTERNAL_CONTRACTS_ARTIFACT_ID 拼接而成。例如,对于组 ID cat-server-side.dog 和构件 ID fish,合约路径将为 cat/dog/fish。 | |
| EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_PASSWORD | (可选)如果 EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL 需要身份验证,则提供密码。默认值为 REPO_WITH_BINARIES_PASSWORD,如果未设置该值,则默认为 password。 | |
| EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL | 您的 Artifact Manager 的 URL。默认值为 REPO_WITH_BINARIES_URL 环境变量的值,如果未设置该环境变量,则默认值为 [localhost:8081/artifactory/libs-release-local](http://localhost:8081/artifactory/libs-release-local)。 | |
| EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_USERNAME | (可选)如果 EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL 需要身份验证,则提供用户名。默认值为 REPO_WITH_BINARIES_USERNAME。如果未设置该值,则默认使用 admin。 | |
| EXTERNAL_CONTRACTS_VERSION | 包含合约的项目版本。默认相当于选择最新版本。 | + |
| EXTERNAL_CONTRACTS_WORK_OFFLINE | 如果设置为 true,则从容器的 .m2 目录中检索包含合约的构件。请将本地的 .m2 挂载为容器中 /root/.m2 路径下可用的卷。 | false |
| FAIL_ON_NO_CONTRACTS | 如果没有合约存在,构建是否应该失败? | false |
| MESSAGING_TYPE | 消息类型。可以是 [rabbit] 或 [kafka]。 | |
| PRODUCER_STUBS_CLASSIFIER | 用于生成生产者存根的归档分类器 | stubs |
| PROJECT_GROUP | 您项目的 group ID | com.example |
| PROJECT_NAME | 您项目的 artifact id | example |
| PROJECT_VERSION | 您项目的版本 | 0.0.1-SNAPSHOT |
| PUBLISH_ARTIFACTS | 如果设置为 true,则将工件发布到二进制存储 | true |
| PUBLISH_ARTIFACTS_OFFLINE | 如果设置为 true,则将构件发布到本地 m2 | false |
| PUBLISH_STUBS_TO_SCM | 如果设置为 true,将运行任务以将存根发布到 scm。 | false |
| REPO_ALLOW_INSECURE_PROTOCOL | (可选) 如果为 <true>,允许通过不安全的 HTTP 向 Artifact Manager 发布构件 | false |
| REPO_WITH_BINARIES_PASSWORD | (可选)当 Artifact Manager 受保护时的密码 | password |
| REPO_WITH_BINARIES_URL | 您的 Artifact Manager 的 URL(本地运行时默认为 Artifactory 的默认 URL) | localhost:8081/artifactory/libs-release-local |
| REPO_WITH_BINARIES_USERNAME | (可选)当 Artifact Manager 受保护时的用户名 | admin |
| STANDALONE_PROTOCOL | 对于独立版本,应添加哪个附加协议 |
运行测试时会使用以下环境变量:
表 2. Docker 环境变量 - 在运行时读取
| 名称 | 描述 | 默认值 |
|---|---|---|
| APPLICATION_BASE_URL | 应用程序运行的 URL。 | |
| APPLICATION_PASSWORD | 访问应用程序的可选密码。 | |
| APPLICATION_USERNAME | 访问应用程序的可选用户名。 | |
| MESSAGING_TRIGGER_CONNECT_TIMEOUT | 连接到应用程序以触发消息的超时时间。 | 5000 |
| MESSAGING_TRIGGER_READ_TIMEOUT | 从应用程序读取触发消息的响应的超时时间。 | 5000 |
| MESSAGING_TYPE | 消息传递类型。可以是 [rabbit] 或 [kafka]。 | |
| MESSAGING_TYPE | 定义处理基于消息的契约时的消息传递类型。 | |
| SPRING_KAFKA_BOOTSTRAP_SERVERS | 对于 Kafka - 代理地址。 | |
| SPRING_RABBITMQ_ADDRESSES | 对于 RabbitMQ - 代理地址。 |
自定义 gradle 构建
你可以通过在运行容器时挂载自定义的构建文件作为卷,来提供一个自定义的 gradle.build 在容器中运行:
$ docker run -v <absolute-path-of-your-custom-file>:/spring-cloud-contract/build.gradle springcloud/spring-cloud-contract:<version>
通过 HTTP 的使用示例
在本节中,我们将探索一个简单的MVC应用程序。首先,请克隆以下git仓库,并通过运行以下命令切换到生成的目录:
$ git clone https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs
$ cd bookstore
合约文件位于 /contracts 文件夹中。
由于我们需要运行测试,可以执行以下命令:
$ npm test
然而,出于学习目的,我们将其拆分为以下几个部分:
# Stop docker infra (nodejs, artifactory)
$ ./stop_infra.sh
# Start docker infra (nodejs, artifactory)
$ ./setup_infra.sh
# Kill & Run app
$ pkill -f "node app"
$ nohup node app &
# Prepare environment variables
$ SC_CONTRACT_DOCKER_VERSION="..."
$ APP_IP="192.168.0.100"
$ APP_PORT="3000"
$ ARTIFACTORY_PORT="8081"
$ APPLICATION_BASE_URL="http://${APP_IP}:${APP_PORT}"
$ ARTIFACTORY_URL="http://${APP_IP}:${ARTIFACTORY_PORT}/artifactory/libs-release-local"
$ CURRENT_DIR="$( pwd )"
$ CURRENT_FOLDER_NAME=${PWD##*/}
$ PROJECT_VERSION="0.0.1.RELEASE"
# Run contract tests
$ docker run --rm -e "APPLICATION_BASE_URL=${APPLICATION_BASE_URL}" -e "PUBLISH_ARTIFACTS=true" -e "PROJECT_NAME=${CURRENT_FOLDER_NAME}" -e "REPO_WITH_BINARIES_URL=${ARTIFACTORY_URL}" -e "PROJECT_VERSION=${PROJECT_VERSION}" -v "${CURRENT_DIR}/contracts/:/contracts:ro" -v "${CURRENT_DIR}/node_modules/spring-cloud-contract/output:/spring-cloud-contract-output/" springcloud/spring-cloud-contract:"${SC_CONTRACT_DOCKER_VERSION}"
# Kill app
$ pkill -f "node app"
通过 bash 脚本,会发生以下情况:
-
基础设施(MongoDb 和 Artifactory)已搭建完成。在实际场景中,您会使用模拟数据库运行 NodeJS 应用。在本示例中,我们希望展示如何快速利用 Spring Cloud Contract 的优势。
-
由于这些限制,契约也代表了有状态的情况。
-
第一个请求是
POST,会导致数据插入数据库。 -
第二个请求是
GET,返回包含 1 个先前插入元素的列表数据。
-
-
NodeJS 应用已启动(在端口
3000上)。 -
契约测试通过 Docker 生成,并针对正在运行的应用运行测试。
-
契约取自
/contracts文件夹。 -
测试输出可在
node_modules/spring-cloud-contract/output下找到。
-
-
存根已上传至 Artifactory。您可以在 localhost:8081/artifactory/libs-release-local/com/example/bookstore/0.0.1.RELEASE/ 找到它们。存根位于 localhost:8081/artifactory/libs-release-local/com/example/bookstore/0.0.1.RELEASE/bookstore-0.0.1.RELEASE-stubs.jar。
通过消息传递的使用示例
如果你想在通过Docker镜像进行消息传递时使用Spring Cloud Contract(例如在多语言应用中),那么你需要满足以下先决条件:
-
在生成测试之前,中间件(例如 RabbitMQ 或 Kafka)必须处于运行状态。
-
您的合约需要调用一个方法
triggerMessage(…),其String参数需等于合约的label。 -
您的应用程序需要提供一个 HTTP 端点,以便我们可以通过它触发消息
- 该端点不应在生产环境中可用(可以通过环境变量启用)
消息传递合约示例
合约需要调用 triggerMessage(…) 方法。该方法已在 Docker 镜像中所有测试的基类中提供,并将向生产者端的 HTTP 端点发送请求。你可以在下方找到此类合约的示例。
Groovy
import org.springframework.cloud.contract.spec.Contract
Contract.make {
description 'Send a pong message in response to a ping message'
label 'ping_pong'
input {
// You have to provide the `triggerMessage` method with the `label`
// as a String parameter of the method
triggeredBy('triggerMessage("ping_pong")')
}
outputMessage {
sentTo('output')
body([
message: 'pong'
])
}
metadata(
[amqp:
[
outputMessage: [
connectToBroker: [
declareQueueWithName: "queue"
],
messageProperties: [
receivedRoutingKey: '#'
]
]
]
])
}
YAML
description: 'Send a pong message in response to a ping message'
label: 'ping_pong'
input:
# You have to provide the `triggerMessage` method with the `label`
# as a String parameter of the method
triggeredBy: 'triggerMessage("ping_pong")'
outputMessage:
sentTo: 'output'
body:
message: 'pong'
metadata:
amqp:
outputMessage:
connectToBroker:
declareQueueWithName: "queue"
messageProperties:
receivedRoutingKey: '#'
用于触发消息的 HTTP 端点
为什么需要开发这样一个端点?Spring Cloud Contract 将不得不生成多种语言的代码(就像它在 Java 中那样),以便能够触发向代理发送消息的生产代码。如果这样的代码没有生成,那么我们仍然需要能够触发消息,而实现这一点的方法是提供一个 HTTP 端点,用户将用他们选择的语言来准备这个端点。
端点必须具有以下配置:
-
URL:
/springcloudcontract/{label},其中label可以是任意文本 -
方法:
POST -
基于
label将生成一条消息,该消息将根据合约定义发送到指定目的地
以下是一个此类端点的示例。如果您有兴趣提供您所用语言的示例,请随时在 Github 上的 Spring Cloud Contract 仓库 中提交 issue。
#!/usr/bin/env python
from flask import Flask
from flask import jsonify
import pika
import os
app = Flask(__name__)
# Production code that sends a message to RabbitMQ
def send_message(cmd):
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.basic_publish(
exchange='output',
routing_key='#',
body=cmd,
properties=pika.BasicProperties(
delivery_mode=2, # make message persistent
))
connection.close()
return " [x] Sent via Rabbit: %s" % cmd
# This should be ran in tests (shouldn't be publicly available)
if 'CONTRACT_TEST' in os.environ:
@app.route('/springcloudcontract/<label>', methods=['POST'])
def springcloudcontract(label):
if label == "ping_pong":
return send_message('{"message":"pong"}')
else:
raise ValueError('No such label expected.')
在生产者端运行消息测试
现在,让我们从契约生成测试来测试生产者端。我们将运行 bash 代码来启动带有附加契约的 Docker 镜像,但也会为消息传递代码添加变量以使其正常工作。在这种情况下,我们假设契约存储在 Git 仓库中。
#!/bin/bash
set -x
CURRENT_DIR="$( pwd )"
export SC_CONTRACT_DOCKER_VERSION="${SC_CONTRACT_DOCKER_VERSION:-4.0.1-SNAPSHOT}"
export APP_IP="$( ./whats_my_ip.sh )"
export APP_PORT="${APP_PORT:-8000}"
export APPLICATION_BASE_URL="http://${APP_IP}:${APP_PORT}"
export PROJECT_GROUP="${PROJECT_GROUP:-group}"
export PROJECT_NAME="${PROJECT_NAME:-application}"
export PROJECT_VERSION="${PROJECT_VERSION:-0.0.1-SNAPSHOT}"
export PRODUCER_STUBS_CLASSIFIER="${PRODUCER_STUBS_CLASSIFIER:-stubs}"
export FAIL_ON_NO_CONTRACTS="${FAIL_ON_NO_CONTRACTS:-false}"
# In our Python app we want to enable the HTTP endpoint
export CONTRACT_TEST="true"
# In the Verifier docker container we want to add support for RabbitMQ
export MESSAGING_TYPE="rabbit"
# Let's start the infrastructure (e.g. via Docker Compose)
yes | docker-compose kill || echo "Nothing running"
docker-compose up -d
echo "SC Contract Version [${SC_CONTRACT_DOCKER_VERSION}]"
echo "Application URL [${APPLICATION_BASE_URL}]"
echo "Project Version [${PROJECT_VERSION}]"
# Let's run python app
gunicorn -w 4 --bind 0.0.0.0 main:app &
APP_PID=$!
# Generate and run tests
docker run --rm \
--name verifier \
# For the image to find the RabbitMQ running in another container
-e "SPRING_RABBITMQ_ADDRESSES=${APP_IP}:5672" \
# We need to tell the container what messaging middleware we will use
-e "MESSAGING_TYPE=${MESSAGING_TYPE}" \
-e "PUBLISH_STUBS_TO_SCM=false" \
-e "PUBLISH_ARTIFACTS=false" \
-e "APPLICATION_BASE_URL=${APPLICATION_BASE_URL}" \
-e "PROJECT_NAME=${PROJECT_NAME}" \
-e "PROJECT_GROUP=${PROJECT_GROUP}" \
-e "PROJECT_VERSION=${PROJECT_VERSION}" \
-e "EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL=git://https://github.com/marcingrzejszczak/cdct_python_contracts.git" \
-e "EXTERNAL_CONTRACTS_ARTIFACT_ID=${PROJECT_NAME}" \
-e "EXTERNAL_CONTRACTS_GROUP_ID=${PROJECT_GROUP}" \
-e "EXTERNAL_CONTRACTS_VERSION=${PROJECT_VERSION}" \
-v "${CURRENT_DIR}/build/spring-cloud-contract/output:/spring-cloud-contract-output/" \
springcloud/spring-cloud-contract:"${SC_CONTRACT_DOCKER_VERSION}"
kill $APP_PID
yes | docker-compose kill
将会发生的是:
-
测试将基于从Git获取的契约生成
-
在契约中,我们提供了一个名为
declareQueueWithName的元数据条目,该条目将在发送触发消息的请求之前,在RabbitMQ中创建一个具有指定名称的队列 -
通过
triggerMessage("ping_pong")方法调用,将向Python应用的/springcloudcontract/ping_pong端点发送一个POST请求 -
Python应用将通过RabbitMQ向名为
output的交换机生成并发送一个'{"message":"pong"}'JSON消息 -
生成的测试将轮询发送到
output交换机的消息 -
一旦接收到消息,将对其内容进行断言
测试通过后,我们知道消息已成功从Python应用程序发送到RabbitMQ。
在消费者端运行存根
本节介绍如何在消费端使用 Docker 来获取并运行存根。
我们发布了一个 spring-cloud/spring-cloud-contract-stub-runner Docker 镜像,用于启动 Stub Runner 的独立版本。
安全
由于 Spring Cloud Contract Stub Runner Docker 镜像使用了 Stub Runner 的独立版本,因此需要考虑相同的安全因素。你可以在文档的这一部分了解更多相关信息。
环境变量
你可以运行Docker镜像,并将JUnit和Spring的通用属性作为环境变量传递。约定是:所有字母应大写,点号(.)应替换为下划线(_)。例如,spring.cloud.contract.stubrunner.repositoryRoot属性应表示为SPRING_CLOUD_CONTRACT_STUBRUNNER_REPOSITORY_ROOT环境变量。
除了这些变量,你还可以设置以下变量:
-
MESSAGING_TYPE- 您正在使用哪种类型的消息系统(当前支持rabbit、kafka) -
ADDITIONAL_OPTS- 您希望传递给应用程序的任何其他属性
使用示例
我们希望使用在[docker-server-side]步骤中创建的存根。假设我们希望在端口 9876 上运行这些存根。您可以通过克隆仓库并切换到以下命令中指定的目录来查看 NodeJS 代码:
$ git clone https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs
$ cd bookstore
现在我们可以通过运行以下命令来运行带有存根的 Stub Runner Boot 应用程序:
# Provide the Spring Cloud Contract Docker version
$ SC_CONTRACT_DOCKER_VERSION="..."
# The IP at which the app is running and Docker container can reach it
$ APP_IP="192.168.0.100"
# Spring Cloud Contract Stub Runner properties
$ SPRING_CLOUD_CONTRACT_STUBRUNNER_PORT="8083"
# Stub coordinates 'groupId:artifactId:version:classifier:port'
$ SPRING_CLOUD_CONTRACT_STUBRUNNER_IDS="com.example:bookstore:0.0.1.RELEASE:stubs:9876"
$ SPRING_CLOUD_CONTRACT_STUBRUNNER_REPOSITORY_ROOT="http://${APP_IP}:8081/artifactory/libs-release-local"
# Run the docker with Stub Runner Boot
$ docker run --rm \
-e "SPRING_CLOUD_CONTRACT_STUBRUNNER_IDS=${STUBRUNNER_IDS}" \
-e "SPRING_CLOUD_CONTRACT_STUBRUNNER_REPOSITORY_ROOT=${STUBRUNNER_REPOSITORY_ROOT}" \
-e "SPRING_CLOUD_CONTRACT_STUBRUNNER_STUBS_MODE=REMOTE" \
-p "${STUBRUNNER_PORT}:${STUBRUNNER_PORT}" \
-p "9876:9876" \
springcloud/spring-cloud-contract-stub-runner:"${SC_CONTRACT_DOCKER_VERSION}"
当上述命令运行时,
-
启动一个独立的 Stub Runner 应用程序。
-
它在端口
9876上下载坐标为com.example:bookstore:0.0.1.RELEASE:stubs的存根。 -
它从运行在
[192.168.0.100:8081/artifactory/libs-release-local](http://192.168.0.100:8081/artifactory/libs-release-local)的 Artifactory 下载。 -
一段时间后,Stub Runner 在端口
8083上运行。 -
存根在端口
9876上运行。
在服务器端,我们构建了一个有状态的存根。我们可以使用 curl 来验证存根是否设置正确。为此,请运行以下命令:
# let's run the first request (no response is returned)
$ curl -H "Content-Type:application/json" -X POST --data '{ "title" : "Title", "genre" : "Genre", "description" : "Description", "author" : "Author", "publisher" : "Publisher", "pages" : 100, "image_url" : "https://d213dhlpdb53mu.cloudfront.net/assets/pivotal-square-logo-41418bd391196c3022f3cd9f3959b3f6d7764c47873d858583384e759c7db435.svg", "buy_url" : "https://pivotal.io" }' http://localhost:9876/api/books
# Now time for the second request
$ curl -X GET http://localhost:9876/api/books
# You will receive contents of the JSON
如果你想在主机上使用本地构建的存根,你应该设置环境变量 -e STUBRUNNER_STUBS_MODE=LOCAL 并挂载本地 m2 目录的卷 (-v "${HOME}/.m2/:/home/scc/.m2:rw")。
消息传递使用示例
为了使消息传递正常工作,只需传递值为 kafka 或 rabbit 的 MESSAGING_TYPE 环境变量即可。这将引导设置 Stub Runner Boot Docker 镜像,并包含连接到消息代理所需的依赖项。
要设置连接属性,您可以查看 Spring Cloud Stream 属性页面来设置适当的环境变量。
-
- 您可以搜索
spring.rabbitmq.xxx或spring.kafka.xxx属性
- 您可以搜索
最常设置的属性是运行中间件的位置。如果设置该属性的属性名为 spring.rabbitmq.addresses 或 spring.kafka.bootstrap-servers,那么你应该分别将环境变量命名为 SPRING_RABBITMQ_ADDRESSES 和 SPRING_KAFKA_BOOTSTRAP_SERVERS。
对现有中间件运行合约测试
有正当理由针对现有中间件运行合约测试。某些测试框架可能会给出误报结果——构建中的测试通过,而在生产环境中通信却失败。
在Spring Cloud Contract的Docker镜像中,我们提供了连接到现有中间件的选项。如前面小节所述,我们已默认支持Kafka和RabbitMQ。然而,通过Apache Camel组件,我们也可以支持其他中间件。让我们来看以下使用示例。
Spring Cloud Contract Docker 与运行中间件
为了连接到任意中间件,我们将利用合约部分中的 standalone 元数据条目。
description: 'Send a pong message in response to a ping message'
label: 'standalone_ping_pong' // <1>
input:
triggeredBy: 'triggerMessage("ping_pong")' // <2>
outputMessage:
sentTo: 'rabbitmq:output' // <3>
body: // <4>
message: 'pong'
metadata:
standalone: // <5>
setup: // <6>
options: rabbitmq:output?queue=output&routingKey=// <7>
outputMessage: // <8>
additionalOptions: routingKey=#&queue=output // <9>
我们将通过 Stub Runner 触发消息时使用的标签
与之前的消息传递示例一样,我们需要触发运行中应用程序的 HTTP 端点,使其根据提供的协议发送消息
Apache Camel 所需的
protocol:destination格式输出消息体
独立的元数据条目
设置部分将包含有关在实际调用运行中应用程序的 HTTP 端点之前如何准备运行契约测试的信息
在设置阶段要调用的 Apache Camel URI。在这种情况下,我们将尝试轮询
output交换中的消息,由于设置了queue=output和routingKey=,将创建一个名为output的队列,并使用路由键绑定到output交换要附加到第 (3) 点中
protocol:destination的附加选项(更多技术性选项) - 将按以下格式组合:rabbitmq:output?routingKey=#&queue=output。
为了让合约测试通过,我们通常需要在多语言环境的消息传递场景中运行一个应用程序和中间件。这次,我们将为Spring Cloud Contract Docker镜像设置不同的环境变量。
#!/bin/bash
set -x
# Setup
# Run the middleware
docker-compose up -d rabbitmq // <1>
# Run the python application
gunicorn -w 4 --bind 0.0.0.0 main:app & // <2>
APP_PID=$!
docker run --rm \
--name verifier \
-e "STANDALONE_PROTOCOL=rabbitmq" \ // <3>
-e "CAMEL_COMPONENT_RABBITMQ_ADDRESSES=172.18.0.1:5672" \ // <4>
-e "PUBLISH_STUBS_TO_SCM=false" \
-e "PUBLISH_ARTIFACTS=false" \
-e "APPLICATION_BASE_URL=172.18.0.1" \
-e "PROJECT_NAME=application" \
-e "PROJECT_GROUP=group" \
-e "EXTERNAL_CONTRACTS_ARTIFACT_ID=application" \
-e "EXTERNAL_CONTRACTS_GROUP_ID=group" \
-e "EXTERNAL_CONTRACTS_VERSION=0.0.1-SNAPSHOT" \
-v "${CURRENT_DIR}/build/spring-cloud-contract/output:/spring-cloud-contract-output/" \
springcloud/spring-cloud-contract:"${SC_CONTRACT_DOCKER_VERSION}"
# Teardown
kill $APP_PID
yes | docker-compose kill
我们需要先启动中间件
应用程序需要启动并运行
通过
STANDALONE_PROTOCOL环境变量,我们将获取一个 Apache Camel 组件。我们将获取的构件是org.apache.camel.springboot:camel-${STANDALONE_PROTOCOL}-starter。换句话说,STANDALONE_PROTOCOL与 Camel 的组件相匹配。我们通过 Camel 的 Spring Boot Starter 机制设置地址(我们也可以设置凭据)。例如 Apache Camel 的 RabbitMQ Spring Boot 自动配置
Stub Runner Docker 与运行中间件
为了触发针对运行中中间件的存根消息,我们可以通过以下方式运行 Stub Runner Docker 镜像。
使用示例
$ docker run \
-e "CAMEL_COMPONENT_RABBITMQ_ADDRESSES=172.18.0.1:5672" \ // <1>
-e "SPRING_CLOUD_CONTRACT_STUBRUNNER_IDS=group:application:0.0.1-SNAPSHOT" \ // <2>
-e "SPRING_CLOUD_CONTRACT_STUBRUNNER_REPOSITORY_ROOT=git://https://github.com/marcingrzejszczak/cdct_python_contracts.git" \ // <3>
-e ADDITIONAL_OPTS="--thin.properties.dependencies.rabbitmq=org.apache.camel.springboot:camel-rabbitmq-starter:3.4.0" \ // <4>
-e "SPRING_CLOUD_CONTRACT_STUBRUNNER_STUBS_MODE=REMOTE" \ // <5>
-v "${HOME}/.m2/:/home/scc/.m2:rw" \ // <6>
-p 8750:8750 \ // <7>
springcloud/spring-cloud-contract-stub-runner:3.0.4-SNAPSHOT // <8>
我们通过 Apache Camel 的 Spring Boot 自动配置 注入 RabbitMQ 的地址
我们告诉 Stub Runner 要下载哪些存根
我们为存根提供外部位置(Git 仓库)
通过
ADDITIONAL_OPTS=--thin.properties.dependencies.XXX=GROUP:ARTIFACT:VERSION属性,我们告诉 Stub Runner 在运行时获取哪些额外的依赖项。在本例中,我们想要获取camel-rabbitmq-starter,因此XXX是一个随机字符串,我们想要获取版本为3.4.0的org.apache.camel.springboot:camel-rabbitmq-starter工件。由于我们使用 Git,需要设置获取存根的远程选项
为了加速 Stub Runner 的启动,我们将本地 Maven 仓库
.m2作为卷挂载。如果您的仓库尚未填充,可以考虑通过:rw设置写入权限,而不是只读:ro。我们暴露 Stub Runner 运行的端口
8750。Stub Runner Docker 镜像的坐标。
稍后,你会在控制台中看到以下文本,这意味着 Stub Runner 已准备好接收请求。
o.a.c.impl.engine.AbstractCamelContext : Apache Camel 3.4.3 (camel-1) started in 0.007 seconds
o.s.c.c.s.server.StubRunnerBoot : Started StubRunnerBoot in 14.483 seconds (JVM running for 18.666)
o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
要获取触发器列表,你可以向 localhost:8750/triggers 端点发送 HTTP GET 请求。要触发存根消息,你可以向 localhost:8750/triggers/standalone_ping_pong 发送 HTTP POST 请求。在控制台中你将看到:
o.s.c.c.v.m.camel.CamelStubMessages : Will send a message to URI [rabbitmq:output?routingKey=#&queue=output]
如果你查看 RabbitMQ 管理控制台,你会看到 output 队列中有 1 条消息可用。