雑食日誌

Vue.jsとServerless。ときどきチーム開発

JavaScriptでURLエンコード

URLクエリ内に別URLをもたせたい時にURLをエンコードする方法についてまとめます。 今回はJavaScriptで実装しました。

encodeURI

はじめ、JavaScriptのencodeURIを試してみました。 本ブログのURLを引数に実行してみます。

> encodeURI('https://keinumata.hatenablog.com/')
'https://keinumata.hatenablog.com/'

結果は引数と何も変わらないです。これはencodeURI関数がURLを構成する予約語(;,/?:@&=+$)およびエンコードせず使用できる非予約語(-_.!~*'())はエンコードしないためです。 試しに空白を入れて実行してみます。

> encodeURI('https://keinumata.hatenablog.com/? a')
'https://keinumata.hatenablog.com/?%20a'

日本語もエンコードされます。

> encodeURI('https://keinumata.hatenablog.com/ジャヴァスクリプト')
'https://keinumata.hatenablog.com/%E3%82%B8%E3%83%A3%E3%83%B4%E3%82%A1%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88'

今回はURLのクエリに使用するため、予約語エンコードしたいので他の関数を探してみます。

encodeURIComponent

encodeURIComponentはencodeURIと比較してURLを構成する予約語(;,/?:@&=+$)もエンコードします。エンコードせず使用できる非予約語(-_.!~*'())はエンコードしません。 試しにencodeURIで試したURLを実行してみます。

> encodeURIComponent('https://keinumata.hatenablog.com/')
'https%3A%2F%2Fkeinumata.hatenablog.com%2F'

スラッシュやコロンもエンコードできました。

結論

  • URLクエリにURLを使用する場合はencodeURI ではなく encodeURIComponent を使う

参考

developer.mozilla.org

Vue Composition APIとTypeScriptの組み合わせ

この記事はギルドワークスAdvent Calendarの3日目の記事です。 Vue.jsのバージョン3にてリリース予定のComposition APIとTypeScriptを組み合わせについて紹介します。

adventar.org

Composition APIとは

Composition APIは関数ベースでコンポーネントを実装する機能です。関数ベースのため、機能の再利用性を高める役割を持っています。他のライブラリではReact hooksやSvelteに近い機能です。

公式のRFCに記載されているモチベーションの1つにTypeScriptとの相性を高めることが書かれています。本記事ではサンプルコードを踏まえてなぜTypeScriptと相性がよくなるのか考えてみます。

https://vue-composition-api-rfc.netlify.com/#better-type-inference

準備

Vue2系で試す場合はプラグインが提供されているので、インストールする。

https://github.com/vuejs/composition-api

$ npm install @vue/composition-api --save
or
$ yarn add @vue/composition-api

インストールが完了したらVue.use を追加します。

import Vue from 'vue';
import VueCompositionApi from '@vue/composition-api';

Vue.use(VueCompositionApi);

TypeScriptと併せて使用する、かつVS CodeでVeturを使用している場合、設定を以下のように変更する必要があります。

{
  "vetur.useWorkspaceDependencies": true
}

Options APIとComposition APIの比較

TypeScriptとの併用がやりやすくなるかを見るために、基本的なVue.jsのAPIであるOptions APIと比較してみたいと思います。 今回は両方のAPIを使って同じ機能を持ったモーダルコンポーネントを実装してみます。 機能としては以下になります。

  • モーダルの表示・非表示ができる
  • モーダル内に日付を表示し、ボタンによってカウントアップできる
  • 日付が条件に達したらメッセージを表示

f:id:keinumata:20191202225611p:plain
メッセージ無

f:id:keinumata:20191202225508p:plain
メッセージ有

以下のGitHubにても公開しています。

https://github.com/keinuma/compare-options-and-composition

テンプレート

テンプレートおよびスタイルは共通のコードを使用します。(スタイルのコードは今回の記事では紹介しません。)

<template>
  <div>
    <div :class="{ 'modal-overlay': showModal }">
      <div
        v-if="showModal"
        class="modal-isshow modal-open"
      >
        <div class="modal-container">
          <button class="close-btn btn btn-outline-info" @click="closeModal">
            閉じる
          </button>
          <slot></slot>
          <div>
            <div v-show="isLastDate" class="alert alert-danger" role="alert">
              <p class="alert-last-date">
                クリスマスになりました。<br />
                アドベントカレンダーの最終日です。
              </p>
            </div>
            <p>この記事はギルドワークスアドベントカレンダーの{{ date }}日目の記事です。</p>
            <button
              type="button"
              class="btn btn-info"
              @click="countUpDate" 
              :disabled="isLastDate">
              次の日
            </button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

Options API

Options APIの場合は以下になります。 TypeScript対応は Vue.extend を使用しています。

import Vue from 'vue'

export default Vue.extend({
  name: 'ModalOptions',
  props: {
    showModal: {
      type: Boolean,
      required: true,
      default: false
    }
  },
  data() {
    return {
      date: 1
    }
  },
  computed: {
    isLastDate() {
      return this.date === 25
    }
  },
  methods: {
    countUpDate() {
      if (this.isLastDate) {
        return
      }
      this.date++
    },
    closeModal(e: Event) {
      e.stopPropagation()
      this.date = 1
      this.$emit("onCloseModal")
    }
  }
})

Composition API

Composition APIの場合は以下になります。 基本的に setup 関数の中でデータやメソッドの定義を行います。 リアクティブなデータはComposition APIで定義されている refreactive 関数から定義できます。 モーダルを閉じるメソッドで使用しているように、Vue インスタンスへは context を使用してアクセスできます。

import {
  createComponent,
  ref,
  computed
} from '@vue/composition-api'

type Props = {
  showModal: boolean
}

export default createComponent({
  props: {
    showModal: {
      type: Boolean,
      required: true,
      default: false
    }
  },
  setup(props: Props, context) {
    const date = ref(1)
    const isLastDate = computed(() => {
      return date.value === 25
    })
    const countUpDate = () => {
      if (isLastDate.value) {
        return
      }
      date.value++
    }
    const closeModal = (e: Event) => {
      e.stopPropagation()
      date.value = 1
      context.emit("onCloseModal")
    }
    return {
      showModal: props.showModal,
      date,
      isLastDate,
      countUpDate,
      closeModal
    }
  }
})

考察

2つのコードを比較すると、Composition APIはOptions APIに比べて this がなくなっています。 これまで Vuethis にはデータやメソッドのような開発者が定義したアプリケーション的意味合いと、filter やVue RouterなどのVueインスタンス自身の2つの意味を持っていました。Composition APIの場合、前者を setup 関数内で定義、後者を context に持たせることで this の責務を分離しています。 責務を分離することで型定義を推論を効かせしやすくしていると考えられます。

また、 Options APImethods でも同じことはできるのですが、相互参照が見えやすい setup 関数だからこそ機能としての再利用性を意識しやすくなります。そのため、外部モジュールへの抽出や構造化が促進されると考えられます。

まとめ

  • Composition APIによってTypeScriptが使いやすくなる
  • Vue 3待ち遠しい

DevLOVE X参加記録

先日DevLOVE Xというイベントに参加してきた。とても豪華なスピーカー陣が5トラック並行にセッションするため、何を聞きにいくか一番悩んだイベントだったと思う。
色々な分野の有力者の方々から深い話をたくさん聞けて面白く、多くの学びがあった。

devlove.wixsite.com

特に印象に残った講演をふりかえっていく。(以下敬称略

エンジニア、エンジニアリングマネージャーとして成長するために必要なこととは? by 安西 剛

speakerdeck.com

今回のイベントで一番考えさせられたセンションだった。 最近の開発ではチームに着眼したふりかえりを実施するものの、個人に焦点を当てたふりかえりはなかなか広まっていない。 しかし、エンジニア個人の成長を考えると一人のふりかえりがより重要ではないかというお話だった。 私自身一人のふりかえりを実施していたが、以下の新しい観点を得られた。

  • KPT/YWTに加えて感情を言語化する
  • ふりかえりした結果を共有する

私は自分の嫌な一面やみたくないところと向き合いたくないから感情を言語化することを避けてきていたように感じる。 セッションを聞いて、感情と向き合うことで自分のモチベーションを知ることができ、成長につながることがわかった。 自分のことは十分知っているだろうという認知バイアスにかかっていたことを自覚させられた瞬間だった。

個人のふりかえりを共有することは気恥ずかしさがあり一人ではやるという発想にすらいたっていなかった。 しかし、自分が見えない観点をフィードバックしてもらえることは重要だと気付かされた。

ふりかえりの結果、学んだことは習慣化しないと意味がなくなる。 無意識におこなわれる習慣にうまく組み込めるようにルーチンを作っていきたい。

それはYAGNIか? それとも思考停止か? by 川島 義隆

www.slideshare.net

最近私はエンジニアとしての働き方、役割が変わり、言われた物を作るよりも価値のあるものを作ることにコミットするようになってきている。
そんな中で YAGNI を間違って認識し、作り始めるスピードを正としてきはじめた自分にはいい教訓になった。
考えることを安易に止めず、本当はもっといい設計があるのではないか、考えることに時間を使う方が投資対効果が高いこともある。 なので、どこまでが本当にユーザーに問わないとわからない領域でどこまでは思考することで最善を尽くせるのかバランスを意識しないといけないと解釈した。

具体的な設計では状態や区分を実装するときにインターフェースを切る考えを学んだ。 ホットスポットを見つけるツールも使っていきたい。

自分に向いている楽しめる仕事をするための目標設定 -失敗のストーリーに分析と対策を添えて- by 川鯉 光起

speakerdeck.com

過去の経験から自分を知り、学びを抽象化する具体的な方法を知ることができた。 エンジニアの自分にとっては「エンジニアリングが好き」で考えを止めず、さらに目的を深掘りしていくことが学びになった。 自分なりに解釈すると、以下のようなる。

  • 経験してきた中で印象に残るストーリーを切り出す
  • ストーリーから事実(仕事)と感情を洗い出す
  • 並べた事実と感情がなぜ起きた/感じたかを問い直す
  • 問いに答えてさらに問い直すことを重ねて学びに抽象化する

これらから自分は作ることよりも価値を届けること、使ってもらえることにモチベーションを感じることが改めてわかった。 他にも紹介されていたキャリアアンカーストレングスファインダーをやってみたいと思う。

アウトカムを出す! 楽しむ! 両方やるために経験を味方にする! by 高橋 陽太郎

poohsunny.hatenablog.com

アウトカムは重要であると同時にエンジニアの楽しさにも経験を通して繋がっていることを学んだ。

セッションを聞くまでプロダクト作りは成果にコミットできいないと意味がないけれども、全員のエンジニアの楽しさにつながるわけではないのではないかと思っていた。 エンジニアは技術領域と何を作るかを決める領域のそれぞれ比重を持っていたからだ。 センションを聞いたおかげで、足りないのは視座を高めたり詳細まで降りたりすることを共有することだと学んだ。

コーディングという観点だけを見ると限りなく綺麗にしていきたいし、最適化したくなる。 ただ、プロダクトとして何を目指していて、現在どういう状況なのかを共有し、理解することでチーム全員が同じ方向を向けるのではないかと思った。 なので、エンジニアに限らずチームメンバーがどの分野に興味があり、どこにギャップがあるか観察して思いを共有していきたい。

まとめ

今回のイベントからは一貫して学びを得ることの重要性を知れた。 学びは自分だけでなく、チームやプロダクトにも当てはめることができる。 これからは積極的に学びを得るためのフィードバックサイクルを早くしていきたい。

最後に、ここまで濃ゆい時間はもう過ごせないと感じるほど素敵な二日間を提供してくださった運営の方々に圧倒的感謝…

使いやすいプロダクトを目指して

使いにくいプロダクト

ある案件で開発したプロダクトがユーザーにとって使いにくいと評価されることがあった。
プロダクトの受け入れ条件を満たし、チームの中で認められたプロダクトであってもユーザーにとって価値が高いわけではない。
頭でわかっていたつもりであってもはじめて経験したときに少なからず戸惑いがあった。
どうすればユーザーにとって使いやすいプロダクトを開発できるのか試行錯誤している考えをまとめてみる。

エンジニアとしての開発

多くの開発では素早く正確に実装することが求められている。 また、何を作れば正解かわからないプロダクト開発では変更容易性も兼ね備えなければならない。
そのため私は以下のことを考えながらプロダクトを開発していた。

  • 設計指針から外れていないか
  • 責任を明確に分離できているか
  • ミスをしていないか
  • 可読性の高いコードになっているか

この時点で意識しないといけないことは多い…
ふりかえるとエンジニアとして開発するときはミクロな目線で糸を縫うような作業をしているようだなと感じた。
このうえでユーザビリティを高めるためにはユーザー目線を得る必要があると思った。

ユーザー目線の獲得

ユーザーの視点を借りるためにはどうすれば良いか。一番はユーザーにプロダクトを体験してもらうことだと思う。
体験してもらっている様子を観察して、問いかけたり、プロダクトの感想を伝えてもらって方針を変えたり機能を追加していく。

しかし、ターゲットユーザーに実際にプロダクトを提供するためには、多くの人を巻き込みプロダクトを一定レベルまで完成しないといけないためコストが大きい。 プロダクトの方針一つ一つをユーザーに問いかける訳にはいかないので、一定レベルまでエンジニアがプロダクトを形作っていかなければならない。
一定レベルまで仕上げるためにはエンジニアがユーザー目線を持たないといけない。 エンジニアがユーザー目線を得るためにはユーザーに共感する必要がでてくる。 共感するためには、バックグラウンドや感じる課題、プロダクトを使用するときに気にかけることなどを知らないといけない。

これらからユーザー目線の獲得は一朝一夕でなくユーザーと対話を重ねることで作り手側にユーザー目線を宿していく深い経験ではないだろうか。
理想の姿とは感じつつも常にエンジニアがユーザーと対話できる状況ではない。対話なしでもエンジニア個人の基準でプロダクトを作り続けられるようになりたい。

違和感を感じる

私が考えたユーザー目線に近づくためのアプローチはプロダクトの違和感を拾うことだった。
違和感はプロダクトを初めて見たときに感じやすい。そのため開発を続けているとプロダクトの当たり前が自分の中に浸透してしまう。 今まで開発してきたプロダクトではリストの見せ方やボタン配置・配色、ページ遷移時のインタラクションなどを当たり前にしてしまっていたように思う。

この当たり前がチームの中に浸透してしまうと、ユーザーに見せるまで誰も疑問に思うことなくプロダクトを改善する機会を失ってしまうことになる。 なのでプロダクトを改善するためには意識的にユーザーになりきって違和感を感じることが大事なのではないかと考えた。

目線を意識的に切り替える

違和感を感じるためにユーザーになりきったまま細やかな開発を遂行するのは難易度が高い。私自身ミクロな目線とマクロな目線を同時に持ち合わせられるほど器用になれない。
なので、まずは実装に集中するとき(開発モード)とユーザーになりきるとき(ユーザーモード)の二つを意識している。

ユーザーとしてプロダクトを触って違和感を感じたらプロダクトバックログに追加する。追加したプロダクトバックログアイテムを元に使いやすさを求めて開発を進めていく。 目線切り替えを繰り返していくと個人の中でプロダクトの改善サイクルが回り始める。

改善を続けていても一人では限界がある。この限界は開発チーム全員が互いに目線を切り替えて、議論を重ねることで超えられるのではないだろうか。 壁を超え続けたプロダクトはきっとユーザーにとっての価値に近づくと信じている。

AppSyncのデプロイについてまとめてみた

はじめに

ある案件でAppSyncを使うことになったが、商用利用の事例が少なくデプロイ方法がわからなかった。
自動でデプロイできる仕組みを探したところ、Serverless FrameworkのAppSyncプラグインがあったので使ってみた。

AppSyncとは

AppSyncはAWSで利用できるGraphQLベースのマネージドサービスである。

特徴として

  • バックエンドサービスを自由に切り替えることができる
  • GraphQLベースのため、フロントエンドに必要なデータを定義できる

などがある。
特にバックエンドにはLambdaやDynamoDB、HTTPなど多様なリソースを選択できる。

前提

今回の案件では以下の条件があった。

  • AppSyncをLambdaバックエンドとHTTPバックエンドの二種類を実装
  • フロントエンドはアプリ(iOS, Android) とWebアプリ(JavaScript)
  • 環境は開発環境・検証環境・本番環境の3つ
  • CloudFormation経験は薄く、Sceptreを使ってECS(Fargate)を作成した経験のみ

デプロイ方法の選定

現状AppSyncを商用利用して、デプロイするためには以下の手段が考えられた。

  • CloudFormationを書く
  • Amplify CLIを使う

CloudFormationを使ってデプロイするためにはYaml地獄と戦い、トライアンドエラーを繰り返す必要がある。 時間的猶予がなかったため却下した。

Amplify CLIはコマンド一つでAWSリソースを作ることができる強者である。 しかし、以下の懸念点があった。

  • 開発当初は env 機能がなかったため、環境を複数設定できない
  • 複数のフロントエンドで共有することが難しい

そのため、リソースの作成にAmplify CLIを使用しなかった。

他に良いデプロイ方法はないか探していたら、以下の記事に遭遇し、ServerlessFrameworkのAppSyncプラグインを知った。
read.acloud.guru

Serverless Frameworkを使う予定があったので、試してみたら思いのほか手軽だった。
ここからは実際にデプロイした手順を記録していく。

Serverless FrameworkでAppSyncデプロイ

今回はAppSyncのバックエンドにLambdaを使用する。 GraphQL SchemaにはブログWebアプリケーションを想定に実装する。

サンプルはGitHubにもあげているので併せてみていただきたい。

github.com

CLIインストール

NPMでインストールする。

$ npm install -g serverless

プロジェクト初期化

プロジェクトを初期化する。
このタイミングでは package.json は作成されていない。

$ serverless create --template aws-nodejs --path appsync-deploy
$ cd appsync-deploy
$ ls -a
.gitignore     handler.js     serverless.yml

create コマンドを実行するには template オプションが必要である。
template の種類は以下に詳細が記載されている。

serverless.com

成果物のうち serverless.yml はCloudFormationのもとになるYaml定義。
handler.jsAWS Lambdaを使用する場合の実行ファイルのため、AppSyncのデプロイだけでは不要である。

プラグインのインストール

ServerlessFrameworkのプラグインはNPMパッケージでまとめられているため、 package.jsonプラグインを管理していく。
Node環境がまだないので、改めてNPM初期化を実施する

$ git init
$ npm init 
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (appsync-deploy)
version: (1.0.0)
description:
entry point: (handler.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to appsync-deploy/package.json:

{
  "name": "appsync-deploy",
  "version": "1.0.0",
  "description": "",
  "main": "handler.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

AppSyncプラグインをインストールする。

$ npm i serverless serverless-appsync-plugin

デプロイコマンドをNPMの scripts に定義する。

"scripts": {
  "deploy": "serverless deploy -v"
},

これでデプロイを実行する準備が整った。 serverless.yml を整えながら、AppSyncの設定を加えていく。

Serverless Framework共通設定

はじめに基本設定を記述していく。
serverless.yml を開くと例が記載されているが、今回必要な部分は少ないので消す。

service: appsync-deploy

provider:
  name: aws
  region: ap-northeast-1

plugins:
  - serverless-appsync-plugin

service はCloudFormationのStack Nameに使用される部分であり、 デプロイ環境の総称となる。今回はプロジェクトのディレクトリ名を使用する。

provider はCloud Providers(AWS)およびリージョンの設定をする。
plugins にはインストールしたAppSyncのプラグインを記述する。

AppSyncの基本設定

このサンプルではAppSyncの認証に Amazon Cognitoを選択する。
Schemaは簡易的にするために QueryMutation を一つずつにした。
Schemaの詳細を以下に記載する。

schema {
    query: Query
    mutation: Mutation
}

type Query {
    listArticle: ArticlesResponse
}

type Mutation {
    createArticle(title: String, content: String): ArticlesResponse
}

type Article {
    id: Int
    title: String
    content: String
}

type ArticlesResponse {
    errorCode: String
    articles: [Article]
}

Schemaと認証設定を serverless.yml に追記する。

custom:
  accountId: ${env:AWS_ACCOUNT_ID}
  appSync:
    name: BlogApp # AppSyncのAPI名
    authenticationType: AMAZON_COGNITO_USER_POOLS # API Keyなど選択
    userPoolConfig:
      # Cognitoの設定
      awsRegion: ap-northeast-1
      defaultAction: ALLOW
      userPoolId: ap-northeast-1_XXXXX
    schema: schema.graphql

Data Sources

バックエンドにはLambdaを使用するため、二つのARNが必要になる。

  • LambdaそのもののARN
  • AppSyncがLambdaを実行するRoleのARN

Roleはデフォルトの設定で作成されないため、事前にコンソールから作るか、 Serverless Frameworkの resources 機能で作る。

DataSourcesを serverless.yml に反映すると以下になる。

custom:
  accountId: ${env:AWS_ACCOUNT_ID}
  appSync:
    name: BlogApp # AppSyncのAPI名
    authenticationType: AMAZON_COGNITO_USER_POOLS # API Keyなど選択
    userPoolConfig:
      # Cognitoの設定
      awsRegion: ap-northeast-1
      defaultAction: ALLOW
      userPoolId: ap-northeast-1_XXXXX
    schema: schema.graphql
    dataSources:
      - type: AWS_LAMBDA
        name: BlogAppResolver
        config:
          functionName: blog-app-resolver
          lambdaFunctionArn: "arn:aws:lambda:ap-northeast-1:${env:AWS_ACCOUNT_ID}:function:blog-app-resolver"
          serviceRoleArn: "arn:aws:iam::${env:AWS_ACCOUNT_ID}:role/service-role/appsync-ds-lam-xxxxxxxxxxxxx

Mapping Templates

最後にMapping Templatesを整える。
Mapping Templatesのリクエストとレスポンスそれぞれをファイル化し、 mapping-templates ディレクトリに格納することでAppSyncに反映する仕組みになっている。

レスポンスのMapping Templatesは共通化させて設定した。

レスポンス
common-response.vtl

$util.toJson($context.result)

Queryのリクエス
query-list-blog-request.vtl

{
  "version" : "2017-02-28",
  "operation": "Invoke",
  "payload": {
    "path": "list_blog",
    "data": $util.toJson($context.args)
  }
}

Mutationのリクエス
mutation-create-blog-request.vtl

{
  "version" : "2017-02-28",
  "operation": "Invoke",
  "payload": {
    "path": "create_blog",
    "data": $util.toJson($context.args)
  }
}

これらを mapping-templates ディレクトリ以下に格納する。ディレクトリ名は変更することもできる。

Mapping Templatesの設定を serverless.yml に反映すると以下のようになる。

custom:
  accountId: ${env:AWS_ACCOUNT_ID}
  appSync:
    name: BlogApp # AppSyncのAPI名
    authenticationType: AMAZON_COGNITO_USER_POOLS # API Keyなど選択
    userPoolConfig:
      # Cognitoの設定
      awsRegion: ap-northeast-1
      defaultAction: ALLOW
      userPoolId: ap-northeast-1_H8OHxpYAa
    schema: schema.graphql
    dataSources:
      - type: AWS_LAMBDA
        name: BlogAppResolber
        config:
          functionName: blog-app-resolver
          lambdaFunctionArn: "arn:aws:lambda:ap-northeast-1:${env:AWS_ACCOUNT_ID}:function:blog-app-resolver"
          serviceRoleArn: "arn:aws:iam::${env:AWS_ACCOUNT_ID}:role/service-role/appsync-ds-lam-ln4jdz-blog-app-resolver"
    mappingTemplates:
      - dataSources: BlogAppResolber
        type: Query # Query, Mutation, Subscription
        field: listArticle # Schema内のフィールド名
        request: "query-list-blog-request.vtl"
        response: "common-response.vtl"
      - dataSources: BlogAppResolber
        type: Mutation
        field: createArticle
        request: "mutation-create-blog-request.vtl"
        response: "common-response.vtl"

デプロイ実行

package.json に登録したコマンドでデプロイを実行する。 サンプルはCognitoやRole ARNを仮で記載しているので、編集してから実行する。

$ npm run deploy

エラーが出る場合は環境変数SLS_DEBUG を追加することで、実行の詳細を確認することができる。

$ export SLS_DEBUG=* 

Serverless Frameworkは最初に、 serverless.yml ファイルをコンパイルして、CloudFormationに変換し、S3バケットに格納する。
成果物は .serverless ディレクトリにも保存される。

S3に格納されたら、CloudFormationが実行され、定義したAWSリソースが生成される。

まとめ

  • AppSyncの情報は少ない
  • デプロイはServerless Frameworkが便利

参考

github.com

「WHYから始めよ! 」を読んでみて思うこと

はじめに

「WHYから始めよ」を読んだきっかけはThe Agile Guild の定例会で、なぜコミュニティに参加しているのかという問いを投げかけられたことであった。私は「価値のあるものを生み出したいから」と答えたものの、きちんと整理できていなかったため、もやもやしていた。 そこで以前紹介されたこの書籍を手に取った。

本記事では組織の一人として感じたこととエンジニアの一人として感じたこと、二つをまとめていく。 (注: 書籍についてはあまりふれません)

WHYから始めよ!  インスパイア型リーダーはここが違う

WHYから始めよ! インスパイア型リーダーはここが違う

書籍について

本書籍はTEDで公開された講演内容をもとにまとめられている。

www.youtube.com

ログミーにもまとめられているので、英語に弱い私でも理解できた。
本記事で気になったらぜひ読んで、見てほしい。

logmi.jp

組織の一人として

WHYがない現場

以前より仕事で採用に関わることが増えてきた。 そこで気づいた問題は誰を採用するのか、この人は採用基準に満たしているのかを判断できる軸がないことである。
各々の中では採用基準があり、面接する上ですり合わせも行う。しかし、企業に明確なWHYがあれば面接官の間で基準が大きくブレることはないと思う。

似たような問題はあらゆるところでみられる。 受託開発のプロジェクトでいえば、エンジニアがなぜプロダクトを開発しているのかわかならい。
これは顧客の中のWHYを明確にし、何を作れば良いかを下ろしていく活動が不足しているためである。

評価の場合は、自分に付けられた評価の納得感がえられにくくなる。
基準が明確ではないため、評価基準が上司に強く依存してしまうからだ。

これらからWHYは企業の中で一つの基準となるものであると考えられる。
WHYがないと各所で下す決断、作る製品つまりWHATがぶれてしまい一貫性がなくなってしまうと感じた。

WHYを得るためにはどうすれば良いか

例え、組織のトップが明晰なWHYを持っていたとしても組織が大きく、WHYを伝えるHOWがなければ社員に広がらない。 そもそもCEOにWHYがなければ得ることはできない。

WHYがない状況で、エンジニアチームにできることはあるのだろうか。 私は自分たちでWHYを見つけ、広げていくしかないと思う。明確なWHYを見つけ、興味のある人に伝えていきたい。

エンジニアの一人として

エンジニアの役割

WHYからWHATまでの流れをプロダクト開発に当てはめて考えてみる。
WHYはマーケターや顧客(受託開発の場合)のようなプロダクトオーナーが考え、プロダクトバックログとして具体化する。エンジニアはプロダクトバックログをもとに開発して、WHYを実現するWHATの役割になる。

そのためエンジニアは正確かつ素早くプロダクトを実現することにコミットする。役割が明確で集中できるが、WHATに特化することがプロダクトのために最大限の価値を出しているのだろうか。

プロダクトの限界

私は(自分含めて)エンジニアがWHATに特化してしまうとタスク消化に追われやすくなるのではないかと感じる。 この場合消化以上のことをしないとプロダクトバックログがプロダクトの成長の上限となってしまう。

そもそもプロダクト開発は開始当初において不確実性が高く、不十分な情報の中かから作るものを決めている。 そのため、開発が進むにつれてわかることが多く、最初に策定したプロダクトバックログを変えていく必要が生まれることがある。

スクラムではそのためにスプリントレビューをしてプロダクトオーナーが修正していくものであると思っていたが、それでもプロダクトオーナーの裁量が上限となってしまう。 本当に価値あるものを作るためにはエンジニアもWHYの視点に立って開発することが求められるのではないか。

WHYと向き合う開発

そこで、ある案件でエンジニアとしての役割を持ちつつ、自分にWHYの視点を入れようとした。
よりよい開発になるとわかっているが、実践しようと思うととても難しい。そもそも書籍内にもある通り使う頭の機能が異なるように感じる。

私が試したときは時間を区切って頭を切り替える必要があった。 開発しているときは詳細の目で一切のブレも許さず正確に手を動かしていく。 ユーザー目線に立つときは俯瞰の目でユーザーの思考を想起させながら作っているものを触り、欲しい機能や修正したいポイントをプロダクトバックログに積んでいく。 この開発を全員とは言わず、一部エンジニアが取り組むだけでプロダクトは大きく変わるのではないかと思うし取り組んでいきたい。

まとめ

  • 「WHYからはじめよ!」をよんだ
  • 組織にWHYがないとどうなるかを思考してみた
  • エンジニアとしてWHYの視点を持つことの重要性を感じた

DjangoのORMでちょっとしたSQLをかいた

この記事はDjango Advent Calendar 2018 15日目の記事です。

qiita.com

はじめに

最近色々な勉強会でORMの是非について話を聞きます。 もともと自分はDjangoで単純なSQLしか書いたことがなかったのできちんと議論できませんでした。
そこで、今回はクエリ中心にちょっとしたSQLDjangoのORMで実装してみます。

動作環境

Dockerについて

SQLを試してみるにあたって、ローカルのPCのデータベースをセットアップすることは面倒です。
そのため、データベースやPythonのランタイムはDocker Composeを使ってセットアップしています。

実際のDockerfileは以下

FROM python:3.7

ENV PYTHONUNBUFFERED 1
ENV DOCKERIZE_VERSION v0.6.1
RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && \
    tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && \
    rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz

WORKDIR /code/
COPY Pipfile* ./
RUN pip install pipenv && \
         pipenv install --system --deploy

ADD . /code

Docker Compose でマイグレーションと初期データの設定を行なっています。

version: "3"

services:
  db:
    image: postgres:10
    environment:
      POSTGRES_DB: $POSTGRES_DB
      POSTGRES_PASSWORD: $POSTGRES_PASSWORD
    env_file:
      - .env
  backend:
    build: .
    command: bash -c "dockerize -wait tcp://db:5432 && pipenv install --system --dev && python3 manage.py migrate && python3 manage.py loaddata init.json && python3 manage.py runserver 0.0.0.0:8000"
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    working_dir: "/code/orm"
    env_file:
      - .env
    depends_on:
      - db

コードはリポジトリにて公開しています。

github.com

準備

テーブルはブログを題材に作りました。モデル定義は以下になります。

from django.db import models


class Post(models.Model):
    title = models.CharField(max_length=255)
    text = models.TextField()


class Comment(models.Model):
    post = models.ForeignKey('Post', on_delete=models.CASCADE)
    text = models.TextField(blank=True, null=True)

ダミーデータはフィクスチャで準備し、各検証はDjango Extensions を使いました。

試すクエリ

以下の5つのクエリを試してみたいと思います。

  • 1テーブルから全データを取得
  • 外部キー含めたデータを取得
  • カラムに条件指定して取得する
  • Case式 を使った分岐
  • Countによる集計

1. テーブルから全データを取得

Postテーブルから記事データを全て取得してみます。
DjangoのORMでは all メソッドで全データを取得することができます。

>> posts = Post.objects.all()
>> posts.values()                                                                                                                                                                          
<QuerySet [{'id': 1, 'title': 'PyConに行ってきた', 'text': '楽しかった'}, {'id': 2, 'title': 'Djangoチュートリアル', 'text': '投票アプリの構築までできた'}]>

>>  print(posts.query) 
SELECT "blog_post"."id", "blog_post"."title", "blog_post"."text" FROM "blog_post"

2. 外部キー含めたデータを取得

Commentと外部キーであるPostを併せて取得してみます。

>> comments = Comment.objects.select_related().all()
>> comments.values()
 <QuerySet [{'id': 1, 'post_id': 1, 'text': '一番よかったセッションは?'}, {'id': 2, 'post_id': 1, 'text': '懇親会はどうだった?'}, {'id': 3, 'post_id': 2, 'text': '自分は難しくてできなかった'}]>

>> [comment.post.title for comment in comments] 
 ['PyConに行ってきた', 'PyConに行ってきた', 'Djangoチュートリアル']

>> print(comments.query)
SELECT "blog_comment"."id", "blog_comment"."post_id", "blog_comment"."text", "blog_post"."id", "blog_post"."title", "blog_post"."text" 
FROM "blog_comment" 
INNER JOIN "blog_post" 
ON ("blog_comment"."post_id" = "blog_post"."id")

3. カラムに条件指定して取得する

特定の条件下のデータを取得してみます。

>>  pycon_comment = Comment.objects.filter(post__id=1)
>>  pycon_comment
 <QuerySet [<Comment: Comment object (1)>, <Comment: Comment object (2)>]>

>> print(pycon_comment.query)
SELECT "blog_comment"."id", "blog_comment"."post_id", "blog_comment"."text" 
FROM "blog_comment" 
WHERE "blog_comment"."post_id" = 1

4. CASE式を使った分岐

SQLのCASE-WHENを使った例です。
Postにを新しくジャンルというカラムを作って分類してみます。

>> genre_post = Post.objects.annotate( 
...:     genre=Case( 
...:             When(title__contains='Django', then=Value('Django')), 
...:             default=Value('Python'), 
...:             output_field=CharField()) 
...: ).values_list('title', 'genre')    
>> genre_post                                                                                                                       
<QuerySet [('PyConに行ってきた', 'Python'), ('Djangoチュートリアル', 'Django')]>

>> print(genre_post.query)
SELECT "blog_post"."title", 
  CASE WHEN "blog_post"."title"::text LIKE %Django% 
  THEN Django 
  ELSE Python 
END AS "genre" 
FROM "blog_post"

5. Countによる集計

記事についているコメント数を集計してみます。
aggregate メソッドは dict を返します。

>>  Comment.objects.aggregate( 
...:     pycon=Count('pk', filter=Q(post__id=1)), 
...:     django=Count('pk', filter =Q(post__id=2)) 
...:   )      
{'pycon': 2, 'django': 1}

まとめ

Case式やCountのオブジェクトが用意されているのは初めて知りました。
Upsertみたいな更新系もやってみたいです。