雑食日誌

PythonとJS。ときどきチーム開発

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みたいな更新系もやってみたいです。

The Agile Guildでのチーム開発

これはThe Agile Guild(TAG) Advent Calendar 2018 6日目の記事です。

qiita.com

The Agile Guild(TAG)に参加して取り組んだプロジェクトについて振り返りたいと思います。

自己紹介

私は元々Pythonメインのサーバーサイドエンジニアで DjangoによるAPIを実装したりしてました。
半年前くらいにでVue.jsに携わる機会があり、TAGではフロントエンドに多く関わらせていただいてます。 フロントエンド開発楽しいです。

TAGで関わってるプロジェクトについて

プロジェクトではIonicを使ったモバイルアプリのフロントエンドを担当しています。
メンバーはエンジニアとしてTAGから3人、POやマネージャーが一人づつのチームです。

TAGメンバーは複業のため、リモートかつ活動する時間帯も異なっています。
なので、情報の同期に遅れがあり、スピーディーにやりとりできない環境でとても難しいです。
開発サイクルは1スプリント1週間で、毎週月曜日にふりかえりをやってます。

ジョインしてからの課題

プロジェクトの参加当初はスムーズに開発に入れずに悩んでいました。
言語化すると以下のような課題があったように思えます。 (今も全て解決しているわけではないですが)

  • 開発プロセスがわからず、進められない
  • 他のチームメンバーの状況が見えにくい
  • 一つのフューチャーに対してどこまで開発すればいいかわからない
  • 知識の偏りがある(マネージャーがIonicに一番詳しかった)

モヤモヤしながら動けないまま数スプリントを過ごしてしまいました。

カイゼン活動

そんな中で他のTAGメンバーやマネージャー主体で、以下のカイゼン活動がなされました。

リモート朝会ではその日に稼働できるかや作業状況を共有する仕組みです。
開発プロセスの整理ではアーキテクチャからGitの運用フローなど細かいところまで決め事を作ってもらいました。

チームビルディングではドラッガー風エクササイズを使って、お互いの思いを共有しました。
そこからチームに足りていない役割やタスクを洗い出して、誰がやるかをざっくり定めていました。
これらのカイゼンはとても参考になっていて、本業の方でも導入したりしています。

また、AWS環境構築やサーバー開発など含めてリモートモブプロ会をよく開催しています。
作業のノウハウをチームに共有することで、いろんな人が対応できるようになるのが面白いです。

できていないこと・今後やりたいこと

できていないが今後やりたいことは以下になります。

  • チームに不足している役割や宙に浮いているタスクを積極的に巻き取ること
  • フロントエンドだけでなくバックエンドやインフラ周りの知識を得ること
  • 本業の忙しさに負けないこと

まだ、課題が多くやらなければいけないことがありますが、カイゼンしながら開発したいと思います。
頑張って年内にリリースしたいです。

VueにAmplifyを入れてみた

この記事はVue.js #3 Advent Calendar 2018の4日目の記事です。
フロントエンドのライブラリかつAWSのリソースを作ることができるツールとして話題のAmplifyをVuejsに組み込んでみたいと思います。

Amplifyとは

AmplifyはAWSリソースとの接続を支援するオープンソースJavaScriptライブラリです。
強力なCLIツールが提供されていて、AWSリソースの作成をコマンド上で実行し、ライブラリに反映することができます。 現在はCognitoによる認証や、AppSyncを用いたGraphQLの利用がサポートされています。

The foundation for your cloud-powered mobile & web apps

CLIのインストール

インストールはNPMで行います。

$ npm i -g @aws-amplify/cli

下記コマンドで詳細が出力されたらインストール成功です。

$ amplify

Vueプロジェクトの初期化

Amplifyの初期化はプロジェクトのルートディレクトで実行することを推奨されているため、先にVueのプロジェクトを作成します。 実行はVue CLIを用いています。

keinuma% vue create amplify-vue-demo 

Vue CLI v3.1.3
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, Linter
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript for auto-detected polyfills? Yes
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: ESLint
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In package.json
? Save this as a preset for future projects? No

Amplifyの初期化

Amplify CLIを用いてAWS環境の初期化を行います。

$ amplify init

実行すると開発環境およびプロジェクトの情報を渡してあげます。

? Choose your default editor: Visual Studio Code
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using vue
? Source Directory Path:  src
? Distribution Directory Path: dist
? Build Command:  yarn build
? Start Command: yarn serve

Amplifyは現状CloudFormationから各リソースを作成するため、認証情報を教えないといけません。
AWS上でユーザーを作成するか聞かれるので、専用のユーザーを用意する場合はYesを選択しましょう。
リージョンとアカウント名を入力するとIAM作成画面に遷移し、ポリシーの設定を行います。

