WireMockを使ってローカルでEventBridgeをモックする

# 概要
ローカル環境でEventBridgeをモックしたい。s3mockのように必要最低限の機能を提供している選択肢がなかったため、WireMockを用いて作成してみました。
(AWSの複数サービスを含んだ環境ごと擬似的に作成したい場合は[LocalStack](https://github.com/localstack/localstack)で実現できるのですが、今回はアプリケーション側の開発を進める場面を想定しています。)

# やったこと
- EventBridgeクライアントを用いてputEventをするだけのデモ用Spring Bootアプリを作成
- WireMockでEventBridgeのAPI仕様を真似たマッピングを作成
- docker-composeでアプリケーションとWireMockを構築

# Spring Bootアプリケーション
Spring Bootで、AWS SDKのEventBrideクライアントを用いてPutEventsするだけのアプリケーションを作りました。
基本的に[AWS SDK for Java コードの例 - PutEvents](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/eventbridge/src/main/java/com/example/eventbridge/PutEvents.java)を参考にしています。
以下、主要な部分だけコードを抜粋します。

#### PutEventする機能の実装クラス

```Java:EventPublisherImpl.java
@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でマッピングした向き先にするよう設定しました。
この記述が適切かどうかは分かりませんが、以下で実現できます。

```Java:DemoForMockConfig.java
@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](https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutEvents.html)が公開されているように、結局リクエストを投げているだけです。


以下のような`mappings.json`を作成し、`/home/wiremock`にマウントしてマッピング登録しました。

```json:mappings.json
{
  "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

```yaml:docker-compose.yaml
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`オプションで、リクエストがマッチした場合もログ出力されます。

# 動作確認

<details><summary>ログ(一部抜粋、改変)</summary><div>

```
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]

```

</div></details>

# おわりに
EventBridgeをWireMockでモックしてみました。状況によっては使いどころがあるかもしれません・・・。

# 参考
- https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/eventbridge/src/main/java/com/example/eventbridge/PutEvents.java
- https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutEvents.html