Amplify StudioとFigma連携を少し掘り下げてみた。

# この記事の目的

Amplify Studioの機能検証のために、コミュニケーション用のReactアプリケーションを作りました。
その際に考慮した点や気になった点を記録することを目的とします。

## 前提事項
**Amplify Studioを活用して開発することで、開発効率の向上がどの程度図れるか？** というのを検証することを目的としています。
現時点（2022/8末）で、Amplify及びAmplify Studioはまだ発展途上であることは承知しております。
既にIssueとして議論されている内容も「気になった点」に挙げている場合があります。

当方のスキル不足、調査不足により誤った内容や解決手段が存在するものもあると思います。そこは分かり次第反映する予定です。

## アプリケーションの機能
机上の空論だと伝わりづらいので、サンプルアプリを作っています。  
分かりやすさ優先で説明すると機能限定版のTwitterクローンのようなものです。
以下のような機能があります。（AWSのサービスとの紐付きで説明します）
* ユーザーはサインアップとサインインが可能（Cognito）
* ユーザーは以下のアプリケーション機能を利用可能（AppSync, DynamoDB）
  * Profileの登録と編集
  * 呟きをPostする
  * 他ユーザーのPostへLikeをつける
  * 他ユーザーのProfileを閲覧する 
* なお全般としては、CloudFront + S3でホスティングしWAFでアクセス元のIPアドレスをホワイトリスト形式で指定しています。

:::note 
Amplify Storageでアイコン画像の登録などを管理したかったのですが、現時点では機能不足と判断し、断念しています。（代わりにGravatarにアイコンを事前登録してもらう形にしました）
:::
## 開発の方針
開発にあたっては以下の方針を立てました。

* なるべくAmplify StudioのGUIで設定を行う
* 画面レイアウトについてはFigmaでデザインしAmplify Studioと同期する
* 画面コンポーネントはAmplify（Amplify Studio?）で自動生成する（自動生成コードを手で修正することは原則禁止する）

## 開発の流れ
開発の流れは以下のようにしました。

1. 環境構築を行う
1. Figmaを用いてUIコンポーネントを定義する
1. Amplify Studioを用いてDataの定義をする
1. Amplify Studioを用いてUIコンポーネントをFigmaと同期する
1. Amplify Studioを用いてUIコンポーネントとDataのマッピングをする
1. amplify pullしてReactコードを記述する
1. Amplify Studioを用いてAuthorization(Cognito連携)機能を追加する
1. amplify cliコマンドで、hosting & publishingを行う

:::note
1と7以外は繰り返し実施し機能を追加変更していきました。
:::

## Amplify (Amplify Studio) の有効性

以下のような所感を得ました。
* **Figma連携はかなり生産性向上に寄与する**
  * AWS UI-KitをベースとしてFigmaで編集したUIについては、高い精度でui-componentsが自動生成されるため、冗長なUIのコードをエンジニアがコーディングしなくても済む
  * 自動生成されたコンポーネントの振る舞いを容易に拡張できる
  * UI以外のAuthorization(Cognito連携), Data(App Sync, DynamoDB)についても、AmplifyStudioを用いて、直感的に定義が可能であり、インフラエンジニアがアサインできないサービスの立ち上げには有効

一方で、クリティカルなシステムや大規模なシステムをAmplify（AmplifyCLIの標準の選択肢やAmplifyStudio）だけでカバーするのは困難と思われます。（Amplifyのライブラリーだけを使って自前で構築する場合にはその限りではありませんが）
この辺りは、他のサービスや既存のスタックなどと連携することが必要と考えます。

**社内で使うツールを作るなどの用途であれば比較的始めやすいので、そのような方面での活用を図っていきたい。**

## 考慮点（学んだ点）
* Figmaでデザインする際にui-components自動化を意識した書き方をする
* 自動生成されたui-componentsは基本的にはラップして使う
* Amplify Studio上でui-componentsにモーションを追加できるが簡単な処理に限定する
* 簡単なサンプル実装をしたらホスティング（必要ならWAF設定）までを早めにやっておいた方がよい

### Figmaでデザインする際にui-components自動化を意識した書き方をする
Figmaのフレームや配下の部品の名前は自動生成されたui-componentsに使用されるので、Reactプログラムで利用するのにふさわしい名前にすることが望まれます。
例えば、単にTextなどとするとどの要素に対応するか分からなくなるので、データとの紐付けが分かるような適切な名前を付けることにより、Reactプログラムの可読性が向上します。
この辺りのノウハウは以下のドキュメントが参考になります。
https://docs.amplify.aws/console/uibuilder/bestpractices/

