AWSCDK基礎(chǔ)結(jié)構(gòu)作為抽象數(shù)據(jù)類型,第三部分
在我們CDK系列的第三部分,項目3,在相同的信息庫,將用來說明一些先進的夸AWS集成特性,連同幾個技巧特定的休息,眾所周知,紅帽實現(xiàn)雅加達休息規(guī)范。
讓我們首先查看項目的pom.xml文件,它驅(qū)動了Maven構(gòu)建進程。您將看到以下依賴項:
...
io.quarkiverse.amazonservices
quarkus-amazon-s3
io.quarkus
quarkus-amazon-lambda-http
io.quarkus
quarkus-rest-jackson
io.quarkus
quarkus-rest-client
...
software.amazon.awssdk
netty-nio-client
software.amazon.awssdk
url-connection-client
...
上面列表中的第一個依賴項,夸克斯-亞馬遜-s3是一個Quarkus擴展,允許您的代碼作為AWS S3客戶端,并在桶中存儲和刪除對象,或?qū)崿F(xiàn)備份和恢復(fù)策略,歸檔數(shù)據(jù)等。
下一個依賴項,夸克斯-亞馬遜-lambda-http,是另一個旨在支持AWS HTTP網(wǎng)關(guān)API的夸克斯擴展。正如讀者已經(jīng)從本系列的前兩個部分中所知道的那樣,使用Quarkus,您可以使用AWS HTTP網(wǎng)關(guān)API或AWS REST網(wǎng)關(guān)API將REST API部署為AWS Lambda。這里我們將使用前一個,不那么擴展,因此提到的擴展。如果我們想使用AWS REST網(wǎng)關(guān)API,那么我們將不得不將夸克-亞馬遜-http擴展替換為夸克-亞馬遜-http擴展。
期待什么
在這個項目中,我們將使用Quarkus 3.11,在撰寫本文時,它是最近的版本。與以前的版本相比,一些RESTeasy依賴已經(jīng)發(fā)生了變化,因此依賴夸克-依賴-杰克遜取代了現(xiàn)在在3.10和以前使用的夸克-依賴依賴。此外,實現(xiàn)Eclipse MP REST客戶端規(guī)范的夸克斯-休息-客戶端擴展對于測試目的是必需的,我們將在稍后看到。最后但并非最不重要的是,需要url-連接-客戶端Quarkus擴展,因為MP REST客戶端實現(xiàn)默認使用它,因此,它必須包含在構(gòu)建過程中。
現(xiàn)在,讓我們來看看我們新的REST API。在cdk-quarkus-s3項目中打開Java類S3文件管理api,您將看到它定義了三個操作:下載文件、上傳文件和列表文件。這三個應(yīng)用程序都使用作為CDK應(yīng)用程序堆棧的一部分創(chuàng)建的相同的S3桶。
Java
@Path("/s3")
public class S3FileManagementApi
{
@Inject
S3Client s3;
@ConfigProperty(name = "bucket.name")
String bucketName;
@POST
@Path("upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@Valid FileMetadata fileMetadata) throws Exception
{
PutObjectRequest request = PutObjectRequest.builder()
.bucket(bucketName)
.key(fileMetadata.filename)
.contentType(fileMetadata.mimetype)
.build();
s3.putObject(request, RequestBody.fromFile(fileMetadata.file));
return Response.ok().status(Response.Status.CREATED).build();
}
...
}
解釋代碼
上面的代碼片段只復(fù)制了上傳文件的操作,另外兩個非常相似。通過利用Quarkus CDI,觀察S3客戶端的實例化是多么簡單,它避免了需要多個樣板代碼行。此外,我們正在使用Eclipse MP配置規(guī)范來定義目標S3桶的名稱。
我們的端點上傳文件()接受POST請求并使用MULTIPART_FORM_DATA MIME數(shù)據(jù)分為兩個不同的部分,一個用于有效負載,另一個包含要上傳的文件。端點接受類文件元數(shù)據(jù)的輸入?yún)?shù),如下圖所示:
Java
public class FileMetadata
{
@RestForm
@NotNull
public File file;
@RestForm
@PartType(MediaType.TEXT_PLAIN)
@NotEmpty
@Size(min = 3, max = 40)
public String filename;
@RestForm
@PartType(MediaType.TEXT_PLAIN)
@NotEmpty
@Size(min = 10, max = 127)
public String mimetype;
...
}
這個類是一個數(shù)據(jù)對象,將要上傳的文件及其名稱和MIME類型一起分組。它使用@RestForm RESTeasy特定注釋來處理具有多部分/表單數(shù)據(jù)的HTTP請求。jakarta.validation.constraints注釋的使用對于驗證目的也非常實用。
要回到上面的端點,它創(chuàng)建一個putobbect請求,輸入?yún)?shù)是目標桶名,一個唯一標識桶中存儲文件的鍵,在這種情況下,是文件名和關(guān)聯(lián)的MIME類型,例如文本文件的TEXT_PLAIN。一旦創(chuàng)建了輸入請求,它將通過HTTP PUT請求發(fā)送到AWS S3服務(wù)。請注意,使用RequestBody.fromFile(…)語句將要上傳的文件插入到請求體中是多么容易。
這都是作為AWSAambda函數(shù)公開的REST API的全部?,F(xiàn)在讓我們來看看在我們的CDK應(yīng)用程序的堆棧中有什么新功能:
Java
...
HttpApi httpApi = HttpApi.Builder.create(this, "HttpApiGatewayIntegration")
.defaultIntegration(HttpLambdaIntegration.Builder.create("HttpApiGatewayIntegration", function).build()).build();
httpApiGatewayUrl = httpApi.getUrl();
CfnOutput.Builder.create(this, "HttpApiGatewayUrlOutput").value(httpApi.getUrl()).build();
...
這些行已經(jīng)被添加到cdk-簡單構(gòu)造項目中的小桶構(gòu)造類中。我們希望我們在當(dāng)前堆棧中創(chuàng)建的Lambda函數(shù)位于HTTP網(wǎng)關(guān)的后面,并對其進行備份。這可能有一些好處。所以我們需要為我們的Lambda函數(shù)創(chuàng)建一個集成。
由AWS定義的集成的概念意味著為API端點提供一個后端。在HTTP網(wǎng)關(guān)的情況下,應(yīng)該為每個API網(wǎng)關(guān)的端點提供一個或多個后端。這些集成有它們自己的請求和響應(yīng),不同于API本身。有兩種集成類型:
· 其中后端是一個Lambda函數(shù);
· HTTP集成,其中后端可能是任何已部署的web應(yīng)用程序;
在我們的示例中,我們使用的是Lambda集成。還有兩種類型的Lambda集成:
· Lambda代理集成,其中不需要集成的請求和響應(yīng)的定義,以及它們到原始請求和響應(yīng)的映射,因為它們是自動提供的;
· Lambda非代理集成,其中我們需要明確地指定如何將傳入的請求數(shù)據(jù)映射到集成請求,以及如何將結(jié)果的集成響應(yīng)數(shù)據(jù)映射到方法響應(yīng);
為了簡單起見,我們在項目中使用了第一個案例。這就是上面的語句。默認集成(...)正在做的事情。一旦創(chuàng)建了集成,我們就需要顯示新創(chuàng)建的API網(wǎng)關(guān)的URL,我們的Lambda函數(shù)是備份的。通過這種方式,除了能夠像我們之前那樣直接調(diào)用Lambda函數(shù)之外,我們還可以通過API網(wǎng)關(guān)來實現(xiàn)它。在一個有幾十個REST端點的項目中,有一個單一的接觸點非常重要,在那里應(yīng)用安全策略、日志記錄、日志化和其他交叉問題。API網(wǎng)關(guān)是一個理想的單個接觸點。
該項目附帶了兩個單元測試和集成測試。例如,類S3文件管理測試使用REST保證執(zhí)行單元測試,如下圖所示:
Java
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class S3FileManagementTest
{
private static File readme = new File("./src/test/resources/README.md");
@Test
@Order(10)
public void testUploadFile()
{
given()
.contentType(MediaType.MULTIPART_FORM_DATA)
.multiPart("file", readme)
.multiPart("filename", "README.md")
.multiPart("mimetype", MediaType.TEXT_PLAIN)
.when()
.post("/s3/upload")
.then()
.statusCode(HttpStatus.SC_CREATED);
}
@Test
@Order(20)
public void testListFiles()
{
given()
.when().get("/s3/list")
.then()
.statusCode(200)
.body("size()", equalTo(1))
.body("[0].objectKey", equalTo("README.md"))
.body("[0].size", greaterThan(0));
}
@Test
@Order(30)
public void testDownloadFile() throws IOException
{
given()
.pathParam("objectKey", "README.md")
.when().get("/s3/download/{objectKey}")
.then()
.statusCode(200)
.body(equalTo(Files.readString(readme.toPath())));
}
}
這個單元測試首先將文件README.md上傳到為此目的定義的S3桶。然后它列出桶中存在的所有文件,并通過下載剛剛上傳的文件完成。請注意應(yīng)用程序。屬性文件中的以下幾行:
Plain Text
bucket.name=my-bucket-8701
%test.quarkus.s3.devservices.buckets=${bucket.name}
第一個定義目標桶的名稱,第二個定義自動創(chuàng)建目標桶。這只在通過Quarkus Mock服務(wù)器執(zhí)行時工作。當(dāng)這個單元測試是在Maven測試階段執(zhí)行的,但是針對由Quarkus自動管理的測試容器運行的本地堆棧實例,一旦部署了CDK應(yīng)用程序,集成實例,S3文件管理,是針對真正的AWS基礎(chǔ)設(shè)施執(zhí)行的。
集成測試使用了一種不同的范式,它們利用了Eclipse客戶端規(guī)范,取代了由Quarkus實現(xiàn)的EPREST客戶端規(guī)范,如下代碼片段所示:
Java
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class S3FileManagementIT
{
private static File readme = new File("./src/test/resources/README.md");
@Inject
@RestClient
S3FileManagementClient s3FileManagementTestClient;
@Inject
@ConfigProperty(name = "base_uri/mp-rest/url")
String baseURI;
@Test
@Order(40)
public void testUploadFile() throws Exception
{
Response response = s3FileManagementTestClient.uploadFile(new FileMetadata(readme, "README.md", MediaType.TEXT_PLAIN));
assertThat(response).isNotNull();
assertThat(response.getStatusInfo().toEnum()).isEqualTo(Response.Status.CREATED);
}
...
}
我們注入了S3文件管理客戶端,這是一個定義API端點的簡單接口,Quarkus也完成了剩下的工作。它將生成所需的客戶端代碼。我們只需要在這個接口上調(diào)用端點,例如上傳文件(……),僅此而已??纯碿dk-夸庫斯-s3項目中的S3文件管理客戶端,看看一切是如何工作的,請注意注釋是如何定義一個名為base_uri的配置鍵,在部署.sh腳本中進一步使用的。
現(xiàn)在,要測試AWS的真實基礎(chǔ)設(shè)施,您需要執(zhí)行deplend.sh腳本,如下所示:
Shell
$ cd cdk
$ ./deploy.sh cdk-quarkus/cdk-quarkus-api-gateway cdk-quarkus/cdk-quarkus-s3
這將編譯和構(gòu)建應(yīng)用程序,執(zhí)行單元測試,在AWS上部署Cloud形成堆棧,并針對此基礎(chǔ)設(shè)施執(zhí)行集成測試。在執(zhí)行結(jié)束時,你應(yīng)該看到類似的東西:
Plain Text
Outputs:
QuarkusApiGatewayStack.FunctionURLOutput = https://.lambda-url.eu-west-3.on.aws/
QuarkusApiGatewayStack.LambdaWithBucketConstructIdHttpApiGatewayUrlOutput = https://.execute-api.eu-west-3.amazonaws.com/
Stack ARN:
arn:aws:cloudformation:eu-west-3:...:stack/QuarkusApiGatewayStack/
現(xiàn)在,除了您在前面的示例中已經(jīng)看到的Lambda函數(shù)URL之外,您還可以看到API HTTP網(wǎng)關(guān)URL現(xiàn)在可以用于測試目的,而不是Lambda。
同時,還提供了一個從郵遞員(S3文件管理網(wǎng))導(dǎo)出的E2E測試用例。它是通過Docker圖像郵遞員/新手來執(zhí)行的:最新的,在測試容器中運行。下面是一個片段:Java
@QuarkusTest
public class S3FileManagementPostmanIT
{
...
private static GenericContainer postman = new GenericContainer<>("postman/newman")
.withNetwork(Network.newNetwork())
.withCopyFileToContainer(MountableFile.forClasspathResource("postman/AWS.postman_collection.json"),
"/etc/newman/AWS.postman_collection.json")
.withStartupCheckStrategy(new OneShotStartupCheckStrategy().withTimeout(Duration.ofSeconds(10)));
@Test
public void run()
{
String apiEndpoint = System.getenv("API_ENDPOINT");
assertThat(apiEndpoint).isNotEmpty();
postman.withCommand("run", "AWS.postman_collection.json",
"--global-var base_uri=" + apiEndpoint.substring(8).replaceAll(".$", ""));
postman.start();
LOG.info(postman.getLogs());
assertThat(postman.getCurrentContainerInfo().getState().getExitCodeLong()).isZero();
postman.stop();
}
}
結(jié)論
正如你所看到的,啟動郵遞員/新聞:最新圖像測試容器,我們運行E2E測試用例輸出從郵遞員傳遞選項全局變量標記base_uri的初始化保持API網(wǎng)址的值保存的部署.sh腳本在API端點環(huán)境變量。不幸的是,可能是由于一個錯誤,郵差/新聞人員的圖像不能識別這個選項,因此,等待這個問題被修復(fù),這個測試現(xiàn)在被禁用了。
當(dāng)然,您可以在郵差中導(dǎo)入文件AWS.postman_collection.json,并在用AWS生成的API URL的當(dāng)前值替換全局變量{{base_uri}}后以這種方式運行它。