私が歌川です

@utgwkk が書いている

DynamoDB localのDescribeTable APIのレスポンスを補完するプロキシを書いた

blog.utgw.net

DynamoDB localのDescribeTable APIのレスポンスを補完することで、Terraform経由でDynamoDB localのテーブルを作成できるようにパッチするためのプロキシを書きました。

github.com

使い方

Docker Compose経由で使う

↓こういう感じで、お手元の docker-compose.yml*1に書き足しつつ、endpoint-urlhttp://localhost:8888 に向けるだけで使えます。

services:
  dynamodb-local:
    command: -jar DynamoDBLocal.jar -sharedDb -dbPath ./data
    image: amazon/dynamodb-local
    ports:
      - "8000:8000"
    volumes:
      - "./docker/dynamodb:/home/dynamodblocal/data"
    working_dir: /home/dynamodblocal
    networks:
      - dynamodb
  dynamodb-local-proxy:
    image: ghcr.io/utgwkk/dynamodb-local-proxy
    environment:
      - DYNAMODB_LOCAL_ADDR=dynamodb-local:8000
      - HOST=0.0.0.0
      - PORT=8888
    ports:
      - "8888:8888"
    networks:
      - dynamodb

networks:
  dynamodb:
    driver: bridge

GitHub Actionsで使う

GitHub Actions runner上でDocker Composeの bridge ネットワークモードを使うと、DynamoDB localに対するAPIコールが詰まってタイムアウトする問題があります。GitHub Actions workflowの services フィールドを使って起動することをおすすめします。

on:
  push:
    branches:
      - main
  pull_request:
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      dynamodb-local:
        image: amazon/dynamodb-local
      dynamodb-local-proxy:
        image: ghcr.io/utgwkk/dynamodb-local-proxy
        env:
          DYNAMODB_LOCAL_ADDR: dynamodb-local:8000
          HOST: 0.0.0.0
          PORT: 8888
        ports:
          - 8888:8888
    steps:
      - run: aws --endpoint-url=http://localhost:8888 dynamodb list-tables

おわりに

どうぞご利用ください。こういうプロキシを書く経験はそんなにないので変なコードがあったらどしどし直してください。

DynamoDB localが修正されたらめでたくお役御免になります。

余談 (実装テクニック)

せっかくなので実装テクニックを書いておきます。

HTTPレスポンスをシリアライズし、モックサーバーのレスポンスとして再利用

net/http/httputilパッケージの DumpResponse 関数を使うことで、以下のようなテキストが得られます。まさにHTTP/1.1のレスポンスという感じで都合がいいですね。

HTTP/1.1 400 Bad Request
Content-Length: 128
Content-Type: application/x-amz-json-1.0
Date: Mon, 30 Mar 2026 11:45:44 GMT
Server: Jetty(12.0.31)
X-Amzn-Requestid: 3b2e4c07-1565-4350-9017-8c67b57dc82d

{"__type":"com.amazonaws.dynamodb.v20120810#ResourceNotFoundException","Message":"Cannot do operations on a non-existent table"}

さて、これをデシリアライズする (*http.Response に復元する) にはどうすればいいか? これはnet/httpパッケージの ReadResponse 関数を使うことで実現できます。あとは得られたレスポンスを返すようにnet/http/httptestServer を作ってやればよいです。簡単ですね。

……ということがテストコードに全部書いてあります。

github.com

graceful shutdown

github.com/ne-sachirou/go-gracefulを使って実現しています。ありものを使うことで「正しいgraceful shutdownができているか」の議論を回避することを狙っています。

c4se.hatenablog.com

ログにリクエストIDを入れる

リクエストを一気通貫してログを眺めたいとき、ログにリクエストを一意に識別するIDが入っていると何かと便利です。

リクエストIDは適当に生成したらよいでしょう。UUID v6とかにしておきます。

現代ではlog/slogを使って構造化ログを吐いたらいいので、あとはcontextの中身をログに出せそうなライブラリを探します。ちょうどそのような記事を読んでおり、github.com/PumpkinSeed/slog-contextを採用できました。

blog.arthur1.dev

github.com

リクエストをcopy as curlできるようにする

github.com/thinkgos/httpcurlを使うと *http.Request をcurlコマンド形式の文字列に変換できます。デバッグなど、何かとcurl経由で気軽に叩きたいことが多いので便利に使っています。

https://github.com/utgwkk/dynamodb-local-proxy/blob/c01a878031937002da00de5a1bc0c92fd70230b9/internal/handler/handler.go#L59-L66

スタックトレースのあるerror

エラーにスタックトレースがないと暮らしていけないと思っているので、そういうライブラリを探しておきます。github.com/cockroachdb/errorsには WithStack 関数があり、エラーにスタックトレースを乗せられるのでちょうどよいでしょう。

環境変数から設定値を注入する

github.com/caarlos0/envを使っています。いつの間にかジェネリクス対応しているようでした。

github.com

JSONの一部のフィールドだけ書き換えてシリアライズする

2026/3/31 1:03 追記: 急に github.com/itchyny/gojq のことを思い出したので書き直しました。jqでフィールドの書き換えができるのでGoで書くよりは取り回しがよさそう、可読性がいいかどうかは諸説ある??

github.com

ここだけ実装テクというよりは泥臭い仕草って感じなんですが、JSONの構造をふわっと定めつつ関心のある一部のフィールドだけ書き換えるために map[string]any 型を使っています。配列は any[] 型にデシリアライズされるのでキャストしてアクセスしたらよい、ということをついさっき知りました。forループに渡すスライスのキャストがノーガードなので、変なレスポンスが返ってきたら壊れると思います。

https://github.com/utgwkk/dynamodb-local-proxy/blob/c01a878031937002da00de5a1bc0c92fd70230b9/internal/handler/handler.go#L93-L129

E2Eテスト

実際にterraform-provider-awsを使ってDynamoDB localにテーブルを作成できるE2Eテストを用意しました。

……ぐらいで終わったらよかったんですが、GitHub Actions上で terraform plan がタイムアウトして一生終わらないのでひたすらデバッグとか、GSIがない場合のハンドリングが漏れていたので直すとか、それはもう色々なことがありました。GSIの配列を返すフィールドがあったら空配列が返ってくると思うじゃん……。

何はともあれ真に実用できるようになってきたので、E2Eテスト書いてよかった〜

github.com

*1:最近は docker-compose.yml じゃなくて compose.yaml が使われていそう

時間のないサイト運営者リング