Software Engineer Blog

エンジニアブログです。技術情報(Go/TypeScript/k8s)や趣味の話も書くかもです。

1ヶ月たったので結果についてまとめる

概要

1ヶ月ほど前に記事を書きました。

midori5.hatenablog.com

1ヶ月たったので、どうだったかについてまとめたいと思います。

(再掲)やることリスト

1ヶ月前に公開したやることリストは下記になります。

  • OSS へ commit する
  • Recout をサービス公開する
  • LeetCode 60 問を全て解く
  • 本を毎日 10 ページは読む
  • (1 人旅をする)
  • (各種家庭向けの諸々)

結果

表にまとめるとこんな感じです。基本的に達成度は低いです。

内容 達成 達成度(%)
OSS へ commit する x 0
Recout をサービス公開する o 100
LeetCode 60 問を全て解く x 6
本を毎日 10 ページは読む x 10
(1 人旅をする) o 100
(各種家庭向けの諸々) x 20

詳細

Recout をサービス公開する

下記リンクにてβ版として公開しています。(細かいサービス紹介はまた別でやろうかと思います。)

recout.dev

サービス紹介

毎日のアウトプットを記録するアプリケーションです

機能

  • アカウント作成/ログイン機能
  • 任意の内容を記録
  • グラフでアウトプットをどの程度行っているか可視化
    • Pixela を利用させていただいております

注意

  • β版として出しています
  • ログインはGitHubアカウントを持っている方は可能です
  • エラーのアラート等仕込んでないので動作不良が検知できてないです

LeetCode 60 問を全て解く

全然解けなかったです。難しかったので、今後も地道に解いていきます。

所感

あまり1ヶ月で良い進捗が出せませんでした。
原因は色々とありそうですが、習慣化がうまくいかなかったにつきそうです。まだまだであることは明白なので、日々コツコツと伸ばしていきます。

Azure Pipelines から GAE へアプリケーションをデプロイ

目次

折りたたみ

概要

Azure Pipelines の build で CI を実行しており、そのままデプロイできないか試してみました。
実際にできたので方法を記載します。

Version

name version
google/cloud-sdk 252.0.0-alpine

内容

  • Azure Pipelines の Build 上でデプロイまで実行
  • azure-pipelines.yml に記載の script から実行
  • GCP の認証は google/cloud-sdk docker image を利用

詳細

基本的には azure-pipelines.yml に deploy stage を作成して job を実行してます。
vmubuntu を利用してます。

YAML schema - Azure Pipelines | Microsoft Docs

Secret

secret 情報は Azure pipelines 上の Variables に設定しています。
Web UI から設定することで、見れないように設定できます。

Variables - Azure Pipelines | Microsoft Docs

secret variables を利用するには、マッピングして環境変数にセットする必要があります。
secret には 2 つの値を設定します。

  1. GCP の Service Account の鍵ファイル(JSON)
    GCP 上からデプロイを実行するための Service Account を発行してください。
    Creating and managing service accounts  |  Cloud Identity and Access Management Documentation  |  Google Cloud
    秘密鍵情報が入った JSON ファイルがダウンロードできるので、ダウンロードします。
    この JSON ファイルの内容すべてを Secret Variables として Azure Pipelines へ登録してください。
    私は、 DEPLOY_KEY_FILE_PRODUCTION という名前にしてます。

  2. GCP のプロジェクト ID
    プロジェクト ID を登録してください。 PROJECT_ID_PRODUCTION として登録してます。

env にて同名の環境変数マッピングしています。

- stage: deploy
  # 前段のbuildが成功 and master branch の場合のみ実行する
  condition: and(succeeded(), eq(variables['build.sourceBranch'], 'refs/heads/master'))
  jobs:
    - job: app_engine_deploy
      pool:
        vmImage: "ubuntu-latest"

      steps:
        - script: |
            # ここに記載

          env:
            DEPLOY_KEY_FILE_PRODUCTION: $(DEPLOY_KEY_FILE_PRODUCTION)
            PROJECT_ID_PRODUCTION: $(PROJECT_ID_PRODUCTION)
            GCLOUD_VERSION: 252.0.0-alpine

Auth GCP

認証周りは docker image を利用します。
公式が出している image の google/cloud-sdk を利用します。

