SAMでローカル実行したLambdaからMongodbへのアクセスを試してみたメモ

## 概要
[前回はフォルダ階層を導入した。](https://qiita.com/hibohiboo/items/924aca6feed689f974d8)
今回はDocumentDBを使った操作を確認できるようにする。
なお、DocumentDBはローカル版が用意されていないのでMongoDBを利用する。
MongoDB/DocumentDBには破壊的変更のある3系と4系があるが、今回は最新版である4.0を導入する。

[ソースコード](https://github.com/hibohiboo/develop/tree/49006cd65e5d56d055399180be25441924f4f89e/tutorial/lesson/aws/typescript/projects/cdk_sample)

### やること
* mongodbの単体テスト
* nongodbのローカルでの実行
  * mongodbはdockerで動かす

### やらないこと
* デプロイ

## MongoDBの導入

* [laradock](https://laradock.io/)を参考に導入。
  * ファイルシステムがwindowsと相性悪いのでvolumesを修正（ubuntuのdockerでは修正しなくても動いた)

```yml:docker/docker-compose.yml
version: '3.5'

networks:
  backend:
    driver: ${NETWORKS_DRIVER}

volumes:
  mongodb-data: {}

services:
### MongoDB ##############################################
    mongo:
      build: ./mongo
      ports:
        - "${MONGODB_PORT}:27017"
      volumes:
        - mongodb-data:/data/db
      networks:
        - backend
```

接続の確認には、[robo3t](https://chocolatey.org/packages/robo3t)を使用した。
[公式- robo3tで接続する](https://docs.aws.amazon.com/ja_jp/documentdb/latest/developerguide/robo3t.html)
[Robo 3Tを使ってmonogoDBを確認する](https://qiita.com/Molly95554907/items/f04418f71bf65cbd60aa)


## MongoDBの利用
### ハンドラ
* [callbackWaitsForEmptyEventLoop ](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-context.html)は[falseにしておくのがベストプラクティス](https://docs.atlas.mongodb.com/best-practices-connecting-to-aws-lambda/)とあったのでそうしている。

```ts:src/lambda/handlers/users/post-user.ts
import { APIGatewayProxyHandler } from 'aws-lambda';
import * as response from '@/lambda/utilities/reponse';
import * as users from '../../users/persistant/users';

export const lambdaHandler: APIGatewayProxyHandler = async (event, context) => {
  context.callbackWaitsForEmptyEventLoop = false;
  const { body } = event;
  if (!body) {
    return response.badRequest('body is required')
  }
  try {
    const user = await users.create(JSON.parse(body))
    return response.ok(user);
  } catch (e) {
    console.error(e)
    throw e
  }
};
```

### スタック

* 環境変数を設定
  * `mongodb-client-encryption`はbundleするときに入れろという内容のエラーが出たので、言われるままに追加。

```ts:cd/lib/sample-stack.ts
    const environment = {
      DOCUMENTDB_CONNECTION_STRING: '',
      DEFAULT_DB_NAME: ''
    }
    const bundling = {
      externalModules: ['aws-sdk', 'mongodb', 'mongodb-client-encryption'],
    }
    const mongoFunctionOptions = { environment, bundling, layers: [nodeModulesLayer] }

    const postUserFunction = new NodejsFunction(this, 'postUser', {
      runtime: lambda.Runtime.NODEJS_14_X,
      entry: `${entryHandlerDir}/users/post-user.ts`,
      functionName: 'post-user',
      handler: 'lambdaHandler',
      ...mongoFunctionOptions,
    });
```


### 実行用の設定

* 関数名の下に環境変数を設定
  * 接続文字列は、`localhost`を`host.docker.internal`に置き換えている。[参照](https://docs.docker.jp/docker-for-mac/networking.html#mac-i-want-to-connect-from-a-container-to-a-service-on-the-host)
      * samはコンテナ上で動いているため、ホストのlocalhostのmongodbを見に行く必要がある

```json:cdk/env.json
{
  "postUser7BA52173": {
    "DOCUMENTDB_CONNECTION_STRING": "mongodb://host.docker.internal:27017",
    "DEFAULT_DB_NAME": "sample"
  }
}
```

* `-n (-env-vars)`[オプション](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-start-api.html)で環境変数設定ファイルを使用するように設定

```diff:cdk/package.json
    "bundle": "cdk synth > cdk.out/template.yaml",
    "localinvoke": "cd cdk.out && sam.cmd local invoke testAF53AC38 --no-event",
+    "dev-serve": "cd cdk.out && sam.cmd local start-api -n ../env.json"
  },
```



## MongoDBのテスト
もともとJestを使っていたため、`@shelf/jest-mongodb`を使用。
インメモリでテストをするためには、`mongodb-memory-server`を追加でインストールする。
インストール時のデフォルトのmongoのバージョンが4.0.14なのに注意

```js:jest-mongodb-config.js
module.exports = {
  mongodbMemoryServerOptions: {
    binary: {
      version: '4.0.14',
      skipMD5: true
    },
    instance: {
      dbName: 'jest'
    },
    autoStart: false,
    instance: {}
  }
};
```


## 実践

```http
POST http://localhost:3000/users HTTP/1.1

{
    "username": "user2",
    "age": 22
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/b0e3f75f-69f6-a1f2-b017-00f5dd3c0bd8.png)

## 注意点
* dockerでmongodbはwindowsとちょっと相性悪いぽい。
  * ファイルシステムの問題


## 疑問
`npm info mongodb versions`と`npm info @types/mongodb`を試してみた。
mongodbの3系は3.6.4が最新であり、@types/mongodbは3.6.9が最新であった。
同期してない？
なお、'4.0.0-beta.1'には型定義ファイル同梱であった。

## 参考
[jest mongodb][*1]
[github shelfio/jest-mongodb][*4]
[mongodb-memory-servere][*9]
[Node.jsからMongoDBに接続してみる][*2]
[github-mongo][*3]
[Express + MongoDBで作成したAPIサーバーをJestでテストする][*5]
[AWS DocumentDB][*6]
[AWS DynamoDB/DocumentDBをローカルで確認する手順(自分用メモ)][*7]
[Do You Need Mongoose When Developing Node.js and MongoDB Applications?][*8]
[ts-jest][*10]
[MongoDB, Mongo Express起動用docker-compose設定手順][*11]
[@aws-cdk/aws-apigateway module][*12]
['mongodb-client-encryption'][*13]
[stack over flow](https://stackoverflow.com/questions/60025680/cannot-access-local-mongodb-from-sam-local)
[linuxでhost.docker.internalを使う](https://qiita.com/skobaken/items/03a8b9d0e443745862ac)
[Dockerコンテナ内からホストへ`localhost` でアクセスしてみる](https://qiita.com/kai_kou/items/5182965ea75c85cf1e3f)
[MongoDBを扱うExpressアプリをAWS Lambdaに乗せてサーバーレスにするgit lo](https://qiita.com/nacam403/items/6207d736a9827d19ebd4)
[AWS SAMの環境変数設定方法](https://qiita.com/wwalpha/items/d28d053f5797f39bde4b)
[aws-cdk-examples](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript)
[robo3t](https://chocolatey.org/packages/robo3t)
[公式- robo3tで接続する](https://docs.aws.amazon.com/ja_jp/documentdb/latest/developerguide/robo3t.html)
[Robo 3Tを使ってmonogoDBを確認する](https://qiita.com/Molly95554907/items/f04418f71bf65cbd60aa)


[*1]:https://jestjs.io/docs/mongodb
[*2]:https://qiita.com/one-kelvin/items/c0c036f774f0b3b98ae5
[*3]:https://github.com/mongodb/node-mongodb-native/tree/4.0
[*4]:https://github.com/shelfio/jest-mongodb
[*5]:https://qiita.com/azukiazusa/items/b0365c95e6509ec57f7a
[*6]:https://docs.aws.amazon.com/ja_jp/documentdb/latest/developerguide/developerguide.pdf
[*7]:https://qiita.com/gemini66/items/7ec6e3541e3fae87f259
[*8]:https://developer.mongodb.com/article/mongoose-versus-nodejs-driver/
[*9]:https://github.com/nodkz/mongodb-memory-server#available-options
[*10]:https://kulshekhar.github.io/ts-jest/docs/
[*11]:https://denor.jp/docker-compose%E3%81%A7mongodb%E3%81%A8mongo-express%E3%82%92%E8%B5%B7%E5%8B%95%E3%81%99%E3%82%8B%E3%81%AB%E3%81%AF
[*12]:https://docs.aws.amazon.com/cdk/api/latest/docs/aws-apigateway-readme.html
[*13]:https://github.com/mongodb/libmongocrypt#readme