概要
ローカル環境でEventBridgeをモックしたい。s3mockのように必要最低限の機能を提供している選択肢がなかったため、WireMockを用いて作成してみました。
(AWSの複数サービスを含んだ環境ごと擬似的に作成したい場合はLocalStackで実現できるのですが、今回はアプリケーション側の開発を進める場面を想定しています。)
やったこと
- EventBridgeクライアントを用いてputEventをするだけのデモ用Spring Bootアプリを作成
- WireMockでEventBridgeのAPI仕様を真似たマッピングを作成
- docker-composeでアプリケーションとWireMockを構築
Spring Bootアプリケーション
Spring Bootで、AWS SDKのEventBrideクライアントを用いてPutEventsするだけのアプリケーションを作りました。
基本的にAWS SDK for Java コードの例 - PutEventsを参考にしています。
以下、主要な部分だけコードを抜粋します。
PutEventする機能の実装クラス
@Component
@RequiredArgsConstructor
@Slf4j
public class EventPublisherImpl implements EventPublisher {
private final EventBridgeClient eventBridgeClient;
@Override
@Async
public void publish(Person person) {
PutEventsRequestEntry reqEntry = PutEventsRequestEntry.builder()
.source("com.github.macaroni10y.DemoForMock")
.detailType("person")
.detail(person.asJson())
.build();
List<PutEventsRequestEntry> list = new ArrayList<>();
list.add(reqEntry);
PutEventsRequest eventsRequest = PutEventsRequest.builder()
.entries(reqEntry)
.build();
log.info(person.asJson());
eventBridgeClient.putEvents(eventsRequest);
}
}
Config
EventBridgeClientはBean登録しておきました。ここで、クライアントはデフォルトでは本物のEventBridgeのエンドポイントに向けてリクエストを投げます。このままではモックができません。ローカル環境の場合のみ、EventBridgeのエンドポイントを上書きしてWimeMockでマッピングした向き先にするよう設定しました。
この記述が適切かどうかは分かりませんが、以下で実現できます。
@Configuration
public class DemoForMockConfig {
@Bean
public EventBridgeClient eventBridgeClient(@Value("${aws.event-bridge.endpoint:#{null}}") String endpoint) {
System.out.println(endpoint);
if (Objects.isNull(endpoint)) {
return EventBridgeClient.builder()
.region(Region.AP_NORTHEAST_1)
.build();
}
return EventBridgeClient.builder()
.endpointOverride(URI.create(endpoint))
.region(Region.AP_NORTHEAST_1)
.build();
}
}
環境変数にaws.event-bridge.endpoint
が設定されている場合はそのエンドポイントで上書き、無ければデフォルトのエンドポイントになります。
Wiremockマッピング
普段EventBridgeClientを使う際は意識しないかもしれませんが、API Referenceが公開されているように、結局リクエストを投げているだけです。
以下のようなmappings.json
を作成し、/home/wiremock
にマウントしてマッピング登録しました。
{
"mappings": [
{
"request": {
"url": "/",
"headers": {
"Content-Type": {
"equalTo": "application/x-amz-json-1.1"
}
},
"bodyPatterns" : [{
"matchesJsonPath" : "$.Entries"
}]
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/x-amz-json-1.1",
"x-amzn-RequestId": "1111"
},
"body": "{\"FailedEntryCount\": \"0\", \"Entries\": [{\"EventId\": \"11710aed-b79e-4468-a20b-bb3c0c3b4860\"}]}"
}
}
]
}
今回は、DetailTypeに関わらずEventBridgeのリクエスト形式ならマッチするようにしました。レスポンスの再現性に関しても、必要に応じて厳密にしていけばよいかと思います。(上の例は正常系のみですし、リクエストに応じた動的なレスポンスでもないので手抜きです。)
docker-compose
services:
application:
image: demo-for-mock/application:latest
ports:
- 8080:8080
volumes:
- ./application.yaml:/app/application.yaml
working_dir: /app
environment:
AWS_ACCESS_KEY_ID: dummy
AWS_SECRET_ACCESS_KEY: dummy
EB_ENDPOINT: http://wiremock:8080/
wiremock:
image: wiremock/wiremock:2.32.0
ports:
- 9090:8080
volumes:
- ./wiremock:/home/wiremock
command:
- --verbose
EventBridgeClientが必要とするcredentialsを環境変数に定義しています(WireMockを使うため、値はdummyにしています。また、本番環境ではハードコーディングした認証情報ではなく、IAMロールを使用します。)
--verbose
オプションで、リクエストがマッチした場合もログ出力されます。
動作確認
ログ(一部抜粋、改変)
local-application-1 | 2022-02-04 16:29:56.585 INFO 1 --- [ task-1] i.g.m.D.infra.EventPublisherImpl : {"name":"name","age":10}
local-wiremock-1 | 2022-02-04 16:29:57.488 Request received:
local-wiremock-1 | 172.30.0.2 - POST /
local-wiremock-1 |
local-wiremock-1 | Authorization: [AWS4-HMAC-SHA256 Credential=dummy/20220204/ap-northeast-1/events/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;content-length;content-type;host;x-amz-date;x-amz-target, Signature=xxx]
local-wiremock-1 | X-Amz-Date: [20220204T162956Z]
local-wiremock-1 | User-Agent: [aws-sdk-java/2.17.121 Linux/5.10.76-linuxkit OpenJDK_64-Bit_Server_VM/17-ea+14 Java/17-ea vendor/Oracle_Corporation io/sync http/Apache cfg/retry-mode/legacy]
local-wiremock-1 | Connection: [keep-alive]
local-wiremock-1 | Host: [wiremock:8080]
local-wiremock-1 | amz-sdk-invocation-id: [xxx]
local-wiremock-1 | amz-sdk-request: [attempt=1; max=4]
local-wiremock-1 | Content-Length: [124]
local-wiremock-1 | X-Amz-Target: [AWSEvents.PutEvents]
local-wiremock-1 | Content-Type: [application/x-amz-json-1.1]
local-wiremock-1 | {"Entries":[{"Source":"io.github.macaroni10y.DemoForMock","DetailType":"person","Detail":"{\"name\":\"name\",\"age\":10}"}]}
local-wiremock-1 |
local-wiremock-1 |
local-wiremock-1 | Matched response definition:
local-wiremock-1 | {
local-wiremock-1 | "status" : 200,
local-wiremock-1 | "body" : "{\"FailedEntryCount\": \"0\", \"Entries\": [{\"EventId\": \"11710aed-b79e-4468-a20b-bb3c0c3b4860\"}]}",
local-wiremock-1 | "headers" : {
local-wiremock-1 | "Content-Type" : "application/x-amz-json-1.1",
local-wiremock-1 | "x-amzn-RequestId" : "1111"
local-wiremock-1 | }
local-wiremock-1 | }
local-wiremock-1 |
local-wiremock-1 | Response:
local-wiremock-1 | HTTP/1.1 200
local-wiremock-1 | Content-Type: [application/x-amz-json-1.1]
おわりに
EventBridgeをWireMockでモックしてみました。状況によっては使いどころがあるかもしれません・・・。