hub.docker.com

gcloud-config という名前で認証を実行するコンテナを作成します。

# Secret Variables に設定した秘密鍵情報をファイルに書き出す
echo $DEPLOY_KEY_FILE_PRODUCTION > /tmp/credential.json

# image を取得
docker pull google/cloud-sdk:${GCLOUD_VERSION}

# docker container 内で gcloud command を実行
docker run -v /tmp:/tmp   
  --name gcloud-config   
  google/cloud-sdk:${GCLOUD_VERSION}   
  gcloud auth activate-service-account --key-file /tmp/credential.json

Deploy GAE

先程作成した、 gcloud-config コンテナの Volume を再利用することで gcloud の認証済みとして各種コマンドを実行できます。
(Docker Hub の Usage に細かい部分は書いてあります。)

GAE の設定に secret.yml が必要で、うまく yml を variables に設定できなかったので、secret.yml を無理やり作ってます。

# XXX: secret.yml を無理やり作る
echo "
env_variables:
  DATASTORE_PROJECT_ID: ${PROJECT_ID_PRODUCTION}
" > ./secret.yml

# deploy
docker run -v $(pwd):/tmp/app   
  --volumes-from gcloud-config    # --volumes-from で認証済みの volumeを再利用
  google/cloud-sdk:${GCLOUD_VERSION}   
  gcloud --quiet --project ${PROJECT_ID_PRODUCTION} app deploy /tmp/app/app.yaml # コマンド実行

Remove Credential

秘密情報が入った volume や ファイルがあるので消しておきます。

# volumeごと消す
docker container rm -v gcloud-config
rm /tmp/credential.json
rm ./secret.yml

全体

azure-pipelines.yml 全体

- stage: deploy
  condition: and(succeeded(), eq(variables['build.sourceBranch'], 'refs/heads/master'))
  jobs:
    - job: app_engine_deploy
      pool:
        vmImage: "ubuntu-latest"

      steps:
        - script: |
            echo $DEPLOY_KEY_FILE_PRODUCTION > /tmp/credential.json
            docker pull google/cloud-sdk:${GCLOUD_VERSION}
            docker run -v /tmp:/tmp   
              --name gcloud-config   
              google/cloud-sdk:${GCLOUD_VERSION}   
              gcloud auth activate-service-account --key-file /tmp/credential.json

            echo "
            env_variables:
              DATASTORE_PROJECT_ID: ${PROJECT_ID_PRODUCTION}
            " > ./secret.yml

            # deploy
            docker run -v $(pwd):/tmp/app   
              --volumes-from gcloud-config    
              google/cloud-sdk:${GCLOUD_VERSION}   
              gcloud --quiet --project ${PROJECT_ID_PRODUCTION} app deploy /tmp/app/app.yaml

            docker container rm -v gcloud-config
            rm /tmp/credential.json
            rm ./secret.yml
          env:
            DEPLOY_KEY_FILE_PRODUCTION: $(DEPLOY_KEY_FILE_PRODUCTION)
            PROJECT_ID_PRODUCTION: $(PROJECT_ID_PRODUCTION)
            GCLOUD_VERSION: 252.0.0-alpine

所感

Azure Pipelines から無事に deploy できました。ただの Ubuntu なのでできると思ったので良かったです。
気になる点としては、このあたりです。

  • Azure Pipelines の Build で deploy を実行している
    • Release でやったほうが良い?
  • 実行時間がそこそこかかる
    • 実際は npm install / go build 等を実行している
    • Cache を活用して速くしたい

参考

1 ヶ月時間が空いたのでなにかする

概要

1 ヶ月ほど時間に余裕ができる期間ができたので、諸々スキルアップしたいと思っています。
やることリストを公開してプレッシャーを少しでもかけていきます。

やることリスト

このあたりを毎日地道にやっていきたいです。

  • OSS へ commit する
  • Recout をサービス公開する
  • LeetCode 60 問を全て解く
  • 本を毎日 10 ページは読む
  • (1 人旅をする)
  • (各種家庭向けの諸々)

詳細

各やりたいことの詳細です

OSS へ commit する

目的

OSS への commit を自然に行えるようになること

内容

