ぴよまるの雑多な技術所

持病で休職中のエンジニアです。ReactとPythonで自分のサービス作り、業務系を普段はやりたくwindowsをいじっています。ORの研究開始。

Serverless Frameworkを使ってLambda + API GatewayのプロジェクトをProduction / Developmentに分けて開発する

概要

Lambda + API GatewayでSPAからAPI Gateway経由でAPIを叩くという構成の時のServerless Frameworkの設定についてまとめます。 長くなりそうなので今回はフロントエンド側のServerless Frameworkの設定内容と、最初のデプロイについてに絞りました。

構成

Lambda, API Gateway, S3, CloudFront

Serverless Frameworkの設定(フロントエンド)

S3、CloudFrontを自動で作成するという内容でyamlを書きます。 S3のバケットポリシー、CloudFrontのhttpsの設定もしています。

serverless.yml

service: test-serverless #ここは自由に名前を入力する

provider:
  name: aws 
  region: ap-northeast-1
  stage: ${opt:stage, self:custom.defaultStage}
  # profile: (もしProfileでアカウントをわけていればここに入力)

custom:
  defaultStage: development
  env:
    development:
      siteName: ${self:provider.stage}.${self:service}

resources:
  Resources:
    StaticContentsS3:
      Type: AWS::S3::Bucket
      DeletionPolicy: Retain
      Properties:
        BucketName: ${self:custom.env.${self:provider.stage}.siteName}
        AccessControl: Private
        WebsiteConfiguration:
          IndexDocument: index.html
          ErrorDocument: index.html
        Tags:
          - Key: Environment
            Value: ${self:provider.stage}

    StaticContentsS3Policy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket:
          Ref: StaticContentsS3
        PolicyDocument:
          Statement:
            - Effect: Allow
              Principal:
                CanonicalUser:
                  Fn::GetAtt:
                    - StaticCloudFrontOriginAccessIdentity
                    - S3CanonicalUserId
              Action: s3:GetObject
              Resource:
                Fn::Join:
                  - "/"
                  - - Fn::GetAtt:
                        - StaticContentsS3
                        - Arn
                    - "*"

    StaticContentsCloudFront: #CloudFrontの設定
      Type: AWS::CloudFront::Distribution
      Properties:
        DistributionConfig:
          Enabled: true
          Comment: "Delivery static contents"
          PriceClass: PriceClass_200 # CloudFrontを配置する場所
          DefaultRootObject: index.html
          CustomErrorResponses:
            - ErrorCode: 403
              ResponseCode: 200
              ResponsePagePath: /index.html
            - ErrorCode: 404
              ResponseCode: 200
              ResponsePagePath: /index.html
          Origins:
            - Id: S3Origin
              DomainName:
                Fn::GetAtt:
                  - StaticContentsS3
                  - DomainName
              S3OriginConfig:
                OriginAccessIdentity:
                  Fn::Join:
                    - "/"
                    - - origin-access-identity
                      - cloudfront
                      - Ref: StaticCloudFrontOriginAccessIdentity
          DefaultCacheBehavior:
            AllowedMethods:
              - HEAD
              - GET
            CachedMethods:
              - HEAD
              - GET
            Compress: true
            DefaultTTL: 900
            MaxTTL: 1200
            MinTTL: 600
            ForwardedValues:
              QueryString: true
              Cookies:
                Forward: none
            SmoothStreaming: false
            TargetOriginId: S3Origin
            ViewerProtocolPolicy: redirect-to-https
          ViewerCertificate:
            CloudFrontDefaultCertificate: 'true'

    StaticCloudFrontOriginAccessIdentity:
      Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
      Properties:
        CloudFrontOriginAccessIdentityConfig:
          Comment:
            Ref: AWS::StackName

  Outputs:
    StaticContentsCloudFrontUrl:
      Value:
        Fn::Join:
          - ""
          - - "https://"
            - Fn::GetAtt:
              - StaticContentsCloudFront
              - DomainName

デプロイ手順

