🚀

「Amplify Gen 2 ワークショップ」の補足の補足(2024/04/21)

2024/04/21に公開

Amplify Gen 2 ワークショップ

Gen2になってCDKとの統合がされたらしく、いつか触ってみたいと思っていた。
Amplify自体も結構前に1回触ったきりなので知識はほぼない。

実施したワークショップのリンク

https://catalog.workshops.aws/amplify-core/ja-JP

Xで以下が流れてきたので、手を動かしてみるかーとなったところ。変更が結構あって躓く人が多そうだったのでメモ
https://qiita.com/asuka0708japan/items/4a6d757a34b3fd127799#詰まった所-2

参照の参照先も
https://zenn.dev/p0n/articles/7cfe53ccaacdda

ワークショップ実施前に確認すること

バージョンの話なので手戻りがないように

Node.jsのバージョン

ワークショップの手順をそのまま進めるとNode.jsのv20になってしまい諸々不都合なのでv18にする

nvm install 18 # v18.19.0が入った
nvm use 18
npm install

amplifyのバージョン

手順だとbetaになっているが、クイックスタートにはlatest使ってたのでこっちにした。betaにして実施してても結局今のドキュメントと関数が変わってたりしてちょくちょくひっかかったので今のドキュメントベースで調べながら変えていく方向に。
https://docs.amplify.aws/gen2/start/quickstart/vite-react-app/

npm create amplify@latest -y

ワークショップ中

データモデルのリレーションを定義

最初に引っかかったところ。以下を見て解決。
https://qiita.com/asuka0708japan/items/4a6d757a34b3fd127799#詰まった所-2

認可ルールを定義

public()がないと怒られた。あってるかどうかはわからないけど、ドキュメントを見て修正した。
specificGroup()も怒られるので同様に対処。

変更前 変更後
public() publicApiKey()
specificGroup() group()

https://docs.amplify.aws/gen2/build-a-backend/data/customize-authz/public-data-access/
https://docs.amplify.aws/gen2/build-a-backend/data/customize-authz/user-group-based-data-access/

この時点のコード
RetailStore/amplify/backend.ts
import { ClientSchema, a, defineData } from '@aws-amplify/backend';

const retailStoreSchema = a.schema({
  Product: a.model({
    id: a.id().required(),
    name: a.string().required(),
    description: a.string(),
    price: a.float(),
    current_stock: a.integer(),
    image: a.string(),
    rating: a.float(),
    style: a.string(),
    category: a.belongsTo('Category', 'id'), 
  }).authorization(allow => [
    allow.publicApiKey().to(['read']),
    allow.owner(),
    allow.group('Admin'),
  ]),
  Category: a.model({
    id: a.id().required(),
    name: a.string().required(),
    description: a.string(),
    image: a.string(),
    styles: a.string().array(),
    products: a.hasMany('Product','id'),
  }).authorization(allow => [
    allow.publicApiKey().to(['read']),
    allow.group('Admin'),
  ])
});


export type Schema = ClientSchema<typeof retailStoreSchema>;

export const data = defineData({
  schema: retailStoreSchema,
  authorizationModes: {
    defaultAuthorizationMode: 'apiKey',
    apiKeyAuthorizationMode: {
      expiresInDays: 7,
    }
  }
});

サンプルデータをロード

サンプルデータのロードを実行しようとすると、undefinedで怒られる。
undefined

とりあえずサンプルデータをアップロードしてそうな部分を探すと以下がそれっぽい。
ファイルだと141行目あたりのところ