### 自動生成されたui-componentsは基本的にはラップして使う
単純なui-componentsであれば、Reactの主処理(App.jsなど)から直接使うことも可能ですが、多くの場合には、
* 前処理でデータの取得をしてui-componentsに渡してあげる処理や、
* 条件により（例えばログインしている本人のデータなのか他人のデータなのかにより）振る舞いを変える
* Buttonクリックイベントで発火させてデータを永続化する処理ケース

などの追加の処理が必要になります。そのため、ラッパー(Container コンポーネント)を経由して使う方が全体のプログラムの可読性が高まると考えています。

典型的な例として、以下のようにプログラムを分割しています。
* 主処理
https://github.com/akiraabe/tweettweet/blob/master/src/routes/Router.jsx

```
<Route path='/form' element={<Form cognitoUser={cognitoUser} />} />
```
という処理で、/formというPathとFormコンポーネント（ラッパー）を紐づけています。この際に、cognitoUser情報を引数に渡しています。

* ラッパー
https://github.com/akiraabe/tweettweet/blob/master/src/components/Form.jsx

```javascript:Form.jsx
import { DataStore } from 'aws-amplify';
import { Post, User } from '../models';
import { useEffect, useState } from 'react';
import moment from 'moment';
import { useNavigate } from 'react-router-dom';
import CommentForm from '../ui-components/CommentForm';

export const Form = ({ cognitoUser }) => {
  const [user, setUser] = useState({});
  const [textFieldValue, setTextFieldValue] = useState('');

  const navigate = useNavigate();
  const getUser = async () => {
    const users = await DataStore.query(User);
    const user = users.filter(
      (obj) => obj.accountName === cognitoUser.username
    )[0];
    setUser(user);
  };

  useEffect(() => {
    getUser();
  }, []);

  return (
    <>
      <CommentForm
        user={user}
        overrides={{
          TextField: {
            value: textFieldValue,
            onChange: (event) => {
              setTextFieldValue(event.target.value);
              // console.log(event.target.value);
            },
          },
          Button: {
            onClick: async (e) => {
              // console.log('button was clicked.');
              e.preventDefault();

              await DataStore.save(
                new Post({
                  content: textFieldValue,
                  likes: 0,
                  postUserId: user.id,
                  postedAt: moment().toISOString()
                })
              );
              // Top画面へ遷移する
              navigate('/');
            },
          },
        }}
      />
    </>
  );
};

export default Form;
```
少し長くなりますが、プログラムの全量を記載しました。
ラッパーの役目としては、
* 必要なデータを取得してui-componentsに渡す
* ui-componentsをオーバーライドしてテキストフィールドからの値の取得やクリック時のデータ永続化
などを実装しています。

* Amplifyにより自動生成されたui-components
https://github.com/akiraabe/tweettweet/blob/master/src/ui-components/CommentForm.jsx

### Amplify Studio上でui-componentsにモーションを追加できるが簡単な処理に限定する
UIの特定のエリアにマウスが移動してきた場合、マウスが去った場合に背景色を変えるなどのUIの装飾については、Amplify Studio上で定義するのも良いかもしれません。（このような処理もReactプログラムで記述出来ますが、あまり本質的な機能ではないのでStudioで定義するという方針でも良いと思います。）

![ui-customize.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130262/21113e0e-4ac1-00b6-ff67-47877ea21cb5.png)

### 簡単なサンプル実装をしたらホスティング（必要ならWAF設定）までを早めにやっておいた方がよい
Amplifyと直接関係ないかもしれませんが、開発がかなり進んでからホスティングするよりも、早めの段階で一回やっておいた方が良いと思います。
理由として以下のようなものがあげられます。
* 一瞬はpublicにアクセス可能になるので開発が進んで機能が増えた段階だとリスクがある
* Amplifyのコンソールからだと設定できない項目（WAFなど）があることに早めに気づける

## 気になった点

気になった点については、既にIssueとして上がっているものもあります。こちらについては以下のサイトを参考にしています。
https://zenn.dev/ibaraki/articles/5b563a935642c2

* FigmaとAmplify Studio連携で、RadioGroupFieldとRadioを生成する方法が不明
* Figmaで定義された制約（画面のサイズが変わった時に特定コンポーネントを右寄せするなど）が、Reactコードに反映されない場合がある
* AmplifyコンソールでホスティングするとWAFの設定ができない
* Storage機能が不足している
* Collectionにページングの機能があるが最初に全件クライアント側に取得するので注意が必要
* Amplify StudioのContent機能の挙動が不安定
* amplify pullの際に引数を付けると正しく動作しない
* amplify publishで修正内容が直ちに反映されない
* When importing ui from Figma. Dialog says that "Maximum call stack size exceeded".
* " Uncaught SyntaxError: Unexpected token '<' "