まずはServerless Frameworkのデプロイコマンドを叩きます。こうするとCloudFormation経由でAWSリソースが作られます。

serverless deploy --stage development

次に出来上がったバケットに対して、フロントエンドのソースコードをアップロードします。詳細は割愛しますが、例えばNuxt.jsを使っている場合は「yarn generate」でできあがったdistの中のファイルをそのままアップロードすれば大丈夫です。

ここまでできるとCloudFrontのURLをブラウザから叩けばフロントエンドの動きが確認できます。

serverless deploy --stage production

という風にすれば、別の名前のS3バケットとCloudFrontができます。何か試すときはdevelopmentで試して、動作確認ができたらproductionにあげる…ということが簡単にできます。(普通はその間にstagingがありますが、今回は突然productionにしています。)

後編は後で書きます!

Pythonでテトリスを作ることでよく知らない分野の実装手順について考察する(3)反省編

ブログ記事を書く際にテトリスの実装を実際にしたわけですが、かなり時間を取られました。 どうしたら早く動かないところを動かすことができるか、web系ではないときのデバッグをどうするべきか少しわかったと思うので、メモしておきます。内容としてはどうやってデバッグしてきたかと、テトリスの実装手順をまとめています。

ぶつかった壁

  • なぜか全体的に動かない
  • テトリスが落ちてこない / 操作しても右に行かない などなど。こういうときにコードを眺めて、変えて、動かして…を繰り返していたら夜が明けました。 その中で試してよかったことと次からこうした方がコーディングが早くなるのではと思ったことを以下にまとめます。

やったこと

youtubeテトリスの実装動画をみて実装手順・ロジックを確認)

www.youtube.com

どうしても動かせずにこちらの動画を視聴しました。「3.ロジックの見直し」はこの動画での実装を参考にしています。 また、実装をしていく中でどの単位で動かすかの参考と、自分の考えたロジックの答え合わせにも使いました。

デバッグしやすい環境を整える)

VSCodeを使っているのですが、F10やF11キーを押すのがMacだとめんどくさいです。

blog.stedplay.com

こちらを参考にMacでもデバッグしやすい環境、つまりF11キーだけでステップイン実行できる環境を整えました。

( ロジックの見直し(テトリスが衝突したときの扱い))

テトリスが衝突したとき、メインのゲームクラスが持っているテトリミノの配列に新しいテトリミノを入れていました。テトリミノはスクリーンに即時反映します。つまり、描画処理はメインのゲームクラスが持っているスクリーンをprintするだけになります。 こうではなくて、テトリミノは常に1つだけで、壁に衝突したらスクリーンの一部にすることにしました。こうすると移動したときの描画が楽になります。ゲームクラスが持っているスクリーンとテトリミノを重ねて表示するだけなので。

反省

  • 実装は1つの処理ずつ行って、実装できたらすぐ動かして確認する
  • なるべく実装する手順をシンプルな単位に切り分けてから実装をする
    • 設計段階ではメソッドやオブジェクトを洗い出しているので、どの順序で実装するかよく考える
  • コードを目で追うのではなくデバッガで変数の中身を確認しながら追う

参考:テトリスの実装手順

(スクリーンの表示)

  1. スクリーンをfor文を使って描画する(配列のインデックスに注意)
  2. テトリミノの種類や回転をEnumなどで表現する
  3. スクリーンとテトリミノをコピーして描画するメソッドを作って動かしてみる

テトリスの落下)

  1. 1秒ごとにテトリスが落下するメソッドを用意し、結果を描画する

テトリスの操作)

  1. キーボード入力を受け付ける
  2. キーボード入力でテトリスの位置が移動することを確認する。この時点では壁にめり込む

(壁との衝突検知)

  1. テトリミノが壁に衝突したか判断するメソッドを用意し、衝突していたら移動しないようにする 実装内容としてはテトリミノの形状と、スクリーンの中でテトリミノを配置すべきところを1pxずつ確認して衝突していないか判断する

(テトリミノの固定)

  1. 落下させたとき、壁にぶつかったらテトリミノはスクリーンと同化する
  2. 同化したら次のテトリミノを生成する