RetailStore/src/utils/data-loader/loader.ts
// 以前省略
    // Load products
    let numProducts: number = products.length;
    if ( currProductCount !== numProducts ) {
        console.log("Loading products");
        let productCount: number = currProductCount;
        localStatus["productProgress"] = ( productCount / numProducts ) * 100;
        for (const prod of products) {
            const { errors, } = await client.models.Product.create({
                id: prod.id,
                name: prod.name,
                categoryProductsId: categoriesMap[prod.category].id,
                current_stock: prod.current_stock,
                description: prod.description,
                image: `${SOURCE_BUCKET_URL}/images/${prod.category.toLowerCase()}/${prod.image}`,
                price: prod.price,
                style: prod.style,
                rating: REVIEWS[Math.floor(Math.random() * REVIEWS.length)]
            });
            if ( errors !== undefined ) {
                console.error(errors);
                if ( errors[0].errorType !== "DynamoDB:ConditionalCheckFailedException" ) {
                    localStatus["productStatus"] = "error";
                    localStatus["productResultText"] = `Error loading products: ${errors[0].errorType}`
                    break;
                }
            } else {
                productCount += 1;
                localStatus["productProgress"] = ( productCount / numProducts ) * 100;
            }
            statusCallback(localStatus);
// 以下省略

ワークショップ上で変更したProductのデータモデルは以下なので、categoryProductsIdに差分がある。

/RetailStore/amplify/data/resource.ts
  Product: a.model({
    id: a.id().required(),
    name: a.string().required(),
    categoryProductsId: a.string(),
    description: a.string(),
    price: a.float(),
    current_stock: a.integer(),
    image: a.string(),
    rating: a.float(),
    style: a.string(),
    category: a.belongsTo('Category', 'id'), 
  }).authorization(allow => [
    allow.publicApiKey().to(['read']),
    allow.owner(),
    allow.group('Admin'),
  ]),

amplify的に正しいのかとか、そもそもこのId自体がなんなのかよくわかってないが、categoryProductsId/RetailStore/amplify/data/resource.tsに追加したら読み込めた。

/RetailStore/amplify/data/resource.ts
  Product: a.model({
    id: a.id().required(),
    name: a.string().required(),
+   categoryProductsId: a.string(),
    description: a.string(),
    price: a.float(),
    current_stock: a.integer(),
    image: a.string(),
    rating: a.float(),
    style: a.string(),
    category: a.belongsTo('Category', 'id'), 
  }).authorization(allow => [
    allow.publicApiKey().to(['read']),
    allow.owner(),
    allow.group('Admin'),
  ]),

サンプルデータから読み込まないように/RetailStore/src/utils/data-loader/loader.tsを変更してもいいのかもしれない。
画像は読み込めてないけど変更箇所ではないのでヨシ!(最後まで行った後にURLを確認したらサンプルデータには恐らくデモ用のS3バケットの署名付きではないURLになってた。直接アクセスするとAccessDeniedなので無視でいいと思う)
loaded
successed

全体のコード
RetailStore/amplify/backend.ts
import { ClientSchema, a, defineData } from '@aws-amplify/backend';

const retailStoreSchema = a.schema({
  Product: a.model({
    id: a.id().required(),
    name: a.string().required(),
    categoryProductsId: a.string(),
    description: a.string(),
    price: a.float(),
    current_stock: a.integer(),
    image: a.string(),
    rating: a.float(),
    style: a.string(),
    category: a.belongsTo('Category', 'id'), 
  }).authorization(allow => [
    allow.publicApiKey().to(['read']),
    allow.owner(),
    allow.group('Admin'),
  ]),
  Category: a.model({
    id: a.id().required(),
    name: a.string().required(),
    description: a.string(),
    image: a.string(),
    styles: a.string().array(),
    products: a.hasMany('Product','id'),
  }).authorization(allow => [
    allow.publicApiKey().to(['read']),
    allow.group('Admin'),
  ])
});


export type Schema = ClientSchema<typeof retailStoreSchema>;

export const data = defineData({
  schema: retailStoreSchema,
  authorizationModes: {
    defaultAuthorizationMode: 'apiKey',
    apiKeyAuthorizationMode: {
      expiresInDays: 7,
    }
  }
});

ワークショップ終了後

Amplify プロジェクトを削除する

マネジメントコンソールが日本語になっている都合で削除の難易度が上がった。ハイコンテキストAWSユーザ力が求められる。
invalid

deleteで削除。
delete

CloudWatch ロググループを削除する

引っかからなかったところではないけど一応。
/aws/lambda/amplify-retailstore-を削除すると記載されてるけど、/aws/lambda/amplify-でフィルターするとデプロイしたamplifyのIDのログがある。
logs

その他にも/aws/amplify/<AmplifyのID>/aws/lambda/Cloud9-Cloud9をプレフィックスに持つログがあるので任意に。

おわりに

amplifyの画面のドメインからアクセスができて、無事完走できた。
complete

complete2

引っかからなかったところ

以下のGraphqlの作成エラーは自分の環境では再現しなかった。
https://qiita.com/asuka0708japan/items/4a6d757a34b3fd127799#詰まった所-3

ワークショップ自体の感想

Typescriptなのでamplifyの操作自体は割となんとかなりそうという感触。
一方で、後半からはワークショップでありがちなコードのコピペになってあんまり中身を理解しないまま進んでしまってる感が否めない…
特にForm Builder以降が置いてかれている感があったので、誰かと一緒にワークショップやるといいんだろうなぁ

Discussion