OSS へ何かしら commit したいと思います。対象のめどはつけているのですが、 とりあえず commit するというところまで公開します。
あまり経験がない + いつも助けられているので少しでも貢献したい という 2 点がモチベーションです。

Recout をサービス公開する

目的

個人サービスを作りきって公開する経験を積みたいため

内容

Recout というサービスを公開しようと開発しています。

midori5.hatenablog.com

しっかりと公開して運用するところまで作りきれたことがないため、一度出してみたいと思っています。
(ここに記載した内容の日々の進捗を記録したいと思います)

LeetCode 60 問を全て解く

目的

データ構造、アルゴリズムについて実装ベースでの理解を深める

内容

下記記事に記載されている問題にチャレンジしようと思います。

1kohei1.com

元々、勉強不足でコンピュータサイエンス周りはとても弱いです。
本を読む or 講座を受ける 等も考えられましたが、おそらく私の学習スタイル的に 「問題を必死に解く」が最も良さそうなので選びました。(あと面白そうなので)

本を毎日 10 ページは読む

目的

本を読む習慣をつける

内容

本を読むのは好きなので、そこまで問題ではないのですが、あまり継続的に読んでなかったので、 ちょっとずつ読んでいこうかと思います。

所感

ちょっと色々あげすぎた感もありますが、一気にやるのではなく毎日コツコツと 続けていって達成できていれば良いかなぁと思います。

GitHubに草を生やすようにアウトプットを可視化するツールを作成している話

概要

recout という個人の学習を可視化するツールを作成しています。
(現状はアカウント登録部分が整備できていないので、URLは非公開にしてます。)

f:id:midori5:20190426195021p:plain
recout

できること

自分がしたアウトプットを投稿すると、GitHubのコミット履歴のように色が付きます。
色が付く部分は、 id:a-know さんが作成された Pixela をそのまま利用しています。
とても素晴らしいサービスで、リリースされた当時からなにか使いたいなぁと思っていました。
ありがとうございます!!!

blog.a-know.me

目的

基本的には、個人用として作成しています。
作ろうと思った動機ですが、継続的な学習ができなくなっていると感じたためです。 以前、Write Code Everyday を実施して無事1年達成できたのですが、一度途切れてしまうと 学習量が減ってしまったように感じていました。

Write Code Every Day (1年目) - Software Engineer Blog

私個人の話ですが、「継続的にやったことが見える化されること」が学習を続けるキーポイントかなと思っています。 commit だけではなくて、学習全般を記録できるようにすることで、継続的に学ぶ土台を作りたいと考えています。

今後の追加予定機能

構成

ソースコードは以下に置いています。

github.com

Front

Nuxt.js を利用していますが、必要なかったかなという感じです。 Vue.js は触ったことがあったので、学習としてNuxt.jsを導入してみましたが、 全く機能を使っていない状態です。

また静的サイトホスティングとして、 Netlify を利用しています。 (GAE上で良いのでは?と思っているので、まとめようかと考えています。)

Backend

GAE/Go で構築しています。学習の投稿データを溜める必要があったので、Datastoreを利用しています。 mercari/datastore を使うことで簡単にアクセスできました。

github.com

GAE自体ははじめて使ったのですが、サクッと作ってデプロイできる点が非常に良いと思いました。
また、Datastoreもテーブルを事前に作ることなく、データを追加できた点(追加されたデータをもとにEntityが作成される) が簡単でよかったです。

所感

まだまだ作り始めですが、とりあえず動くものができてよかったなという印象です。 いままでは途中で終わってしまうことが多かったので、ひとつ突破できたかと思います。
4月中にブログを書くことが目標だったので、URL公開には至りませんでしたが、引き続き開発を続けます。

Wire を活用した Web API パッケージ構成

概要

DI Tool であるWireを利用した際の、Web API のパッケージ構成について考えてみました。 また、導入するメリットについてもあわせて記載しております。

github.com

パッケージ構成

パッケージは下記の通りに組みました。ドメイン層込みのパッケージ構成に、provider パッケージを追加しています。
providerパッケージ配下に、wireを通してgenerateするコードを配置します。

パッケージ構成

.
└── wire
  ├── main.go        # package main
  ├── app            # アプリケーション層 (ロジック)
  ├── handler        # API Entrypoint
  ├── provider       # Dependency 管理
  ├── domain         # ドメインモデル 管理
  └── infra          # データ層の実装
      └── db         # DB系実装