(テトリミノの削除)

  1. テトリミノを1つずつ下に移動させて一番上の行を削除

Pythonで実装したLambda + API Gatewayで任意のエラーコードを返す

サーバレスな構成を作っている時、Lambdaのステータスコードを500で返してもAPI Gatewayで200に書き換わってしまうということがありました。その時は独自エラークラスを作って、マッピングテンプレートを設定してなどなど、大掛かりなことをしたのですが、マッピングテンプレートなしで任意のエラーコードを返す方法をまとめます。

全体図

LambdaがAPI本体、API GatewayAPIのエンドポイントになる構成です。

結論

先に結論を書くと、マッピングテンプレートは必要ありません。API GatewayのPOSTの「統合リクエスト」は「Lambda プロキシ統合の使用」のまま、Lambda側でエラー用のjsonをreturnすれば大丈夫です。

参考記事 https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/handle-errors-in-lambda-integration.html

以下に具体的なコードを載せます。POSTで{'test': '何か文字列'}という値を送信するという想定です。

Lambda

import json

def lambda_handler(event, context):
    body = json.loads(event['body'])#例えばbodyを読み込んで

    if (len(body['test'])>10000):#文字が長すぎたら500エラーを返す
      return {
            'errorType': "InternalServerError",
            'httpStatus': 500,
      }
   #文字が長すぎなければ200のレスポンス
    return {
        'statusCode': 200,
        'isBase64Encoded': False,
        'body': 
            json.dumps({'result': 'OK!'})
        ,
        'headers': {
            "Content-Type" : "application/json",
            "Access-Control-Allow-Origin" : "*"
        }
    }

API Gateway

これから記事を書く予定の記事のserverless.yamlの設定をコピペすれば動きます。書いたらリンクをはります。 おそらく「OPTIONS」と「POST」の2つのリソースができていると思いますが、「OPTIONS」はpreflightレスポンスに使います。「POST」の方が実際にLambdaにリクエストを送るリソースで、細かいマッピングは「統合リクエスト」の中の「Lambda プロキシ統合の使用」にチェックをするとよしなに行ってくれるため、マッピングテンプレートが不要です。

f:id:let_piyomaru:20200310105435p:plain
API Gatewayの設定
私の環境だと「POST」の方は「メソッドレスポンス」にステータスコードを登録する必要すらありませんでした。 留意点としては、Lambda側で返すjsonの項目がAWSによって定められています。その形さえ間違わなければ動くはずです。

Pythonでテトリスを作ることでよく知らない分野の実装手順について考察する(2)実装編

今回は後編として、実装したコードについて書きます。 前回の記事は以下です。

let-piyomaru.hatenablog.com

リポジトリは以下です。

github.com

実装する際にどういう手順で取り掛かるかも悩んだので、私が実際に考えた順に記事を書きます。内容としてはクラス設計、実装を悩んだ箇所の調査、実際の実装という順番です。 ちなみにまだリポジトリのコードは動きません。とりあえず動いたら追記します。

クラス設計

ゲームクラス

  • フィールド
    • スクリーン
    • テトリミノ
    • 次に落下するテトリミノ
  • メソッド
    • 初期化
    • テトリミノの衝突判定(衝突していたら固定)
    • 1行埋まったかの判定
    • 次に落下するテトリミノの生成(種類・回転をランダムに決定)
    • ゲームオーバー判定(次に落下するテトリミノが入りきらない時)
    • メインからゲームループの中で呼ばれる処理(ゲームの流れの管理)
    • スクリーンの描画処理

テトリミノクラス

  • フィールド
    • x座標
    • y座標
    • 回転の種類
    • テトリミノの種類
  • メソッド
    • テトリミノの左右移動
    • テトリミノの下移動

シーケンス設計

初期化

  • スクリーンの初期化
  • テトリミノの初期化
  • スクリーンの表示
  • 次に落下するテトリミノの生成(種類・回転をランダムに決定)
  • 左右と回転

