ひげぶろぐ

開発とか組織とかの話

AWS AmplifyでAPI(AppSync, GraphQL, Dynamo DB)を構築してReactで参照/更新する

前回の記事はこれ。

macha-dev.hatenablog.com

今回は以下のリファレンスに従って amplify add api を試す。
https://aws-amplify.github.io/docs/cli/graphql

Schema ファイルの作成

src/graphql/schema/schema.gql

type Blog @model {
  id: ID!
  name: String!
  posts: [Post] @connection(name: "BlogPosts")
}
type Post @model {
  id: ID!
  title: String!
  blog: Blog @connection(name: "BlogPosts")
  comments: [Comment] @connection(name: "PostComments")
}
type Comment @model {
  id: ID!
  content: String
  post: Post @connection(name: "PostComments")
}

@connection というディレクティブが見慣れないが、 同ページの下の方に説明があるので読むと良い。
要はリレーションを構築する仕組みで、相互に同じ name で紐付ける。
1対1, 1対多 のリレーションをサポートしているが、紐づけ用の中間テーブルの利用により多対多も実現可能。

テーブル/コード作成

設置したスキーマファイルを参照してコードまで生成。

-> % amplify add api
? Please select from one of the below mentioned services GraphQL
? Provide API name: amplifysample
? Choose an authorization type for the API Amazon Cognito User Pool
Use a Cognito user pool configured as a part of this project
? Do you have an annotated GraphQL schema? Yes
? Provide your schema file path: src/graphql/schema/schema.gql

-> % amplify push
? Do you want to generate code for your newly created GraphQL API Yes
? Choose the code generation language target javascript
? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested] 2

終わるとDynamoDBにテーブルが作成され、ローカルではこんな感じでファイルが生成されている。

-> % ls src/graphql/
mutations.js     queries.js       schema           schema.json      subscriptions.js

queries が参照系。mutations が更新系。subscriptions がサーバからのPUSH受け取る系(WebSocket使用)。
この分け方はGraphQLのお作法らしい。

データ作成

以下のコマンドで AppSync のクエリページに飛ぶ。

-> % amplify console api
? Please select from one of the below mentioned services GraphQL

※ 最初左下のVariablesペインに気づかなかった。

appsync_query
appsync_query

ここで色々とデータ入れる。

参照

src/graphql/queries.js の中から使いたいクエリを選んで実行すれば良い。

import React from "react";
import "./App.css";
import { withAuthenticator } from "aws-amplify-react";
import { API, graphqlOperation } from "aws-amplify";
import * as Query from "./graphql/queries";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { postList: [] };
  }

  listQuery = async () => {
    try {
      var ret = await API.graphql(graphqlOperation(Query.listPosts));
      this.setState({ postList: ret });
      console.log(this.state.postList.data.listPosts.items);
    } catch (e) {
      console.error(e);
    }
  };

  componentDidMount() {
    this.listQuery();
  }

更新

src/graphql/mutations.js の中から使いたいクエリを選んで実行すれば良い。

import * as Mutation from "./graphql/mutations";
                :
                :
      var ret = await API.graphql(
        graphqlOperation(Mutation.createPost, { input: {title: "ポストのタイトル"} })
      );

AWS Amplify x React.js で ログイン機能(Cognito)追加

Amplify の authについて調べてたら、amplify add コマンドを使用しないでAWS Consoleで手作業でCognitoの設定をしている記事が多かった。
なんか違う感があったので amplify add auth 使うとどうやって作れるかというのを雑にメモ。

Reactアプリ作成

$ npm install create-react-app
$ create-react-app amplify-sample
$ cd amplify-sample
$ npm run start // 起動

デフォルトAppが立ち上がる

Amplify 組み込み

$ npm install --save aws-amplify
$ npm install --save aws-amplify-react
$ amplify init
$ amplify add auth
$ amplify add push

これやってからAWS Consoleに行くとUser Poolができてる

コード編集

--- a/src/index.js
+++ b/src/index.js

 import './index.css';
 import App from './App';
 import * as serviceWorker from './serviceWorker';
+import Amplify from 'aws-amplify';
+import config from "./aws-exports";

+Amplify.configure(config);
--- a/src/App.js
+++ b/src/App.js

 import React from 'react';
 import logo from './logo.svg';
 import './App.css';