### FigmaとAmplifyStudio連携でRadioGroupFieldとRadioを生成する方法が不明
FigmaのAWS UI-Kitには「Primitives」として、さまざまなコンポーネントが登録されています。（ある種のデザインシステムになっているようです）
これらを活用して、入力フォームを作ることが期待されますが、現実的には、以下の課題があります。
* 入力フォームをFigmaでどのように定義するとどのようなUIコンポーネントが生成されるか？を説明したドキュメントが見当たらない
* 例えば、Radioコンポーネントを作成してみると、それ自身の生成は可能だが、RadioGroupFieldの生成方法が不明（Radioだけだと選択時にイベントを発火できないので、あまり使い道がない）
参考：https://ui.docs.amplify.aws/react/components/radiogroupfield


#### やりたいこと
画面イメージ（Optionを選択肢Contextなどに保持する）
![amplify-react-ui-radio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130262/456b0075-d29a-706c-2c59-143ae5186a3d.png)


実装（ここでは、Figma & AmplifyStudioでは無理そうなのでReactを手動コーディングした）
```javascript
import { useState } from 'react';
import { RadioGroupField, Radio, Button } from '@aws-amplify/ui-react';
import PageTitle from '../ui-components/PageTitle';

const OptionSelector = ({ userId } ) => {

  // 将来的には、この値リストは、DBで管理するようにする
  const options = ['hoge', 'fuga', 'foo', 'bar'];
  
  const [value, setValue] = useState('');
  return (
    <>
      <PageTitle
        overrides={{
          LabelPageTitle: {
            children: 'Option Selector',
          },
        }}
      />
      <RadioGroupField
        name='system'
        value={value}
        onChange={(e) => {
          setValue(e.target.value);
          // console.log('value:', value);
        }}
      >
        {options.map((option) => {
          //console.log(option);
          return (
            <Radio value={option} key={option}>
              {option}
            </Radio>
          );
        })}
      </RadioGroupField>
      <Button
        variation='primary'
        onClick={async () => {
          console.log('value at Button was clicked:', value);
          // 何かしらの処理を実行する
        }}
      >
        Set to context
      </Button>
    </>
  );
};

export default OptionSelector;
```

:::note
この件で、DiscordのAmplifyコミュニティーに質問してみたところ、コメントがAWSの方(正確にはDiscordでRoleがAWS Staffとなっている方)からつきました。
残念ながらコメント内容は、今回の件にフィットしませんでしたが、色々な情報が得られるので、Discordのコミュニティーは非常に有用です。
https://discord.com/channels/705853757799399426/783751834241204235/1022288577381535805
:::

### Figmaで定義された制約が、Reactコードに反映されない場合がある
FigmaにはAutoLayoutやConstraintsという機能があり、これを活用すると、画面サイズが変更した際に、例えばSignOutボタンだけは右寄せしておきたいなどの対応が出来ます。（レスポンシブデザインにも適用が可能）
しかしながら、制約が正しくReactコードに反映されない場合がありました。

こちらについては、根本的な原因と対策は不明ですが、解決策がありますので、今回はそれで回避しました。
具体的には、widthやhegihtのプロパティーをオーバーライドしてあげるという方法です。
以下にコードスニペットを記載します。

```javascript
      // 自動生成コンポーネントにwidthが固定値で定義されてしまっているので、overrideして無効化する
      <Header
        overrides={{
          Header: {
            width: { undefined },
          },
        }}
      />

```

### AmplifyコンソールでホスティングするとWAFの設定ができない