依存関係

パッケージ間の依存関係を簡単な図にしてみました。 appパッケージの構造体は、providerを経由して、取得するようにしています。

f:id:midori5:20190203232238p:plain

ソースコード

github.com

wire を利用している部分のソースコードは下記のようになっています。 このあたりは、wire のチュートリアル 等からそのまま利用しています。
providerの役割は、appを依存関係を解決した上で生成することになります。
apphandlerから呼び出すので、下記のように実装します。

package handler

import (
    "io"
    "net/http"

    "github.com/gmidorii/api-design/wire/provider"
)

func Hello(w http.ResponseWriter, r *http.Request) {

    // providerよりapp を取得
    // Hello structにはすでに依存関係が注入されている
    hello, err := provider.InitHelloApp()
    if err != nil {
        return

    message, err := hello.GetMessage("id")
    if err != nil {
        return
    }

    io.WriteString(w, message)
}

handler以降は、 appdomaininfra/db の順に呼び出して、データを取得しています。
このあたりに関しては、一般的に考えられているものに沿っているのではないかと考えています。
参考

www.slideshare.net

メリット

wire を導入する前と後で、何が良くなったのかについてですが、2点あると考えています。

  1. 利用元に変更を加えることなくアプリケーション層の依存性の修正ができる
  2. 依存関係を作る処理をwireで自動生成することができる

1. 利用元に変更を加えることなくアプリケーション層の依存性の修正ができる

アプリケーション層の実装の際、テスタビリティを上げるため、interfaceを通して互いに依存させ、 実態をInjectするといった実装をします。 この際、コンストラクタインジェクションを利用して、依存性のInjectを実施します。
(Goの場合は、Newはじまり関数を利用して、structを生成します。
余談: ここでは、フィールドをパッケージプライベートにすることで、外部からはコンストラクタでのみ依存関係を制御するようにしています。)

app package

type Hello struct {
    user domain.UserRepository
}

func NewHello(user domain.UserRepository) Hello {
    return Hello{
        user: user,
    }
}

上記の実装の際に、アプリケーション層の仕様が変更され、依存を増やしたい(新しいデータを取りに行きたい)といったことは、 よくあるかと思います。 コードの変更は、依存をコンストラクタで受け取れるように修正し、受け取った依存性をフィールドにつめるようにします。

type Hello struct {
  user domain.UserRepository
  // 増やしたい依存
  book domain.BookRepository
}

// 引数を増やす
func NewHello(user domain.UserRepository, book domain.BookRepository) Hello {
    return Hello{
    user: user,
    booK: book,
    }
}

この変更を加えた場合、呼び出し元をすべて修正する必要があります。(コンストラクタの引数が変わったため)

before

user := db.NewUser()
hello, err := app.NewHello(user)

after

user := db.NewUser()
book := db.NewBook()
hello, err := app.NewHello(user, book)

この変更は、実際に行うと面倒ですし、変更したいところ以外にも差分がでてしまうので、 あまりよろしくないのではないかと思います。 この問題を解決するに当たり、依存関係を管理する中間層(=provider)を用意してあげる方法をとります。 このようにすることで、呼び出し元の修正なく、appの依存関係を変更することができるようになりました。
(※ ちなみにここまでのことは、wireを利用せずとも実現可能です。)

package provider

func InitHelloApp() (app.Hello, error) {
    user := db.NewUser()
    book := db.NewBook()
    hello := app.NewHello(user, book)
    return hello, nil
}

2. 依存関係を作る処理をwireで自動生成することができる

では、wireを導入するメリットですが、これは依存関係を解決した上で、コードを自動で生成してくれることに あるのではないかと思います。 wireで生成するためには、生成元コードが必要になりますが、それは下記のように実装します。

func InitHelloApp() (app.Hello, error) {
    wire.Build(db.NewUser, app.NewHello)
    return app.Hello{}, nil
}

wire.Build に依存関係を生成する関数を引数として、渡してあげるだけです。あとは、wire側で依存関係を整理して、コードを生成してくれます。

生成後

func InitHelloApp() (app.Hello, error) {
    userRepository := db.NewUser()
    hello := app.NewHello(userRepository)
    return hello, nil
}

特筆すべき点は、 hello structにuserRepositoryが必要であることを読み取って、生成する順序を自動で調整してくれる点 です。 wire.Build() に渡す順番を実装者は意識する必要はありません。(適当な順番で渡してもうまく生成してくれました。)
また、依存が足りない場合は、生成時にエラーを吐いてくれます。

エラー例

$ wire gen github.com/gmidorii/api-design/wire/provider
github.com/gmidorii/api-design/wire/provider: generate failed
~/src/github.com/gmidorii/api-design/wire/provider/hello.go:11:1: inject InitHelloApp: no provider found for github.
com/gmidorii/api-design/wire/domain.UserRepository

所感

wireを実際のプロダクトコードで利用する際は、どういった構成にすれば良いか、実装してまとめてみました。 記載したメリットの他に、依存性を切り出す意識をパッケージ構成レベルですることができるので、テスタブルなコードを強制できる点も、 良いのではないのかなぁと思いました。実際に使っていきたいと思います。

参考

React.js 学習 (Soft Skillsフォーマット)

概要

React.jsに入門してみました。その際に、 Soft Skillsで言及されていたフォーマットに沿って学習しました

Soft Skills

SOFT SKILLS ソフトウェア開発者の人生マニュアル

SOFT SKILLS ソフトウェア開発者の人生マニュアル

書籍内にて、学習のフォーマットが提示されていたので、そのとおりにやってみています。
番号が振ってある部分が、フォーマットになります。

フォーマット

1. 日付

2018/08/27 ~ 2018/09/12 (=> 伸びて 2018/10/09になりました)

2. スコープを調べる

調査

メモ

  • viewの状態を動的に変えれる
  • 親と子のコンポーネントがあって、うけわたしする
  • 状態遷移があって、どの状態で何をするかを考える必要がありそう
  • Facebook
  • 入力した値を即座に反映したいときに便利
  • 仮想domは速いらしい
  • reactiveプログラミング

3. スコープを決める

  • React.jsを利用したシングルページアプリを作成
  • TypeScript+React.jsの構築/コンパイルができる
  • アプリを外部公開できる

4. 成功の基準

React.jsを利用した、シングルページアプリを公開してブログを書く

5. 参考資料を見つける

(できる限りたくさん見つけるのが良い模様)

Essence

  • Declarative
  • component base
    • render()Componentを記載
    • this.propsでpropertyにアクセス
  • Virtual Domは高速
    • Virtual Dom全更新→差分計算→Domへの全適用
    • Domの全更新より早い
  • props
    • 外部とのInteface
    • Immutableに扱う必要がある
  • state
    • Component内部の状態を扱う変数

6. 学習プラン作成

step title Goal describe
1 TS + React環境構築 Hello World !表示 - tsコンパイルができる環境作成
- webpackを利用する
2 公式チュートリアル実施 チュートリアル終了 - 公式チュートリアルをそのまま実施する
- ただし、TypeScript対応はする
3 シングルページ作成 ✅ローカルでページが見れる - 作成物は要検討
- 掲示
4 公開 ✅特定のIP/ドメインでリクエストしてページが見れる - Nowを利用して公開
5 このページをブログ公開 ブログとして公開される

7. リソースを絞り込む

8. 使い始められるようにする方法を学ぶ

環境構築

mkdir -p dist src/components
npm init

# react domと type packageを取得
npm install --save react react-dom @types/react @types/react-dom

# typescript loader(tsconfig.json) + sourcmap作成(debug時にtsから見れるようになる)
npm install --save-dev typescript awesome-typescript-loader source-map-loader

その他

# globalにinstallしたpackage list
npm ls -g

# webpack
npm install -g webpack
# webpack-cli or webpack-commandのどちらかが必要
npm install -g webpack-command

# --save-dev(-D) => 開発時のみ利用のオプション

tsconfig.json

{
  "compilerOptions": {
    "outDir": "./dist/",
    "sourceMap": true,
    "noImplicitAny": true,
    "module": "commonjs",
    "target": "es5",
    "jsx": "react"
  },
  "include": [
    "./src/**/*"
  ]
}

webpack.config.js

module.exports = {
  entry: "./src/index.tsx",
  output: {
    filename: "bundle.js",
    path: __dirname + "/dist"
  },
  devtool: "source-map",
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".json"]
  },
  module: {
    rules: [
      { test: /\.tsx?$/, loader: "awesome-typescript-loader"},
      { enforce: "pre", test: /\.js$/, loader: "source-map-loader"}
    ]
  },
  externals: {
    "react": "React",
    "react-dom": "ReactDOM"
  }
}