+import { withAuthenticator } from 'aws-amplify-react';

 function App() {
   return (
@@ -23,4 +24,4 @@ function App() {
   );
 }

-export default App;
+export default withAuthenticator(App);

詰まったところ

configure の部分を以下のように書いている記事が多かったが、現状はaws-exports.jsを使うのが正しいっぽかった。ちゃんとドキュメント読めばよかった。

Amplify.configure({
    Auth: {
        identityPoolId: 'ap-northeast-1:XXXXXXX',
        region: 'ap-northeast-1',
        userPoolId: 'ap-northeast-1_XXXXXX',
        userPoolWebClientId: 'XXXXXXXXX',
    }
});

上の書き方で書いてたときはそれぞれが何を示すのか分からなかったので以下に一応メモ。
identityPoolId は、Cognitoの「全般設定」のページの「プール ARN」の中のap-northeast-1:XXXXXXX(XXXXは数字)。
userPoolId は同ページの「プール ID」。
userPoolWebClientId は「アプリクライアントの設定」の中の「clientWeb」の方のID。

動作確認

AWS Console > Cognito > ユーザプール > ユーザとグループ > ユーザの追加 でユーザ追加。
npm run start でアプリを立ち上げるとログイン画面になる。設定がうまく行ってればログインしてデフォルト画面が出るはず。

所感

FirebaseだとFirebase ConsoleのUI上で完結するところが、AmplifyだとCognito等のAWSサービスも意識しないといけない(Amplify Authみたいな機能ではない)のが若干敷居が高いかもしれない。
慣れの問題かもしれないけど。

Nuxt.jsプロジェクトでデバイスに応じて要素を出し分ける

画像サイズを、mobileなら64x64, それ以外なら128x128とかにしたかった。

こいつが便利だった。 nuxt-device-detect - npm

setupに書いてあるとおりですが、
yarn add nuxt-device-detect してあげて
nuxt.config.js の modules に nuxt-device-detect を追記して
yarn run dev 再実行すれば使えます。

こんな感じで画像サイズのmobile, desktop 出し分け出来ました。

<p v-if="$device.isMobile" class="image is-64x64">
  <img :src="user.image">
</p>
<p v-else class="image is-128x128">
  <img :src="user.image">
</p>

Nuxt.jsプロジェクトのプログラム上でURLを生成する

コード内で特定のルートへの絶対パスを取得するには以下のように書けば良い。

let path = this.$router.resolve({
  name: 'page_name',
  params: { id: 1}
}).href
var fullUrl = window.location.origin + '/' + path

が、Nuxt.jsで生成したプロジェクトの pages/articles/_title.vue 的なページに遷移したかったのに、以下のコードだとエラーが出た。

let path = this.$router.resolve({
  name: 'articles',
  params: { title: 'hogehoge'}
}).href
var fullUrl = window.location.origin + '/' + path
# エラー
[vue-router] Route with name 'articles' does not exist

リファレンス見たら name の部分が間違ってるのが分かった。 pages/articles/_title.vue のnameは articles-title になる。

というわけで以下で解決。

let path = this.$router.resolve({
  name: 'articles-title',
  params: { title: 'hogehoge' }
}).href
var fullUrl = window.location.origin + '/' + path

Vue.jsの v-bindで Unexpected 'v-bind' エラー

v-bind でこんな感じで書いてたら

<input
  v-model="title"
  v-bind:class="{ 'is-danger': titleError}"
  class="input"
  type="text"
  placeholder="記事タイトル"
>

エラーが出た

  20:15  error  Unexpected 'v-bind' before ':'  vue/v-bind-style

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

eslint に引っかかってた。eslint: v-bind-style

v-bind 省略したら大丈夫。

<input
  v-model="title"
  :class="{ 'is-danger': titleError}"
  class="input"
  type="text"
  placeholder="記事タイトル"
>

lintに引っかかてるの分かりにくいなあと思ったので一応メモ。

Cloud Functions for Firebase から Cloud SQL上の MySQL を参照する

モチベーション

Firebaseを使ったプロジェクトでリレーショナルなDBを組むため、Cloud SQLと繋げてみることにした。

GCPにしたのはなんとなくfirebaseとの連携がラクそうなイメージだったから。  
Cloud SQL以外で良い方法があるなら知りたいです。

Cloud SQL は第2世代インスタンスMySQLを利用。
ほとんど公式リファレンスのリンク貼ってるだけだけど、手順メモとして残します。

※ 以下の記事にある通り、Function系サービスとRDBの相性は良くないので取り扱いには要注意

なぜAWS LambdaとRDBMSの相性が悪いかを簡単に説明する - Sweet Escape

 

やったこと

簡単な Cloud Function を ローカル実行 & deploy

とりあえず作業スペース確保と動作確認ということで。慣れてないので

Cloud SQL の準備

特筆すべきこともなく、公式ドキュメントなり、適当なブログを見ながらインスタンス作成。

Cloud SQL for MySQL のクイックスタート  |  Cloud SQL for MySQL  |  Google Cloud

ローカルのMySQL ClientからProxyを使って Cloud SQL に繋げる

これも公式ドキュメントに従って実施。

Cloud SQL Proxy を使用して MySQL クライアントに接続する  |  Cloud SQL ドキュメント  |  Google Cloud

Sequel Pro で繋いでテスト用のテーブルを作ったりした。

Cloud Function のコードを編集

ここにテストコードがあるので、コピペでおk

Connecting to Cloud SQL  |  Cloud Functions Documentation  |  Google Cloud

関数として実行したかったのでちょっと変えた。

- exports.mysqlDemo = (req, res) => {
+ exports.mysqlDemo = functions.https.onRequest((req, res) => {

テスト

以下のコード内のクエリが実行されれば成功です。

  mysqlPool.query('SELECT NOW() AS now', (err, results) => {
    if (err) {
      console.error(err);
      res.status(500).send(err);
    } else {
      res.send(JSON.stringify(results));
    }
  });

コマンドラインからテストします。

-> % firebase experimental:functions:shell
i  functions: Preparing to emulate functions.
✔  functions: helloWorld
✔  functions: mysqlDemo

firebase > mysqlDemo.get();
Sent request to function.
firebase > info: User function triggered, starting execution
info: Execution took 926 ms, user function completed successfully

RESPONSE RECEIVED FROM FUNCTION: 200, "[{\"now\":\"2019-01-09T21:38:48.000Z\"}]"

できてました。

あとはコード内のqueryをいじれば色々できました。

読書ログ_201611

なぜ、我々はマネジメントの道を歩むのか

なぜ、我々はマネジメントの道を歩むのか

なぜ、我々はマネジメントの道を歩むのか

 

マネージャーになった時に先輩からの薦めで読んだ。

結構前に読んだけど、マネージャーという役割が、ただの役職ではなく、非常に大きな責任を伴う仕事なのだと認識させられた。 
マネージャーとして歩み始めるタイミングで読めて良かった。

文章も読みやすい。

 

部下を持ったら必ず読む 「任せ方」の教科書 「プレーイング・マネージャー」になってはいけない

これも先輩の薦めで読んだ。

具体的な仕事の任せ方に関して記載されている本。フレームワーク的な、仕事にすぐ使える考え方が書いてある。

結構前に読んだ本だけど、特に色褪せる内容ではないので今も仕事に活かせていると思う。

精神論も大事だけど行動が伴わないと部下からの信頼は得られないので、こういう本も読んでおいて良かった。

こういう内容は研修でも習うんだけど、研修のタイミングを初めての学びにするか反省/改善のタイミングにするかの違いは大きかった。

サーバント・リーダー 「権力」ではない。「権威」を求めよ

サーバント・リーダー 「権力」ではない。「権威」を求めよ

サーバント・リーダー 「権力」ではない。「権威」を求めよ

  • 作者: ジェームズ・ハンター,James Hunter,高山祥子
  • 出版社/メーカー: 海と月社
  • 発売日: 2012/05/21
  • メディア: 単行本(ソフトカバー)
  • クリック: 3回
  • この商品を含むブログ (1件) を見る
 

会社でちょっと話題になっていたので読んだ。

奉仕の精神こそ真のリーダーシップを生み出すという話。

「理想のリーダー像」と「キリスト教における行為としての愛」の共通点を述べ、「行為としての愛とはどのようなものか?」という視点でリーダシップを述べている。 

 ストーリー仕立てになっており、仕事と家庭に悩んだ管理職の男が主人公。
「仕事と家庭に悩んだ」というのが面白いところで、この本に記載されていることはマネジメント以外の対人関係でも有効と思われる。

印象に残ったのは以下の内容。

  • 権力を行使するときは、なぜ使う必要があるのか考えるべき
  • ときにリーダーは犠牲を払う
  • 嫌いな人、苦手な人ほど愛することが出来る
  • 考えより行動が大切
  • 人の成長の4段階(「無意識の未熟」「意識して未熟」「意識して成熟」「無意識の成熟」)
  • 欲求ではなくニーズを満たす(甘やかしではない)
  • 人の前で怒れば全体からマイナス、人の前で褒めれば全体にプラス