このissueは2019年に上がっているので対応が難しいのかもしれません。仕様として受け入れるべきと認識しました。
[Web Application Firewall Integration #36](https://github.com/aws-amplify/amplify-hosting/issues/36)

:::note
なお、このことを知らずに、一度Amplify（マネジメントコンソール）でホスティングしたので、ConsoleからDisconnectした上で、AWS CLIで`amplify remove hosting`をする必要がありました。
:::

#### 以下の記事を参考にamplify publishして個別にCloudFrontにWAFを設定
[Amplify CLIから構築する独自ドメインカスタムホスティング環境](
https://qiita.com/dayjournal/items/57cb0a8232d11c6c99af#amplify-cli%E3%81%8B%E3%82%89%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%E7%8B%AC%E8%87%AA%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%9B%E3%82%B9%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E7%92%B0%E5%A2%83)

* まずは、Amplify CLIを使ってホスティング
* 次にCloudFrontにWAFを設定（マネジメントコンソールから）

今回はアクセス可能なソースIPアドレスをホワイトリスト形式で登録したいという要件なので、以下のクラスメソッドさんのサイトを参考にしました。
[Amplify CLIから構築する独自ドメインカスタムホスティング環境](https://dev.classmethod.jp/articles/how-to-use-aws-waf-v2-to-filter-incoming-traffic-based-ip-address/)


### Storage機能が不足している
[[Feedback] S3 Image Picker On Completed Upload Trigger? #249](https://github.com/aws-amplify/amplify-ui/issues/249)

Storageを活用したアプリを作りたいところですが、現状だとAmplify uiのライブラリー機能が不足しており、あまり実用的に思えませんでした。
* AmplifyS3ImagePickerというコンポーネントがあるがCallBack処理が記述できない（[Hubを使用する例](https://qiita.com/azuharu07/items/e47e01160a8d3f6f3720)が紹介されていたが動かなかった）
* S3にアップロードしたイメージを参照したいがPresignedURLが前提の仕様となっているため、URLの有効期間切れに注意が必要です。

### Collectionにページングの機能があるが最初に全件クライアント側に取得するので注意が必要
Amplify StudioのUIの設定で、Collectionの作成ができます。この際に、検索やページングが指定できますが、ページングといってもUIが提供されるだけで、ページングの都度DBアクセスする処理は自前のReactプログラムで記述する必要があります。

Amplify UIの責務としてはUIの提供のみなので、妥当な仕様なのかもしれませんが、ページング周りがDBアセクスも含めてフレームワークサポートされていないのは少々残念に思いました。

:::note
追記：DynamoDBでページング処理を行うのはあまり現実的ではなさそうなので、ページングを行う場合にはQuery用にRDBMSを使う方が良さそうに感じています。
:::

### Amplify StudioのContent機能の挙動が不安定
Amplify Studio にContentという機能があります。ここでテストデータのセットアップなどを行えるので便利です。

しかし、若干挙動が不安定で、画面がLoadingのまま起動しない時がありました。
下のURLのIssueと同じ事象ですが、原因が別のようです。
（今回はブラウザのキャッシュをクリアしたタイミングで直ったように思いますが、再現が難しいため正確なことは分かりません。）
[Content tab doesn't load the data (show 'Loading' forever) #132(Closed)](https://github.com/aws-amplify/amplify-adminui/issues/132)

:::note
追記：その後この事象は再発していないので、再現待ちとしています。（再現したらIssueなどをあげるかもしれません）
:::

### amplify pullの際に引数を付けると正しく動作しない
こちらはIssueに上がっていますが、まだオープンの状態です。

:::note
追記：現在はCloseされています。
:::

対応としては``` amplify pull``` のように引数を指定しなければうまくいきます。

[Amplify CLI: AWS Amplify Failed to communicate with the Amplify CLI [using Safari browser] #84](https://github.com/aws-amplify/amplify-adminui/issues/84)

### amplify publishで修正内容が直ちに反映されない
これは仕様なのかもしれませんが、以下のサイトを参考にとりあえず対応しています。
[「amplify publish」をした後、ブラウザに更新が反映されない時にCloudFrontでやること](https://qiita.com/nomi3/items/87f6bfd3f4237da47278)

### When importing ui from Figma. Dialog says that "Maximum call stack size exceeded".

https://github.com/aws-amplify/amplify-adminui/issues/575

:::note
こちらは私がIssueをあげましたが、現在はCloseされています。
:::

### " Uncaught SyntaxError: Unexpected token '<' "
amplify publishによりホスティングした画面がホワイトアウトしてしまいました。
CloudFrontのキャッシュをクリアすると直るといった情報もありましたが、今回はそれだけでは有効になりませんでした。そのため、一旦、ホスティングをし直した(`amplify hosting remove` & `amplify hosting add`)上で、CloudFrontのキャッシュのクリアをしました。



## 所感
Amplifyをローコード開発ツールと括って良いのか分かりませんが、企業による内製化などには非常に有用なツールであると思います。

我々の立場からすると、社内で使うツールを作るなどの用途であれば比較的始めやすいのでその辺りから取り組んでいきたいと思います。

## おまけ
かなり共感した記事
https://note.com/seyanote/n/n5657a1647911