ゲームループの中

  • テトリミノの衝突判定
    • 衝突していたら次に落下するテトリミノの生成
    • していなければテトリミノのy座標を1つ落とす
  • キー入力があれば動かせるか判定してテトリミノの操作
  • ゲームオーバー判定
  • スクリーンの描画処理

詰まった実装

Pythonでキー入力を受け取る

  • pip install readchar
import readchar
while True:
    kb = readchar.readchar()
    if kb == "q":
        break
    else:
        print(kb)

(注意)上記の実装だとキー入力があるまでループが止まります

キー入力で待たない

こちらの記事のコードをお借りしました。

Pythonでキー入力待ちをしないキー入力 - Qiita

(だいたい)1秒ごとに描画ループ

import time
while True:
    # 何か処理
    time.sleep(1)

Pythonでテトリスを作ることでよく知らない分野の実装手順について考察する(1)設計編

今年はコーディング力向上を目標にしており、いろんなジャンルのコードを自力でゴリゴリ書こうと思っています。理由としては普段自分で考えて実装する時間が少なくコンプレックスだからです。 まずは落ちものゲームの代表であるテトリスPythonでゴリゴリ書いてみました。 前提として、私はゲーム系のプログラミングの常識を知らず、そもそもテトリスで遊んだことがありません。実装を通してよく知らない分野の実装手順について考察します。 今回は前編として、実装前の設計と事前調査部分についてまとめます。

リポジトリは以下です。

github.com

テトリスとは

上からテトリミノというブロックが落ちてきて、横一列揃うとブロックが消えるゲームです。

テトリスの仕様

ネット上で調べたところ、下記のような項目にまとめられそうでした。

  • できる操作はテトリミノの左右移動、落下速度をあげるの3つ
  • テトリミノの種類は7種類で、90°の回転でそれぞれ4パターンずつ
  • テトリミノは縦横最大4*4
  • 横1列揃ったらその行のブロックを消す
  • ゲームスクリーンは横6、縦20(一般的ではなく今回向けの仕様)

実装方針

  • ゲーム画面は配列で管理する
  • テトリミノはクラスのインスタンスとして管理する
    • 自分の座標、回転の種類、状態(非表示、移動中、停止)をもつ
  • テトリミノの管理は配列を用いる
  • 描画とできる操作は関数として定義する

まとめ:よく知らない分野の実装手順(設計編)

  1. 実装対象について実際に動かして仕様を調べてみる
  2. 実装対象の分野(今回ならゲーム)の常識を調べる(今回の例なら以下のようになる)
    • リアルタイムのゲーム系は無限ループを回しておく
    • リアルタイムのゲーム系は無限ループの中で状態更新、画面描画を繰り返す
    • ゲーム盤面やパズルパーツを配列で管理する
    • キーボード入力のとり方
  3. 実際のオブジェクトと操作を洗い出し、クラスと関数を書き出してみる
  4. 処理順序の確認をする(無限ループのなかで表示をするとか、状態更新いつするとか)

競技プログラミングデビューした

競プロデビューしたのでまとめます。

競技プログラミングとレーティングとは

与えられた問題を早く正確にとくあれです。よくいわれる「青色コーダー」とか「赤色コーダー」はAtCoder上のコンテストに参加するレートがつくので、それに付随した色をさします。 コンテストもレベルがあって、初心者はAtCoderのABCコンテストに出ます。週末の夜に開催していて、おそらく最初の数回は色がつきません(レーティングが上がるのゆっくりなので)。はじめたばかりで目指すのは茶色コーダー、水色までいけばアルゴリズムに関してはまあまあなスキルが担保されているような印象です。

参加方法

AtCoderに登録して、毎週日曜日の21時あたりにリアルタイムで参加します。

環境構築とかファイル管理まわり

私はPython使いですが、

pip3 install online-judge-tools
npm install -g atcoder-cli

こんな感じで、オンラインからテストケースとってきて良い感じに設定してくれるツールを使っています。自動でフォルダもコンテストごとに切られます。