.gitignore

node_modules
dist

index.ts

import * as React from 'react'
import * as ReactDOM from 'react-dom'

ReactDOM.render(undefined, document.getElementById('root'))

HTML

<body>
  <div id="root"></div>
  
  <!-- Dependencies -->
  <!-- https://reactjs.org/docs/getting-started.html#development-vs.-production-builds -->
  <script src="./node_modules/react/umd/react.development.js"></script>
  <script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
  <!-- Main -->
  <script src="./dist/bundle.js"></script>
</body>

tslint + perettier

npm install -D tslint prettier tslint-config-prettier tslint-plugin-prettier tslint-config-standard

tslint.json

{
  "rulesDirectory": ["tslint-plugin-prettier"],
  "extends": ["tslint-config-standard", "tslint-config-prettier"],
  "rules": {
    "prettier": [
      true, {
        "singleQuote": true,
        "semi": false
      }
    ]
  }
}

テスト環境構築

# jest
npm install -D jest babel-jest babel-preset-env babel-preset-react react-test-renderer ts-jest
# 型ファイル
npm install -D @types/react-test-renderer @types/jest

# package.json
"scripts": {
  "test": "jest"
},
"jest": {
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js"
    ],
    "testMatch": [
      "**/__tests__/*.+(ts|tsx|js)"
    ]
}

