2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS AmplifyとAWS×フロントエンド #AWSAmplifyJPAdvent Calendar 2024

Day 8

Amplify Tips解説 (Custom Resolver/テスト&タグ管理)

Last updated at Posted at 2024-12-07

AWS Amplify Advent Calendar 2024 の8日目の記事です!

この記事 is 何

Amplifyを実装している中で使ったTipsを簡単にまとめる記事です。
テーマは以下の2つです。

  1. バックエンドのCustom Resolverの実装&テスト
  2. Amplify生成リソースのタグ管理

1. バックエンドのCustom Resolverの実装&テスト

Amplify Gen2からバックエンドのAPIのCustom Resolverを簡単に実装できるようになりました。
バックエンドのAPIにバリデーションなどの実装を追加する場合にはCustom Resolverを利用します。

参考:
https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/#step-2---configure-custom-business-logic-handler-code

例えば、郵便番号を受け取って保存するAPIを想定したときに、リクエストされるデータは「半角数字7桁」であることを強制したりするときに利用します。

実装方法

使い方は簡単で、backendのスキーマにCustom Resolverとなるjsのコードを参照するような実装を追記します。

resource.ts
const schema = a.schema({
  Todo: a
    .model({
      content: a.string(),
    })
    .authorization((allow) => [allow.publicApiKey()]),
  createNumber7Todo: a // 7桁の10進数を表す文字列を受け取るCustom Resolverをエンドポイントに紐づける
    .mutation()
    .arguments({
      content: a.string(),
    })
    .returns(a.ref("Todo"))
    .authorization((allow) => [allow.publicApiKey()])
    .handler(a.handler.custom({
      dataSource: a.ref('Todo'),
      entry: './createNumber7.js'
    }))
});

あとはCustom Resolverとなるjsファイルを作成し、リクエストで受け取った値をutilsライブラリを使ってチェックをかけたりすることができます。

createNumber7.js
import { util } from '@aws-appsync/utils';

export function request(ctx) {
    const { content } = ctx.args;
    
    // 半角数字7桁かどうかをチェック
    if (content.length !== 7 || !util.matches('^[0-9]{7}$', content)) {
        return util.error('Invalid input: content must be a 7-digit number');
    }

    const now = util.time.nowISO8601();
    return {
        operation: 'PutItem',
        key: util.dynamodb.toMapValues({
            id: util.autoId()
        }),
        attributeValues: util.dynamodb.toMapValues({
            content,
            createdAt: now,
            updatedAt: now
        })
    };
}

export function response(ctx) {
  return ctx.result
}

utilsライブラリは他にも色々なmatcherなどの実装があるので、それらを利用しながらより複雑な実装をすることもできます。

テストの実装方法

Custom Resolverでロジックの実装をする場合、きちんとテストを書きたくなります。

バックエンドに対するAPIレベルでのテストを実施するのも良いですが、ここではSmallなテストをする場合のサンプルを書いておきます。

こちらのGitHubを参考にさせていただいています!
https://github.com/naedx/amplify-playground/tree/dev/projects/amplify-appsync-vitest

基本的には @aws-appsync/utils をモックすることでSmallテストを実装します。
郵便番号のInputに関するテストは以下のように実装することでテストコードがかけます。

import { beforeEach, describe, expect, test, vi } from 'vitest';
import { request } from './amplify/data/createNumber7Todo';
import { util } from '@aws-appsync/utils';

// util のモック
vi.mock('@aws-appsync/utils', () => ({
  util: {
    matches: vi.fn(),
    time: {
      nowISO8601: vi.fn().mockReturnValue('2024-03-20T00:00:00Z'),
    },
    autoId: vi.fn().mockReturnValue('mock-id'),
    dynamodb: {
      toMapValues: vi.fn(x => x),
    },
    error: vi.fn(msg => ({ message: msg })),
  },
}));

describe('createNumber7', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('request', () => {
    test('正常な7桁の数字を受け付ける', () => {
      util.matches.mockReturnValue(true);
      const ctx = {
        args: {
          content: '1234567',
        },
      };

      const result = request(ctx);

      expect(result).toEqual({
        operation: 'PutItem',
        key: {
          id: 'mock-id',
        },
        attributeValues: {
          content: '1234567',
          createdAt: '2024-03-20T00:00:00Z',
          updatedAt: '2024-03-20T00:00:00Z',
        },
      });
    });

    test('7桁未満の数字はエラーを返す', () => {
      util.matches.mockReturnValue(false);
      const ctx = {
        args: {
          content: '123456',
        },
      };

      const result = request(ctx);

      expect(result).toEqual({
        message: 'Invalid input: content must be a 7-digit number',
      });
    });

    test('7桁を超える数字はエラーを返す', () => {
      util.matches.mockReturnValue(false);
      const ctx = {
        args: {
          content: '12345678',
        },
      };

      const result = request(ctx);

      expect(result).toEqual({
        message: 'Invalid input: content must be a 7-digit number',
      });
    });

    test('数字以外の文字を含む場合はエラーを返す', () => {
      util.matches.mockReturnValue(false);
      const ctx = {
        args: {
          content: '123a567',
        },
      };

      const result = request(ctx);

      expect(result).toEqual({
        message: 'Invalid input: content must be a 7-digit number',
      });
    });
  });
});

2.Amplify生成リソースのタグ管理

Amplifyを使うといろんなAWSリソースがバンバン作られていきます。
1つのAWSアカウントで複数のAmplifyプロジェクトを利用する場合、似たようなリソースがたくさんできてしまい、コスト管理などに支障をきたします。

その対策として、タグ付けをしてリソース管理を実現できるようにします。

実装方法

やり方自体は簡単で、backendリソースを管理しているCDKにタグのコードを追記するだけです。
authやdataなどのリソース事に細かくタグ付けを分けることもできます。

backend.ts
import { Tags } from 'aws-cdk-lib';
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { data } from './data/resource';

const backend = defineBackend({
  auth,
  data,
});


// タグをリソース毎に追加
const backendTags = Tags.of(backend.stack);
backendTags.add('project', 'amplify-test-app');

const authTags = Tags.of(backend.auth.stack);
authTags.add('service', 'amplify-test-app-auth');

const dataTags = Tags.of(backend.data.stack);
dataTags.add('service', 'amplify-test-app-data');

結果はこんな感じになります!

タグついた画像

参考:
https://docs.amplify.aws/react/build-a-backend/add-aws-services/tagging-resources/


以上です!
なにかのお役に立てば幸いです。

こういった発信等、Xで行っていますのでよろしければフォローいただけると嬉しいです🙇

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?