Using default provider awscloudformation
AWS access credentials can not be detected.
? Setup new user Yes
Follow these steps to set up access to your AWS account:

Sign in to your AWS administrator account:
https://console.aws.amazon.com/
Press Enter to continue

Specify the AWS Region
? region:  ap-northeast-1
Specify the username of the new IAM user:
? user name:  amplify-demo
Complete the user creation using the AWS console
https://console.aws.amazon.com/iam/home?****

ユーザーを作成したら、コンソールに戻ってクレデンシャル情報を入力します。
Access Key IDおよびSecret Access Keyを入力したら、Amplify上で管理するための名前をつけることができます。

Press Enter to continue
Enter the access key of the newly created user:
? accessKeyId:  A****************
? secretAccessKey:  *******************************
This would update/create the AWS Profile in your local machine
? Profile Name:  demo

Successfully set up the new user.

認証情報の設定が完了すると改めて、ユーザー選択を行うので先ほど作ったdemoを選択します。

? Do you want to use an AWS profile? Yes
? Please choose the profile you want to use demo
⠇ Initializing project in the cloud...

CREATE_IN_PROGRESS amplifyvuedemo-20181126103438 AWS::CloudFormation::Stack Mon Nov 26 2018 10:34:38 GMT+0900 (日本標準時) User Initiated
CREATE_IN_PROGRESS UnauthRole                    AWS::IAM::Role             Mon Nov 26 2018 10:34:42 GMT+0900 (日本標準時)
CREATE_IN_PROGRESS DeploymentBucket              AWS::S3::Bucket            Mon Nov 26 2018 10:34:42 GMT+0900 (日本標準時)
CREATE_IN_PROGRESS AuthRole                      AWS::IAM::Role             Mon Nov 26 2018 10:34:42 GMT+0900 (日本標準時)
CREATE_IN_PROGRESS AuthRole                      AWS::IAM::Role             Mon Nov 26 2018 10:34:43 GMT+0900 (日本標準時) Resource creation Initiated
CREATE_IN_PROGRESS UnauthRole                    AWS::IAM::Role             Mon Nov 26 2018 10:34:43 GMT+0900 (日本標準時) Resource creation Initiated
CREATE_IN_PROGRESS DeploymentBucket              AWS::S3::Bucket            Mon Nov 26 2018 10:34:44 GMT+0900 (日本標準時) Resource creation Initiated
⠹ Initializing project in the cloud...

CREATE_COMPLETE UnauthRole       AWS::IAM::Role  Mon Nov 26 2018 10:35:02 GMT+0900 (日本標準時)
CREATE_COMPLETE AuthRole         AWS::IAM::Role  Mon Nov 26 2018 10:35:02 GMT+0900 (日本標準時)
CREATE_COMPLETE DeploymentBucket AWS::S3::Bucket Mon Nov 26 2018 10:35:05 GMT+0900 (日本標準時)
⠸ Initializing project in the cloud...

CREATE_COMPLETE amplifyvuedemo::AWS::CloudFormation::Stack Mon Nov 26 2018 10:35:09 GMT+0900 (日本標準時)
✔ Successfully created initial AWS cloud resources for deployments.

Your project has been successfully initialized and connected to the cloud!

Some next steps:
"amplify status" will show you what you've added already and if it's locally configured or deployed
"amplify <category> add" will allow you to add features like user login or a backend API
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

Pro tip:
Try "amplify add api" to create a backend API and then "amplify publish" t

CloudFormationのStack、デプロイ用のバケットが作成されると初期化は完了になります。
実行完了後に以下のファイルが作成されています。

  • .amplifyrc
  • amplify/

.amplifyrcはCloudFormationで作成されたリソース情報が記載されていて、amplifyディレクトリ以下にはプロジェクトの情報やリソースの詳細が格納されています。

Cognitoの作成

AmplifyでCognitoユーザープールを作成してみたいと思います。
addコマンドでユーザープールの設定を行います。 認証方法はEmailで、パスワードの強度などをカスタマイズしています。 IdeintityプールはCognitoからAPI以外の他のAWSリソースに紐づけるときに使用します。
例えばS3へのアップロードを許可する場合などですね。

$ amplify add auth
Using service: Cognito, provided by: awscloudformation
 The current configured provider is Amazon Cognito.
 Do you want to use the default authentication and security configuration? No, I will set up my own configuration.