9. 遊び回る

チュートリアル実施

  • JavaScriptではclassのconstructorで、super(props)を最初に呼ぶ必要がある
  • 子Component同士でやりとりするのではなく、親Componentを介してstateを共有する
  • immutable
    • Complex Features Become Simple
    • Detecting Changes
    • Determining When to Re-render in React
  • It’s strongly recommended that you assign proper keys whenever you build dynamic lists
  • チュートリアル完了
    • +α で押されたボタンをハイライトする機能を追加

10. 役に立つことができるところまで学ぶ

掲示板作成

- ソースコード

github.com

  • 機能
    • 入力した文字をそのまま下に表示
    • clear

Nowへデプロイ

  • expressでサーバーを立てる
    • index.js
  • webpack/webpack-commandをdevdependenciesに追加
    • サーバー上でbuildする際にwebpackが必要になるため
npm install -D webpack webpack-command
  • package.jsonにbuildとstartを追加
{
   "scripts": {
       "build": "./node_modules/.bin/webpack",
       "build-auto": "webpack --progress --watch",
       "start": "node index.js"
   }
}

11. 教える

ブログ書く

所感

(後日記載)

AWSコンソールからShellを実行できる機能を試してみた (AWS Systems Manager Session Manager for Shell Access)

概要

AWSGCPのCloud Shell的な機能が出たとあり、ちょっと面白そうなので触ってみました。

aws.amazon.com

やったこと

  • ログインIAMユーザーに AmazonSSMFullAccess ポリシーをアタッチ
  • AmazonEC2RoleforSSM ポリシーをアタッチしたロールを作成
  • 作成したロールをアタッチしたEC2を作成
  • AWS Systems Manager > Run Command から AWS-UpdateSSMAgent を実行してssm agentをアップデート
  • AWS Systems Manager > Session Manger から起動

スクリーンショット

起動画面 (200%拡大)

f:id:midori5:20180912231128p:plain

デフォルト

試して見た感想

普通にコンソールとして動作する印象です。

  • 起動は高速で待たされることなく画面がすぐ開く
  • 動作はけっこう重め
  • Ctrl + xxx のコマンドは利用可能
  • マウススクロールで画面スクロールができる
  • vi 開ける

所感

GCPのCloud Shellが便利で、AWSにもこういった機能が欲しかったので待望のリリースでした。
ただ立ち上げてみただけですが、sshポートを開けることなく接続できて、セキュアだと思うので、
動作が軽くなれば、こちらを積極利用していけると良さそうです。

参考