AmazonQ 使ってみたのでGithub Copilotと比較

# はじめに
会社でAmazonQライセンスを貰えることになったので、Github Copilotと比較しつつ使い心地を知りたい筆者の記事です

## この記事想定読者
- VSCodeからAmazonQが使えるよう状態になっている
 ※ AmazonQのライセンスの申請手順は省略します
  
- AmazonQが使えるようになったけど、どのようなことが出来るのか分からない

- GithubCopilotとの違いや、使い分け方
 
## 今回の題材
ちょっとしたチャットアプリを作る仕事があったので、今回の記事の題材にします
今回はAWSに主眼をおいた記事にしたいので、フロントエンドはまた別の記事で

### 使用するAWSサービス
- DynamoDB
- Lambda
- API Gateway
- S3（今回触れません）
- Bedrock（今回触れません）

# 比較開始～AmazonQ vs Github Copilotの熱い戦い～
AmazonQ/GithubCopilotへの指示は、以下の緑色の部分に書きます

:::note info
ここにプロンプトを書く
:::


## DynamoDBを作る
どういう挙動の差があるのか知りたいので、まずはざっくりな指示でいきます

:::note info
チャット履歴を格納するDynamoDBを作成する
:::

のちほど、それぞれの出力したコードの差異を整理するので、コードは読み飛ばしていただいても大丈夫です。

- AmazonQ
```ts:dynamodb_amazonq.ts
import { DynamoDBClient, CreateTableCommand } from "@aws-sdk/client-dynamodb";

const client = new DynamoDBClient({ region: "us-east-1" });

const createTable = async () => {
  const command = new CreateTableCommand({
    TableName: "ChatHistory",
    KeySchema: [
      {
        AttributeName: "id",
        KeyType: "HASH",
      },
    ],
    AttributeDefinitions: [
      {
        AttributeName: "id",
        AttributeType: "S",
      },
    ],
    ProvisionedThroughput: {
      ReadCapacityUnits: 1,
      WriteCapacityUnits: 1,
    },
  });

  try {
    const response = await client.send(command);
    console.log(response);
  } catch (err) {
    console.error(err);
  }
};

createTable();
```

- Github Copilot
```ts:dynamodb_copilot.ts
import * as cdk from '@aws-cdk/core';
import * as dynamodb from '@aws-cdk/aws-dynamodb';
export class DynamoDBStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const table = new dynamodb.Table(this, 'ChatHistory', {
      tableName: 'ChatHistory',
      partitionKey: {
        name: 'SessionId',
        type: dynamodb.AttributeType.STRING,
      },
      sortKey: {
        name: 'Timestamp',
        type: dynamodb.AttributeType.STRING,
      },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
    });

    table.addGlobalSecondaryIndex({
      indexName: 'UserId-Timestamp-index',
      partitionKey: {
        name: 'UserId',
        type: dynamodb.AttributeType.STRING,
      },
      sortKey: {
        name: 'Timestamp',
        type: dynamodb.AttributeType.STRING,
      },
      projectionType: dynamodb.ProjectionType.ALL,
    });
  }
}
```

### 比較

| 項目               | Amazon Q                              | GitHub Copilot                   |
|--------------------|-----------------------------------|-----------------------------|
| ツール             | AWS SDK                           | AWS CDK                           
| テーブル設計       | 単純なテーブル構造のみ         　   | GSI、ソートキーなどもある

さっそく結構な差が発生してます
構築ツールの指定を失念していたので、次のAPI Gateway作成では、CDK決め打ちでいきます

## API Gateway、Lambdaを作る

:::note info
チャット履歴を格納するためにのDynamoDBがあります
API GatewayにPOSTリクエストを送信して、チャット履歴をDynamoDBに格納する処理を実行します
API GatewayとLambdaをCDKで書いてください
そのURLを、Reactのコードで取得し、表示するようにしてください
:::