//ログイン
acc login
//ログイン確かめる
acc session
//online-judge-toolsもログイン
oj login https://beta.atcoder.jp/

初期設定はこんな感じで、

//コンテストのディレクトリ作る
acc new abc101
//あとは対話式で解く問題を選択する

//テスト
oj t -c " python3 ./a.py" -d ./tests/

//submit
acc submit main.cpp

//次の問題
 acc add

こんな感じで使えます。 ファイル管理でいうと、今のところ、勉強する際は1つのリポジトリに全部のコンテストをフォルダごとにつっこんでいる感じです。書き捨てない方がいいです。あとで入出力まわりとか、やったことを見返すことがわりとある気がします。

問題の難しさについて

ABCコンテストしかわかりませんが、A問題は入出力できるかな程度、B問題はちょっとソーととかfor文での全探索が入った程度です。ここまでは言語に慣れているかどうかの話な気がします。 C問題からがちょっとアルゴリズムやデータ構造の知識がいるかなという難易度で、個人的には無勉強・無知識でとけるのはCまでかなという気がしました。Dはあんまりまだチャレンジしていません…。

勉強方法

有名な蟻本…は難易度が高いので、問題の種別を蟻本でざっとみたら、コンテストで過去問をとくみたいです。問題の種別というのは、例えばforループでとく全探索問題とか、DPとか、貪欲法とか、どんな考え方をどんな時に使うかという意味です。 サイトとしては

あたりが有名みたいです。tagsの方はカテゴリ別なので、何か1つのトピックの考え方を勉強して、過去問を集中的にとく…みたいな使い方ができそうです。

所感

競プロって楽しそうですよね。楽しいです。ただ勉強時間が沼なので、その辺の配分に悩んでいます。

フライングですが来年の目標を立ててみた

ぴよまるです。夜間大学に行き、業務でもフロントもバックエンドもやって、副業もして…という生活の中で、ふと自分は目標が曖昧すぎるという課題を感じたので、とりあえず具体的になるまで落としこむべく、「来年の目標」として言語化することにしました。

エンジニア関連

  • 資格取得

秋になったら、システムアーキテクトをとりたいです。あとはPythonの資格をとりたい。1つの言語をちゃんと勉強したことがなかったので、Pythonならわかるぞ!とできると、他の言語を学ぶ時にも役立つと思っています。

  • 個人サービスのリリース

エンジニアとして業務でサービスを作る経験が少ないので、もういっそ個人で出したい。具体的にはPythonでアイディアを出して、来年中にプロトタイプを公開する。過程をアウトプットする。

最近機械学習のコードを見る機会が増えてきましたがあんまりコードがかけません。なので1習慣に1つくらいテクニックを勉強してブログで発信します。来年終わる頃にはkaggleも怖くない程度の知識と実装力を身につけます。 あと特徴量エンジニアリングとか統計モデリングの本が積まれているので、これは読みます。読んで読書録を公開します。

それ以外

  • 英語のwritingスキルあげ

実は留学がしたいタイプの人間です。能力は足りていないのですが、writingは一番時間がかかるかつ一番重要と思っています。留学するとレポートもきっとかくので…。月に1つくらいwriting練習をします。

  • 数学力あげ

3年生になると数学っぽい授業がなくなります。数学の基盤が大事だと思って夜間大学に入学したので、位相や群論については少なくとも学習します。


結局何がしたいのか

決めなければいけないと思います。 興味だけで動いている方向をまとめると、きっと

  • エンジニアとしてちゃんとサービスを作る経験が欲しい、自信を持ちたい

  • その上でアカデミックよりに方向転換して、機械学習を仕事にしたい

の2点だと思います。 社会との関わりが希薄で社会課題を解決するとか、世の中を便利にするとか、そういうたいそうなやりたいことは見つかっていません。ふわっとしたやりたいことに対して具体的にアプローチすることで、本当にやりたいことを見つけるというのが来年の課題です。

おそらく、今日の記事通りにブログのカテゴリがつけられて、ちょっとずつ成長すると思います。言ったのでやらざるを得ません。