? Select the authentication/authorization services that you want to use: User Sign-Up, Sign-In, connected with AWS IAM controls (Enables per-user Stor
age features for images or other content, Analytics, and more)
? Please provide a friendly name for your resource that will be used to label this category in the project: amplifyauthdemo
? Please enter a name for your identity pool. amplifyvuedemo96530290_identitypool_96530290
? Allow unauthenticated logins? (Provides scoped down permissions that you can control via AWS IAM) No
? Do you want to enable 3rd party authentication providers in your identity pool? No
? Please provide a name for your user pool: amplifyvuedemo96530290_userpool_96530290
? Multifactor authentication (MFA) user login options: OFF
? Email based user registration/forgot password: Enabled (Requires per-user email entry at registration)
? Please specify an email verification subject: パスワード再設定
? Please specify an email verification message: 認証コードはこちらになります。 {####}
? Do you want to override the default password policy for this User Pool? Yes
? Enter the minimum password length for this User Pool: 8
? Select the password character requirements for your userpool: Requires Lowercase, Requires Uppercase, Requires Numbers
? Userpool users are created with a standard set of attributes defined by the OpenID Connect specification.  Mark the required attributes below:
? Specify the app's refresh token expiration period (in days): 30
? Do you want to specify the user attributes this app can read and write? Yes
? Specify read attributes: Email
? Specify write attributes:
Successfully added resource amplifyauthdemo locally

Some next steps:
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

作成したCognitoをAWSに反映してみる。 init時に作成したCloudFormationにネストしてリソースを作成していることがわかります。

$ amplify push
| Category | Resource name   | Operation | Provider plugin   |
| -------- | --------------- | --------- | ----------------- |
| Auth     | amplifyauthdemo | Create    | awscloudformation |
? Are you sure you want to continue? true
⠙ Updating resources in the cloud. This may take a few minutes...

UPDATE_IN_PROGRESS amplifyvuedemo-20181126103438 AWS::CloudFormation::Stack Mon Nov 26 2018 11:16:29 GMT+0900 (日本標準時) User Initiated
CREATE_IN_PROGRESS authamplifyauthdemo           AWS::CloudFormation::Stack Mon Nov 26 2018 11:16:34 GMT+0900 (日本標準時)
CREATE_IN_PROGRESS authamplifyauthdemo           AWS::CloudFormation::Stack Mon Nov 26 2018 11:16:36 GMT+0900 (日本標準時) Resource creation Initiated
⠼ Updating resources in the cloud. This may take a few minutes...
...
✔ All resources are updated in the cloud

完了するとCognitoが作成されていることを確認できます。
また、パスワードの設定などがカスマイズされていることも確認できます。

プロジェクトの src/aws-exports.js にユーザープールのIDが保存されます。

AppSyncの作成

続けてAppSyncを作成してみます。
認証方法は先ほど作成したCognitoを用いたいと思います。

$ amplify add api
? Please select from one of the below mentioned services GraphQL
? Provide API name: amplifyvuedemo
? 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? No
? Do you want a guided schema creation? true
? What best describes your project:
? What best describes your project: One-to-many relationship (e.g., “Blogs” with “Posts” and “Comments”)
? Do you want to edit the schema now? No
? Press enter to continue

GraphQL schema compiled successfully. Edit your schema at ***/amplify-vue-demo/amplify/backend/api/amplifyvuedemo/schema.graphql
Successfully added resource amplifyvuedemo locally

Some next steps:
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

今回はSchemaを定義せずにチュートリアルで用意されているブログ用のスキーマを選択しました。
チュートリアルではデータソースにDynamoDBが使われているので、AppSyncと直接同期することができます。
完了するとamplify/backend/api/{API name}/build/schema.graphqlが作成されて、スキーマが定義されます。

schema.graphql

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")
}

Resolverが amplify/backend/api/{API name}/build/resolver 以下に格納され、リソースの詳細情報が保存されています。 作成したAppSyncをAWSに反映しましょう。

$ amplify push
| Category | Resource name   | Operation | Provider plugin   |
| -------- | --------------- | --------- | ----------------- |
| Api      | amplifyvuedemo  | Create    | awscloudformation |
| Auth     | amplifyauthdemo | No Change | awscloudformation |
? Are you sure you want to continue? true

GraphQL schema compiled successfully. Edit your schema at ***/amplify-vue-demo/amplify/backend/api/amplifyvuedemo/schema.graphql
? Do you want to generate code for your newly created GraphQL API (Y/n)
? Do you want to generate code for your newly created GraphQL API Yes
? Choose the code generation language target
? Choose the code generation language target javascript
? Enter the file name pattern of graphql queries, mutations and subscriptions (src/graphql/**/*.js)
? 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 (Y/n)
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes

⠇ Updating resources in the cloud. This may take a few minutes...

UPDATE_IN_PROGRESS amplifyvuedemo-20181126103438 AWS::CloudFormation::Stack Mon Nov 26 2018 11:34:52 GMT+0900 (日本標準時) User Initiated
UPDATE_IN_PROGRESS authamplifyauthdemo           AWS::CloudFormation::Stack Mon Nov 26 2018 11:34:57 GMT+0900 (日本標準時)
...