- AmazonQ
```ts:apigateway_lambda_amazonq.ts
import * as cdk from "@aws-cdk/core";
import * as dynamodb from "@aws-cdk/aws-dynamodb";
import * as lambda from "@aws-cdk/aws-lambda";
import * as apigateway from "@aws-cdk/aws-apigateway";

export class ChatHistoryStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const table = new dynamodb.Table(this, "ChatHistory", {
      partitionKey: { name: "id", type: dynamodb.AttributeType.STRING },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
    });

    const lambdaFunction = new lambda.Function(this, "ChatHistoryFunction", {
      runtime: lambda.Runtime.NODEJS_14_X,
      code: lambda.Code.fromAsset("path/to/lambda/code"),
      handler: "index.handler",
      environment: {
        TABLE_NAME: table.tableName,
      },
    });

    table.grantReadWriteData(lambdaFunction);

    const api = new apigateway.RestApi(this, "ChatHistoryAPI", {
      restApiName: "Chat History API",
      description: "This service stores chat history.",
    });

    const chatHistoryIntegration = new apigateway.LambdaIntegration(
      lambdaFunction,
      {
        requestTemplates: { "application/json": '{ "statusCode": "200" }' },
      }
    );

    api.root.addMethod("POST", chatHistoryIntegration);
  }
}
```

- Github Copilot

```ts:apigateway_lambda_copilot.ts
// 以下のコードは、Lambda関数を作成するためのコードです
// このコードを元に、Lambda関数を作成してください
const chatHistoryTable = new dynamodb.Table(this, 'ChatHistoryTable', {
  partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
  sortKey: { name: 'createdAt', type: dynamodb.AttributeType.STRING },
  billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
});

const lambda = new lambda.Function(this, 'ChatHistoryLambda', {
  runtime: lambda.Runtime.NODEJS_14_X,
  handler: 'index.handler',
  code: lambda.Code.fromAsset(path.join(__dirname, 'lambda')),
  environment: {
    TABLE_NAME: chatHistoryTable.tableName,
  },
});

chatHistoryTable.grantReadWriteData(lambda);

const api = new apigateway.RestApi(this, 'ChatHistoryApi', {
  restApiName: 'ChatHistoryApi',
});

const integration = new apigateway.LambdaIntegration(lambda);

api.root.addMethod('POST', integration);
```

### 比較

両者に大差はないですが、一部異なる点があります
また、フロントエンド統合時にはCORS対応が必要になることが多いですが、CORSの設定はどちらも支援してくれませんでした

| 項目               | Amazon Q                              | GitHub Copilot                      |
|--------------------|---------------------------------------|-------------------------------------|
| **全体構成**         | DynamoDB、Lambda、API Gatewayの基本構成は実現 | DynamoDB、Lambda、API Gatewayの基本構成は実現 |
| **CORS設定**       | 設定されていない               | 設定されていない                   |
| **DynamoDB**   | `id`のみ                        　| `id`+`createdAt`ソートキーあり　   |
| **API Gateway**| 基本的なPOSTメソッドを実装        　| 基本的なPOSTメソッドを実装          |
| **Lambda**     |Lambdaコードパスを静的に指定      　 |Lambdaコードパスを相対パスで動的に計算

Lambdaについて具体的にピックアップすると、以下のような差異があります。

- AmazonQ
```
code: lambda.Code.fromAsset("path/to/lambda/code"),
```

- GithubCopilot
```
code: lambda.Code.fromAsset(path.join(__dirname, 'lambda')),
```

AmazonQでの実装では、パスをハードコードしているのに対して、GithubCopilotでは実行場所に依存しない実装です
今回は、指示をかなりアバウトなので少し難癖をつけているようですが、GithubCopilotの提案は有難いですね

# まとめ
この記事では、Amazon QとGitHub Copilotを比較し、AWS構築におけるコード生成の違いを確認しました
どちらも現時点で実用に耐えうる性能だと感じますが、少ない事例からですが少し異なる印象がありました

- Amazon Q
AWSサービスに特化したシンプルなコード生成が得意

- GitHub Copilot
Amazon Qと同じ指示でも、より柔軟で実践的な構成を作成可能
一方で、初心者には少し複雑な実装である可能性がある

今回の検証では、指示がアバウトであったのもあり、明確にどちらが優れているか・選ぶべきかは明言できません。
ですが、初心者がすぐにAWSサービスを使いたい場合はAmazon Q 、より実践的なケースではGitHub Copilot が最適と言えそうです