✔ Generated GraphQL operations successfully and saved at src/graphql
✔ All resources are updated in the cloud

GraphQL endpoint: https://***.appsync-api.ap-northeast-1.amazonaws.com/graphql

言語を選択してコード生成ができます。今回はJavaScriptを選択しています。

完了するとプロジェクトに以下のファイルが生成されています。

  • src/graphql
  • .graphqlconfig.yml

.graphqlconfig.ymlはAppSyncの情報が格納されています。
src/graphql は定義されているクエリやミューテーションが文字列として格納されている。 使用するときにクエリを選ぶことができる。

Vueパッケージをインストール

Vueのライブラリとして使用するためのパッケージをインストールする

$ npm i aws-amplify aws-amplify-vue

これでAmplifyをライブラリとして使う準備ができました。

ユーザーの認証画面を作成する

AmplifyにはいくつかUIが用意されています。
認証では Authenticator コンポーネントを組み込むことで認証フローの実装をスキップできます。

認証情報はストア上で行うため、先にストアの設定を行います。

store.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    user: null
  },
  mutations: {
    setUser(state, user) {
      state.user = user
    }
  },
  actions: {
  }
})

認証情報はページ遷移ごとにチェックする必要があるため、ルーティングに認証チェックフローを実装します。
Vue Routerと一緒にAmplifyのライブラリのインポートを行います。

router.js

import Vue from 'vue'
import Router from 'vue-router'
import Home from '../views/Home.vue'
import AmplifyStore from '../store'
import { AmplifyEventBus, AmplifyPlugin, components } from 'aws-amplify-vue'
import * as AmplifyModules from 'aws-amplify'

Vue.use(Router)
Vue.use(AmplifyPlugin, AmplifyModules)

認証状態がパスワードチェック前なのかサインイン前なのかなどをEventBusで確認します。

router.js

let user;

getUser().then((user) => {
  if (user) {
    router.push({path: '/'})
  }
})

AmplifyEventBus.$on('authState', async (state) => {
  if (state === 'signedOut'){
    user = null;
    AmplifyStore.commit('setUser', null);
    router.push({path: '/auth'})
  } else if (state === 'signedIn') {
    user = await getUser();
    router.push({path: '/'})
  }
});

function getUser() {
  return Vue.prototype.$Amplify.Auth.currentAuthenticatedUser().then((data) => {
    if (data && data.signInUserSession) {
      AmplifyStore.commit('setUser', data);
      return data;
    }
  }).catch(() => {
    AmplifyStore.commit('setUser', null);
    return null
  });
}

ルーティング前にユーザーが設定されているかで、ログイン画面にルーティングするかを決める処理を実装します。

router.beforeResolve(async (to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    user = await getUser();
    if (!user) {
      return next({
        path: '/auth',
        query: {
          redirect: to.fullPath,
        }
      });
    }
    return next()
  }
  return next()
})

Amplifyが用意しているAuthenticatorコンポーネント/auth に宣言します。

const router = new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/auth',
      name: 'auth',
      component: components.Authenticator
    }
  ]
})

ローカル上で起動し http://localhost:8080/auth にアクセスするとログイン画面にルーティングされます。

f:id:keinumata:20181203235853p:plain

これで認証機能を作成することができました。
Authenticator コンポーネントはストアのユーザー情報とCogntioにリクエストした認証状態を確認し、適切なルーティングを行ってくれます。

GraphQLにリクエストしてみる

AppSyncのリソースと同時に作成された src/graphql/ を使うことでリクエストを簡単に実装できます。 はじめに必要なライブラリをインポートしましょう。

import { API, graphqlOperation } from 'aws-amplify'
import * as mutaions from '@/graphql/mutations'

メソッドにGraphQLへのリクエストを持ったコンポーネントを実装します。

export default {
  name: 'CreatePost',
  data: function() {
    return {
      postData: {
        author: '',
        content: ''
      },
      myPost: ''
    }
  },
  methods: {
    createPost: function () {
      API.graphql(graphqlOperation(mutaions.createPost, this.postData ))
        .then((response) => {
          console.log(response)
          this.myPost = response
        })
        .catch((err) => {
          console.log(err)
        })
    }
  }
}

graphqlOperation に使用するクエリとパラメータを指定したオブジェクトをAPI.graphql 関数に渡しています。 クエリを変更するだけで、リクエストを変えることができます。

実装の全体はGitHubリポジトリを作成しているのでこちらをご確認ください。

github.com

まとめ

AmplifyをVueに組み込んでみました。
CLIAWSリソースを作るのは便利ですが、CI/CDとの兼ね合いが難しそうです。

VueのAmplify UIはReactに比べてまだデザインの拡張性は難しいですが、簡単にAWSとの連携を実装できます。
AmplifyはOSSなので欲しい機能などはPRを出してみたいです。

参考:

Vue