tobb422のブログ

スタートアップエンジニアの奔走

入門 TypeORM(2)~ リレーション ~

はじめに

入門 TypeORM (1) ~ テーブル定義と基本操作 ~ ← こちらの記事の続編です。
今回は、リレーションについてまとめていきたいと思います。
早速内容に入っていきます!
tobb422.hatenablog.com

github.com

1 : 1 のケース

前回の記事で利用した User クラスに関連するテーブルとして、 UserDetail クラスを作成したいと思います。

こちらは前回作成した User クラス(usersテーブル)です。

import {
  Entity, Column, PrimaryGeneratedColumn,
  BaseEntity, CreateDateColumn, UpdateDateColumn
} from 'typeorm'

@Entity('users')
export class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number

  @Column({ name: 'name' })
  name: string

  @Column({ name: 'password', nullable: true })
  password: string

  @Column({ name: 'email', unique: true })
  email: string

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date
}

ここで、UserDetail クラスを定義してみます。
UserDetail クラスの位置付けとしては、 User の補足情報ということにしておきます。
一旦、誕生日をプロパティとして持つ、UserDetail クラスを定義してみます。
(説明の便宜上、こうしておりますので、誕生日も User クラスのプロパティで良くね?というツッコミはナシということで....w)

import {
  Entity, Column, PrimaryGeneratedColumn,
  BaseEntity, CreateDateColumn, UpdateDateColumn,
  OneToOne, JoinColumn
} from 'typeorm'
import { User } from 'user.entity'

@Entity('user_details')
export class UserDetail extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number

  @Column({ name: 'birthday' })
  birthday: Date

  // この記述が、1:1の関係を表している
  @OneToOne(type => User, user => user.userDetail)
  @JoinColumn()
  user: User
  

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date
}

前回、テーブル定義では、デコレーターを利用することに触れましたが、
リレーションもデコレーターを利用して定義します。
OneToOne という名前からも 1:1 の関係を表していることが明白です。
また JoinColumn デコレーターが、このカラムがリレーションで利用するものだということを明示してくれています。

User クラス側にも、追加の定義が必要です。

import {
  Entity, Column, PrimaryGeneratedColumn,
  BaseEntity, CreateDateColumn, UpdateDateColumn,
  OneToOne
} from 'typeorm'
import { UserDetail } from 'user-detail.entity'

@Entity('users')
export class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number

  @Column({ name: 'name' })
  name: string

  @Column({ name: 'password', nullable: true })
  password: string

  @Column({ name: 'email', unique: true })
  email: string

  // 追加で定義
  @OneToOne(type => UserDetail, userDetail => userDetail.user)
  userDetail: UserDetail

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date
}

ほぼ、 UserDetail クラス で実装したものと同じことを User クラスでも定義しています。
違うこととしては、 User クラス側(usersテーブル)では、追加カラムが必要ないので、
JoinColumn デコレーターを定義していないことだと思います。

1 : N のケース

1:1 の関係を定義するのに、 OneToOne デコレーターを利用しましたが、
1:N では、 OneToMany ManyToOne デコレーターを利用します。

User クラスが、Card クラスを複数持つということを想定して、実装してみます。

import {
  Entity, Column, PrimaryGeneratedColumn,
  BaseEntity, CreateDateColumn, UpdateDateColumn,
  ManyToOne, JoinColumn
} from 'typeorm'
import { User } from 'user.entity'

@Entity('cards')
export class Card extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number

  @Column({ name: 'name' })
  name: string

  // 1:Nを表してくれるデコレーター
  @ManyToOne(type => User, user => user.cards)
  @JoinColumn({ name: 'user_id' })
  user: User

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date
}

1:N でも、 1:1 の時と同様に User クラス側への追加実装が必要です。

import {
  Entity, Column, PrimaryGeneratedColumn,
  BaseEntity, CreateDateColumn, UpdateDateColumn,
  OneToOne, OneToMany
} from 'typeorm'
import { UserDetail } from 'user-detail.entity'
import { Card } from 'card.entity'

@Entity('users')
export class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number

  @Column({ name: 'name' })
  name: string

  @Column({ name: 'password', nullable: true })
  password: string

  @Column({ name: 'email', unique: true })
  email: string

  @OneToOne(type => UserDetail, userDetail => userDetail.user)
  @JoinColumn({ name: 'user_detail' })
  userDetail: UserDetail

  // 追加で定義 
  @OneToMany(type => Card, card => card.user, { cascade: true, nullable: true })
  cards?: Card[]

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date
}

M : N のケース

1:1 や 1:N に比べると、若干特殊な感じがするのが M:N の実装です。
今回は、 先程作成した Card クラスと新たに Label クラスを作成して説明します。
カード毎に、複数のラベルを付与することができるといった感じです。

M:N のリレーションを定義するためにデコレーターを利用するということは同じで
今回のケースでは、 ManyToMany デコレーターを利用します。
特殊なのは、 先程まではカラムとして保持していたのですが、
今回は、「cardsテーブルとlabelsテーブルをつなぎ合わせる中間テーブルが必要」という点です。

では、実装を見ていきます。 まずは、Label クラスです。

import {
  Entity, Column, PrimaryGeneratedColumn,
  BaseEntity, CreateDateColumn, UpdateDateColumn,
  ManyToMany
} from 'typeorm'
import { Card } from 'card.entity'

@Entity('labels')
export class Label extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number

  @Column({ name: 'name' })
  name: string

  @ManyToMany(type => Card, card => card.labels, { nullable: true })
  cards?: Card[]

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date
}

Label クラスでは、先程紹介した M:N を表す ManyToMany デコレーターを利用しただけで、目新しい実装はありません。
続けて、Card クラスに実装を追加していきます。

import {
  Entity, Column, PrimaryGeneratedColumn,
  BaseEntity, CreateDateColumn, UpdateDateColumn,
  ManyToOne, JoinColumn, ManyToMany, JoinTable
} from 'typeorm'
import { User } from 'user.entity'
import { Label } from 'label.entity'

@Entity('cards')
export class Card extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number

  @Column({ name: 'name' })
  name: string

  @ManyToOne(type => User, user => user.cards)
  @JoinColumn({ name: 'user_id' })
  user: User

  // 追加実装
  @ManyToMany(type => Label, label => label.cards)
  @JoinTable({
    name: 'card_labels',
    joinColumn: { name: 'card_id', referencedColumnName: 'id' },
    inverseJoinColumn: { name: 'label_id', referencedColumnName: 'id' },
  })
  labels?: Label[]

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date
}

ManyToMany デコレーターの追加は、 これまで通り、この Card クラスが Label クラスと M:N の関係にあることを示しています。
新しく登場した JoinTable というデコレーターが、先程説明した中間テーブル作成の役割を果たします。

実際に マイグレーション処理(マイグレーションに関しては、別途他の記事 → をご参考ください)を行うと、
card_labels という中間テーブルが作成されます。

(Typescript + TypeORMセットアップ & migration世代管理 & expressへの組み込み)

まとめ

リレーションの定義方法について、まとめました。
このリレーションを使ったDB操作まで記述しようと考えていたのですが、定義について書くだけで力付きてしまったので、
気が向いたときに追記 or 別の記事で紹介したいと思います。
(たぶん、書かないのだろう....w)

入門 TypeORM (1) ~ テーブル定義と基本操作 ~

はじめに

現在、Nest.js というTypeScript を利用した Webフレームワークを使って、アプリケーションを開発しているのですが、
Database とのやりとりに TypeORM という ORM を使用しています。
(Database には、 postgresql を採用しています!)
まだ、簡単なTodoアプリを作成してみた程度なのですが、一通り触ってみたので、メモがてらまとめておきます。
今回は、その中でも初めの一歩として、TypeORMの概要, テーブル定義, そして基本的なDB操作についてまとめました! github.com

TypeORMとは

Node.js の ORM です。Node.js で ORM というと他には、Sequelize などが有名だと思います。
TypeORM の特徴としては、RailsActiveRecord のような書き方ができる点なのかなと思っています。
そのため、以前 Rails を利用したアプリケーションをいくつか書いていた経験がある方には、すぐ書ける気がします。
正確には、 TypeORM を利用した実装パターンを大きく2つあり、そのうちの1つが ActiveRecord のような記述になっています。
詳しくは、Github をご覧になっていただければ、概要を把握できると思います。
前置きとして、以下の説明では、 ActiveRecord っぽい実装を採用しています。

テーブルの定義

まず、テーブル定義についてまとめていきます。
実際の実装をみるほうがわかりやすいと思うので、以下のコードを順に追っていく形で説明します。

import {
  Entity, Column, PrimaryGeneratedColumn,
  BaseEntity, CreateDateColumn, UpdateDateColumn
} from 'typeorm'

@Entity('users')
export class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number

  @Column({ name: 'name' })
  name: string

  @Column({ name: 'password', nullable: true })
  password: string

  @Column({ name: 'email', unique: true })
  email: string

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date
}

まず、 @Entity('users')@Column({ name: 'name' }) についてですが、
こちらは、デコレーターと呼ばれるもので、TypeORM では、このデコレーターを定義することで、テーブル定義を行うことが可能となります。
このデコレーターを定義していくことで、目的としているテーブルを定義することができます。

@Entity() では、このテーブルの名前を定義しています。
@Column() では、このプロパティがカラムであることを定義しています。
どちらも、名前を付けなくても TypeORM がテーブルを作成する際に、クラス名やプロパティ名から名前を付けてくれるのですが、
たとえば、createdAt などは、 そのまま createdAt になっており、私としては、テーブルのカラム名は、 created_at という形にしておきたいという理由から
少し冗長的ですが、 @Column({ name: 'created_at' }) といった形で、明示的に定義するようにしています。
テーブル名も同様で、 @Entity('users') とテーブル名を定義しています。
ここまでで、どういったテーブルに、どういった名前のカラムが定義されるのかがわかるようになります。

続けて、 @PrimaryGeneratedColumn()@CreateDateColumn @UpdateDateColumn とまだ触れていないデコレーターについて説明いたします。
これらのデコレーターは、そのカラムの振る舞いを定義していくれるデコレーターと考えると良いと思います。
@PrimaryGeneratedColumn() であれば、その名の通りなのですが プライマリキーとして連番を振ってくれるカラムになります。

このように TypeORM では、デコレーターを利用することで、そのテーブルやカラムの定義や振る舞いを作っていくことになります。

最後に class User extends BaseEntity こちらの部分の説明ですが、
BaseEntity を継承してクラスを作成います。この BaseEntity を継承することで、
User クラスは、DB操作(データの取得, 作成, 更新, 削除) といったことができるメソッドを持つことができます。

User.find() // users テーブルの全件取得

上記のように、 ActiveRecord っぽく扱えます。

基本的なDB操作

続いて、基本的なDB操作についてまとめます。
先程の User クラスを例に説明していきます。

取得

User.find() // 全件取得
User.findOne(id) // id を指定して、取得
User.findOne({ email: 'typeorm@test.com' }) // メールアドレスを指定して、取得

新規作成・更新

■ 新規作成

const user = new User()
user.name = 'test'
user.email = 'typeorm@test.com'
user.password = 'typeorm'
user.save()

■ 更新

const user = await User.findOne(1) // 実際のメソッドで async を定義していると仮定して....
user.email = 'test@typeorm.com'
user.save()

削除

const user = await User.findOne(1)
user.remove()

以上が、基本的なDB操作の例です。
私の主観としては、非常に直感的でわかりやすいな〜といった印象です。

まとめ

今回は、 TypeORM の概要, テーブル定義, そして、基本的なDB操作についてまとめました。
次回は、リレーションについてまとめたいと思います。
書いた → 入門 TypeORM(2)~ リレーション ~

Nest.js における認証機能の基本的な仕組みについて

はじめに

サーバーサイドのFWとして、Nest.js を利用したアプリを作成中です。
その一環として、今回は認証周りの機能を作ってみました!
認証機能を作るにあたっては、Express でも良く利用されている passport が Nest でも採用されており、
ドキュメント に書いてあった内容やNest の Github にあがっていたイシュー等を参考にしつつ、
色々と手元で動かしてみて、なんとなく形になったので、内容をまとめておきます。
※ Nest は使い始めて間もないFWなので、完全な正解は私も良くわかっていません。
ご容赦ください。

認証の流れ & 実装方針

Nest では、クライアントからリクエストを受けた際に、
Controller に到達する直前に Guard と呼ばれる仕組みを通過させることで認証機能を実装することができます。

f:id:tobb422:20190325223753p:plain
認証の流れ

今回は、認証の責務を持つ Guard なので、 AuthGuard としています。
簡単ではありますが、図にあるようにクライアントからのリクエストが
Controller のアクションに到達する直前で、AuthGuard を通過するようになっています。
この AuthGuard を通過することができたリクエストのみが、無事 Controller のアクションへと到達することができるというわけです。

より詳細な説明をすると、 Guard は、canActivate と呼ばれるメソッドを実装している必要があります。
この canActivate メソッドが true を返す場合に、Guard を通過することができます。

そのため、AuthGuard を作成して、配置すれば良いということになるのですが、
今回は、@nestjs/passport にて、既に用意されているものがありますのでそちらを利用します。

自分で実装をする必要があるのは、どういった認証ロジックにするか ということだけです。
実装の詳細は次の項目でまとめます。

ここまでを基本的な流れとします。
まとめると、 以下のような話でした。

  • Controllerの前段に Guard という機能を置く
  • その Guard の中で認証を判断する
  • 認証で利用する Guard は、@nestjs/passport で既に用意されたものがあるので、そちらを利用する
  • 実装する必要があるのは、認証ロジックをどういったものにするかということ

実装

※ ここから先は、Nest にも記載されている Authentication のドキュメント に補足を入れる形で、説明していきます。
※ 説明を簡易にするため、ドキュメントとは説明の順番を前後している箇所があります。

今回の実装では、 Authorization: Bearer ヘッダ を利用した認証機能を想定した作りになっています。

まず、今回の実装で必要な ライブラリをインストールします。
$ npm install --save @nestjs/passport passport passport-http-bearer

つづいて、今回のメイン機能である認証ロジックを作成します。
(以下のコードは、ドキュメントから http.strategy.ts 転載です。)

import { Strategy } from 'passport-http-bearer';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';

@Injectable()
export class HttpStrategy extends PassportStrategy(Strategy) {
  // AuthService はのちほど、作成いたします!
  constructor(private readonly authService: AuthService) {
    super();
  }

  // この validate メソッドが今回の本丸です!
  // HTTPヘッダの Authorization:Bearer の token を引数として受け取ります。
  // 受け取った引数をもとに、なんらかの認証を行います! 
  // ここで返したオブジェクトは、Controller のアクション内で リクエストに含まれるので、例のように、認証したユーザーを返しておくと良いです。
  async validate(token: string) {
    // token をもとに、該当ユーザーを検索する
    const user = await this.authService.validateUser(token);
    if (!user) {
      // ユーザーがいない場合は、エラー
      throw new UnauthorizedException();
    }
    // ユーザーがヒットした場合は、そのユーザーを返却
    return user;
  }
}

@nestjs/passport の AuthGuard は引数として様々なタイプを受け取ることができるのですが、 AuthGuard('bearer') とすることで、今回実装した HttpStrategy の validate を参照してくれます!

こちらは、HttpStrategy 内で利用した AuthService の実装例です。

import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';

@Injectable()
export class AuthService {
  constructor(private readonly usersService: UsersService) {}

  async validateUser(token: string): Promise<any> {
    // Validate if token passed along with HTTP request
    // is associated with any registered account in the database
    // token にヒットするユーザーを検索して、返す処理を担っています。
    return await this.usersService.findOneByToken(token);
  }
}

あとは、使用したい Controller で AuthGuard を指定します。以下は、例です。

import { Controller, Get, Req, UseGuards } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
import { UsersService } from './users.service'
import { User } from './user.entity'

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get('sample')
  @UserGuards(AuthGuard('bearer')) // AuthGuard を使用することを明記する
  async sample(@Req() req): Promise<User> {
    // HttpStrategy の validate で返却した user は、 
    // リクエスト内のオブジェクトとして参照可能です。(ex) req.user
    return req.user
  }
}

すべての Controller で認証を行いたい場合は、Module に明記することでも可能です。

import { Module } from '@nestjs/common'
import { APP_GUARD } from '@nestjs/core'
import { AuthGuard } from '@nestjs/passport'

@Module({
  providers: [
    AppService,
    {
      provide: APP_GUARD,
      useClass: AuthGuard('bearer'),
    },
  ],
})
export class AppModule {}

以上が具体的な実装例です。

まとめ

Nest.js における認証機能の作り方をまとめてみました。
また、副作用的に、認証機能を作ることで、 Guard の理解も深めることができた気がします!
passport をしようすることで、簡単に実装が可能なので、ぜひ、試してみてください。

ECSへのデプロイステータスを監視 & Slack に通知する

はじめに

ECS で動かしている アプリケーションのデプロイに CodePipeline を利用しているのですが、
今回は、その デプロイフローを Slack に通知するという仕組みを作ってみたので、
まとめておきたいと思います。

構成

デプロイの構成は、以下のようになっています。
※ 実際にはS3が登場したりするのですが、今回の例は説明しやすくするために省略いたします。

f:id:tobb422:20190318231455p:plain
デプロイフロー

手順としては、
1 :GitHub のレポジトリ(特定のブランチ)を監視して、変更を検知 & ソースコードを取得する
2 :取得したコードから Docker イメージを作る
2':生成した Docker イメージは、ECR へプッシュする
3 :CodePipeline で ECS へのデプロイをスタートする
3':さきほどプッシュしたイメージを ECR から取得して、新しいコンテナを作る
ざっくりこのようなデプロイフローを行っています。

そして、このデプロイフローを CloudWatch で監視
変更を検知したら、SNS へ通知し、SNS から Lambda を発火させて、Slackへ通知を送る
という仕組みを構築したいと思っています。

f:id:tobb422:20190318232927p:plain
デプロイフローを監視する

CloudWatchEvents でデプロイフローのステータスを監視する

今回のメイン機能です。
CodePipeline で管理しているデプロイフローのステータスを、 CloudWatchEvents を利用して監視します。
CloudWatchEvents 公式ドキュメント にもあるように
CloudWatchEvents のターゲットとして、CodePipeline を設定できるのでそちらを利用しようする形です。

CloudWatchEvents は、AWS コンソールから CloudWatch へ移動することで利用できます。
デプロイフローを監視するための新しいルールを設定します。

f:id:tobb422:20190318233439p:plain
CloudWatchEvents 新しいルールの設定

画像のように、サービス名を選んで、イベントを選ぶ形で簡単にルールを作成することができます。

今回は、CodePipeline を設定しようと思っているので、
サービス名: CodePipeline
イベントタイプ: CodePipeline Stage Execution State Change
を選択しています。

イベントタイプを上記にしたのは、今回のデプロイフローで

  1. ソースコードの取得
  2. ビルド
  3. デプロイ

とわけているので、Stage を選択しています。
もっと細かく分けたい方は、Action を選択することで細かく監視することができると思います。

また、状態も選択できるのですが ( Stage の場合は、 'CANCELD', 'FAILED', 'RESUMED', 'SUCCEEDED', 'STARTED' の5つ)
今回は、その中から開始と成功と失敗のステータスを監視したいと思っています。
そのため、GUI では限界があるので、実際のルールでは、カスタムイベントパターンを利用して作成します。
以下が、今回使用するカスタムイベントパターンの設定例です。

{
  "source": [
    "aws.codepipeline"
  ],
  "detail-type": [
    "CodePipeline Stage Execution State Change"
  ],
  "resources": [
    "arn:aws:codepipeline:ap-northeast-1:<aws account id>:staging",
    "arn:aws:codepipeline:ap-northeast-1:<aws account id>:production"
  ],
  "detail": {
    "state": [
      "FAILED",
      "STARTED",
      "SUCCEEDED"
    ]
  }
}

そして、最後に、ターゲットを選択します。
ターゲットは SNS トピックを選択し、候補のから該当のトピックを選択します。
(※ 先に SNS を作っておく必要があります。)

f:id:tobb422:20190318235011p:plain
ターゲットの選択

以上が、デプロイフローを監視するための CloudWatchEvenets の設定です。

ここまでの設定で、 以下ができるようになりました。

  • CodePipeline のステータス変更を検知する
  • 検知した変更内容を SNS へ通知する

SNS から Lambda を発火させる

先程ターゲットとして設定した SNS にて、サブスクリプションを作成します。
(※ 先に Lambda で関数を作成しておく必要があります。)

f:id:tobb422:20190318235654p:plain
サブスクリプションの作成

プロトコルAWS Lambda を選択
エンドポイントに今回利用する関数を選択すれば完成です。

ここまでの設定で、先程の

  • CodePipeline のステータス変更を検知する
  • 検知した変更内容を SNS へ通知する

に加えて

  • SNS から Lambda へ通知する

ことができるようになりました。

Lambda から Slack へ通知を送る

Lambda から Slack へ通知を送るために、Slack の Incoming Webhook を利用します。
また、SNS から受け取れる引数から、
CodePipeline のどの Stage がどういうステータスになっているのかがわかるので、
そちらを利用して、メッセージを切り替えます。

実際の実装には、Go言語を利用しました。

package main

import (
  "bytes"
  "fmt"
  "net/http"
  "encoding/json"
  "github.com/aws/aws-lambda-go/events"
  "github.com/aws/aws-lambda-go/lambda"
)

type Response struct {
  StatusCode int    `json:"statusCode"`
  Body       string `json:"body"`
}

// SNSEventsの詳細データの構造を定義しておく。
// 今回は Stage と State がわかればいい
type Entity struct {
  Detail struct {
    Stage string `json:"stage"`
    State  string `json:"state"`
  } `json:"detail"`
}

// evnets.SNSEvent に今回のステータス変更内容が入っている。
func HandleRequest(_ context.Context, snsEvent events.SNSEvent) (Response, error) {
  record := snsEvent.Records[0]
  snsMessage := record.SNS.Message
  var entity Entity
  _ = json.Unmarshal([]byte(snsMessage), &entity)

  req, err := http.NewRequest(
    "POST",
    "Incoming Hook で取得したURL",
    bytes.NewBuffer([]byte("取得した entity に応じてメッセージを変えると良さそう")),
  )
  req.Header.Set("Content-Type", "application/json")
  client := &http.Client{}
  resp, err := client.Do(req)
  defer resp.Body.Close()

  return Response{
    StatusCode: 200,
    Body:       "",
  }, nil
}

func main() {
    lambda.Start(HandleRequest)
}

Go言語の場合、SNS から受け取れる引数の内容はこちらを見ればよくわかります。

github.com

おそらく、形は少し違ったとしても、他の言語でも同じようなことができるはずです。
あとは、コードをビルドして、バリナリファイルにした後、zip化したものをアップロードすればOKです。

以上ですべての設定が完成です。

完成物

今回は、会社のデザイナーさんに、素敵なキャラクターを作っていただきました。
わんきんすというキャラクターです。モチーフは Jenkins をお願いしました。
(※ いま、キャラ設定を考え中だそうですw)

f:id:tobb422:20190319001419p:plain
わんきんす

そのため、弊社(one visa というスタートアップで働いています。)では、 このわんきんすがデプロイ状況を逐一知らせてくれるということになりました!!

ちなみに、エラーの際は、以下のようにわんきんすが怒ります。

f:id:tobb422:20190319001633p:plain
わんきんす(エラー時)

実際の通知は、以下のような感じです。
(※ 語尾は、不可抗力でこうなりました。気にしないでください。)

f:id:tobb422:20190319001749p:plain
ビルド開始
f:id:tobb422:20190319001803p:plain
デプロイ成功

まとめ

CodePipeline のステータスを CloudWatch で監視して、Slack に通知するという仕組みを簡単に作成できました。
今回は、シンプルにステータスを監視するだけでしたが、
組み合わせによっては、非常に色々なことができるな〜といった印象を持つことができました!
簡単に設定可能なので、ぜひ試してみてください。

CloudWatch による外形監視で Slack に通知する

はじめに

業務で、EC2 上で Nginx を動かしており、
今回は、その Nginx を外形監視して、アラートがあれば Slack に通知をする
というシステムを作ったので、メモとして残しておきます。 (※ 2019年3月10日時点)

なぜ Slack 通知なのか

外形監視は、
たとえば、ユーザーがそのWebページに問題なくアクセスできることを監視するものであるため、
外形監視を行う目的としては、私は以下の理由があると考えています。
- 障害発生にいち早く気がついて、復旧できる体制を作る

この目的を果たす手段として
私が、日頃から業務のコミュニケーションツール
として利用していた Slack への通知が最適ではないかと判断しました。

構成

f:id:tobb422:20190312130930p:plain
今回の構成

上記の図で表したように、
今回は、CloudWatch → SNS → Lambda → Slack
といった構成で実現したいと思います。
CloudWatch でHTTPステータスの監視を行い
基準値を超えた場合に、SNSへ通知
Lambda がその SNS のトピックをサブスクライブしており
Slack へ通知が流れるといった仕組みです。
(Lambda では、言語として Go を採用しました!)
順を追って説明します。

Lambda から Slack へ通知を送る

Lambda から Slack へ通知を送るために、
Slack の Incoming Webhook を利用します。
やっていることとしては、 Lambda から Slack へ POST しているだけです。

Go言語で書いた、Lambda の例です。

package main

import (
  "bytes"
  "fmt"
  "net/http"
  "github.com/aws/aws-lambda-go/lambda"
)

type Response struct {
  StatusCode int    `json:"statusCode"`
  Body       string `json:"body"`
}

func HandleRequest() (Response, error)   {
  text := "通知の本文"

  req, err := http.NewRequest(
    "POST",
    "Incoming Webhook で得たURL",
    bytes.NewBuffer([]byte(text)),
  )

  if err != nil {
    fmt.Print(err)
  }

  req.Header.Set("Content-Type", "application/json")

  client := &http.Client{}
  resp, err := client.Do(req)
  if err != nil {
    fmt.Print(err)
  }
  defer resp.Body.Close()

  return Response{
    StatusCode: 200,
    Body:       "アラート",
  }, nil
}

func main() {
    lambda.Start(HandleRequest)
}

あとは、上記のようなコードをビルド → ジップ化して
Lambda 上で作成した関数にアップロード、保存すればOKです。

SNS の設定

SNSシステム間メッセージング機能を利用します。
流れとしては、SNS 上に作成したトピックに対して通知を送る
通知が来たら、そのトピックをサブスクライブしているものへ通知が流れるといったイメージです。

トピックの作成, サブスクリプションの作成は、画面に従っていけば、簡単に設定できると思います。
サブスクリプションの作成時に、プロトコルAWS Lambda を選択します。
そして、エンドポイントは先程作成した Lambda の関数を選択すればOKです!

f:id:tobb422:20190312111433p:plain
SNS - サブスクリプションの例

設定が上手くできると、作成した Lambda 側で、
以下の画像のように、SNS が追加されていると思います。

f:id:tobb422:20190312111826p:plain
SNS - Lambda 設定

ここまでの設定で、
SNS の通知を受け、Lambda のイベントが発火し、Slack へ通知が飛ぶという流れが完成しました。
あとは、SNS へ通知を飛ばす設定を CloudWatch を利用して行っていきます。

CloudWatch の設定

アラームの設定を行っていきます。
アラームの設定には、 CLoudWatch のメトリクスという概念を利用します。
メトリクスとは、ログをある条件で加工したものくらいに考えておいて、問題ないと思います。
そのため、メトリクスの作成には、ログが必要です。
EC2 上で動く Nginx のログを取得できるよう設定していきます。
※ CloudWatch への出力ロールをEC2に付与する必要があります
※ 今回はアクセスログのみに省略します

awscli.conf

[plugins]
cwlogs = cwlogs
[default]
region = ap-northeast-1 // リージョンを東京に変更しています

awslogs.conf

[/var/log/nginx/access.log]
datetime_format = %b %d %H:%M:%S
file = /var/log/nginx/access.log
buffer_duration = 5000
log_stream_name = {instance_id}
initial_position = start_of_file
log_group_name = nginx_access_log // ログの名前
$ sudo yum install awslogs
$ cd /etc/awslogs/
$ sudo vi awscli.conf // 上記例のように変更
$ sudo vi awslogs.conf // 上記例のように変更
$ sudo service awslogs start
$ sudo chkconfig awslogs on

これで、Nginx のアクセスログを CloudWatch へ出力できるようになりました。

続けて、メトリクスを作成します。
ログ一覧で、該当のログを選択すると、以下のようにメトリクスフィルタの作成を選択できます。

f:id:tobb422:20190312113722p:plain
メトリクスフィルタの作成

フィルタパターンを記述する欄があるので、そちらで自分が取りたいフィルタパターンを入力します。
AWSのドキュメント でも作成方法は紹介されているので、参考にしてください。
(今回は、5xx のエラーを取得するように設定しています。)

最後に、作成したメトリクスから、アラーム設定を行います。
フォームに従っていけば、問題なく設定できると思います。
通知先を選択できる欄があるので、そこで、先程作成したSNSのトピックを選択してください。

まとめ

ざっくりとではありますが、以上が
CloudWatch による外形監視から、アラートを Slack へ通知する流れでした。

f:id:tobb422:20190312114314p:plain
Slack通知

上記のように、
Slack でメンションが飛んでくれるので すぐに発見でき、無事目的を果たすことができました。

今回の構成は、
コストも少なく、気軽に導入できる
といった印象です。
参考にできる記事も非常に多く、大変助かりました!

参考記事

qiita.com qiita.com

Nest.js Overview

はじめに

最近、趣味で Nest.js という Node.js のフレームワークを使っています。
(会社でも徐々に広めて、本番運用させていこうと勝手に考えております...)

日本語のドキュメントや記事がまだ少なく(2019年3月時点)、サービスでの採用例も少ないな〜と感じてます。

今回は、少しでも広まれば良いなと思いまして、
どんなフレームワークなのかをざっくり紹介することにしました。
公式ドキュメントは、こちら → Nest.js ドキュメント

※ Nest.js のアプリケーションを作ってみるといった、いわゆるハウツーは一切書いておりません。
ドキュメントをご覧になっていただければ、実際の開発に必要な環境構築等の説明はございますので、割愛いたします。

概要

思想は、こちらにまとまっているので、参考にしていただければ良いなと思いますが、
要点をまとめておきます。

です。

このあと、各機能の概要をまとめていきますが、ドキュメントを通して
一環して、 SOLIDの原則を強く意識したフレームワークであり、
DI (依存性の注入)がキーワード的に散りばめられている印象を受けました。

設計が苦手な方でも、Nest.js の思想に則って開発を進めれば、 いい感じに拡張性があり、メンテナンスしやすいアプリケーションを構築できそうだな
といった印象です!

目次

今回は、ドキュメントにある Overview の中から、
以下の項目を、ふわっと意訳する形で、ご紹介したいと考えています。

Controllers

クライアントからリクエストを受けて、レスポンスを返す役割があるクラスです。
何かしらのフレームワークを触ったことがある方であれば、ピンとくる責務だと思います。 特定のデコレーターが付与されることで、Nest.js ではそのクラスがコントローラーであると認識できます。

import { Controllers, Get } from '@nestjs/common';
    
@Controller('sample') // このデコレーターが付与されることで、コントローラーと識別される
export class SampleController {
  // → Decorator:path の定義をしている
  // 今回の場合は、@Controller で 'sample' と定義しているので、エンドポイントは、 /sample となる
  @Get()
  findAll() {
    return 'Sample';
  }
}

コントローラでは、返り値を記述方法として、2つのパターンが存在します。

  • 値をそのまま返す
  • Response Objectというライブラリを使用する

@Get() こちらのデコレーターにて、パスをさらに指定でき、ワイルドカードを使えるなど自由に記述ができます。
@Get() のほかにも @Post() @Put() @Delete() といった具合に、
コントローラーで使うであろうアクションは、揃っているなという印象です。

Providers

Provider は、 @Injection() デコレーターが付与されたクラスのことを指しており、
Nest.js では、サービス, リポジトリ, ファクトリ, ヘルパーといったものは、全て Provider の一種であるとみなされます。
この Provider は、コンストラクターを介して、依存関係を注入できることが、ポイントで多様な結合が可能になっています!

※ 冒頭でもお伝えしましたが、Nest.js ではオブジェクト指向が取り入れられており、SOLIDの原則に従うことを強く推奨しています。
そして、 DI(依存性の注入)と呼ばれているデザインパターンを中心に、構築されていることを念頭にいれておくと、理解が捗ると思います!

以下は、サービス の定義と、コントローラー側で利用する例です。

サービスの定義

import { Injectable } from '@nestjs/common'
import { Task } from './interfaces/task.interface'

// @Injectable デコレーターをつけることで、Nest.js ではクラスがプロバイダーであることを認識する
@Injectable()
export class TasksService {
  private readonly tasks: Task[] = []

  findAll(): Task[] {
    return this.tasks
  }
}

コントローラー側で、サービスを利用する

import { Controller, Get } from '@nestjs/common'
import { TasksService } from './tasks.service'
import { Task } from './interfaces/task.interface'

@Controller('tasks')
export class TasksController {
  // constructor で Controller に Service を 注入している
  constructor(private readonly tasksService: TasksService) {}

  @Get()
  async findAll(): Promise<Task[]> {
    return this.tasksService.findAll();
  }
}

Modules

@Module() デコレーターを付与したクラスをモジュールと呼びます。
アプリケーション全体で、少なとも1つのモジュールを持っており、アプリケーションツリーを構成しています。
構成されているモジュール1つ1つが小さなアプリケーションになっているといったイメージでしょうか...
さきほど、Provider の説明で示した例を使って、モジュールのサンプルを書いてみたいと思います。

import { Module } from '@nestjs/common';
import { TasksController } from './tasks.controller';
import { TasksService } from './tasks.service';

@Module({
  controllers: [TasksController],
  providers: [TasksService],
})
export class TasksModule {}

TasksModule をルートモジュールにインポートします。

import { Module } from '@nestjs/common';
import { TasksModule } from './tasks/tasks.module';

@Module({
  imports: [TasksModule],
})
export class ApplicationModule {}

ここまで(Controller ~ Module)で、手元で簡単なTODOアプリを作ることなどはできると思います。 (実際には, TypeORM 等を用いて、DB接続をする必要があるのですが...)
わたし自身も現在、 Nest.js を使ってAPIサーバーを構築中ですが、

  • Controller でルーティングを定義
  • Provider でビジネスロジックを作成
  • Module として登録
    が一連の流れかなと感じています。

Middleware

Middleware は、ルートハンドラーに行く前に呼ばれる関数のことを指しています。
リクエストとレスポンスのオブジェクトにアクセスが可能です。
Nest.js の Middleware は、基本的には、 Express の Middleware を同じ機能を持っています。

Middleware は、関数で定義をする
もしくは、Provider 同様に @Injectable デコレーターが付与されたクラスです。
ただし、 @injectable デコレーターを付与したクラスの場合は、 NestMiddleware インターフェースを実装してある必要があります。

リクエストとレスポンスのオブジェクトにアクセスができることから、
Logger を Middleware として実装することが多いようです。

各 Module 毎に、そして、アクションの指定等、 Middleware を実行する箇所は柔軟に表現可能であり、 グローバルの Middleware を設定することも可能です。

Exception filters

いわゆる、例外処理です。
@nestjs/common というパッケージに組み込まれている HttpExeption を利用することで簡単に利用可能です。
ドキュメントのコードを見るのが、一番はやいかなと思うので、以上の説明に留めます。

Pipes

Pipe は、Provider や Middleware 同様に @Injectable デコレーターが付与されたクラスです。
Pipe の場合は、デコレーターの付与に加えて、 PipTransform インターフェースが実装されている必要があります。

責務としては、入力されたデータを、目的の形へと変換して出力することを担っています。

具体的な例としては、
- バリデーションロジックとしての役割
- parseInt のような 変換器としての役割
があるようです。

Guards

Guard もまた、Pipe 同様に @Injectable デコレーターが付与されたクラスであり
CanActivate インターフェースを実装している必要があります。

責務としては、
リクエストがルートハンドラで処理されるべきかどうかを判断する
ことを担っています。

アクセス制限ロジックなどは、前述であった Middleware 側に記載するケースもあります。
トークンの検証やリクエストオブジェクトへのプロパティの追加といったことは特定のルートと強く結びついていないので、それでも問題ないのですが、
Middleware の性質上、 next() 関数を呼び出した後にどのハンドラが実行されるのかはわからないのに対して、
Guard は (ExecutionContext インスタンスにアクセスできるため)、評価対象が正確にわかるという利点があります。
(だいぶ疲れてきて、ドキュメントの訳のみになってしまいました。。申し訳ないですw)

※ 追記 (2019/03/25)
Nest.js で認証機能を作るという文脈でブログを書いた際に、Guard についても言及しました。
tobb422.hatenablog.com

Interceptors

Interception もまた、@Injecrable デコレーターを付与されたクラスで
NestInterceptor インターフェースを実装しているものです。
Interception では、アスペクト指向プログラミング(AOP)の側面を持った便利な機能があります。
とここまで、ドキュメントにも書いてあるのですが、
実は、わたし自身まだ Interception を利用していないので、より具体的な効用を持っていません。
利便性が分かり次第、追記したいなと思います。

まとめ

ここまで長々と、Nestjs に組み込まれている概念について書いてみました。
最後の方は、力尽きていてかなり雑になってしまったこと、許してくださいww

まずは、個人プロダクトとしてどんどん利用しつつ、
機会があれば、会社のプロダクトとしても利用できるといいな〜と思っています。

最後までご覧くださりありがとうございました!

1人目のエンジニアとして、スタートアップに入社して1年経過した

 

はじめに

one visa というスタートアップで働いています。

この3月で働き始めて1年が経過したので、これまで学んだ考え方やスタンスを書いてみることにしました。

※ かなりエモいエントリーになっており、技術的な話はほぼ出てきません。

※ かなり長く、まとまりもないです。先に謝っておきます。すみません。

 

2017年12月に、いまの会社の代表に声をかけてもらい、当時働いていた会社に辞めると伝え、

2018年3月にエンジニアが自分だけという状況の one visa に入社しました。

辞める際に、当時お世話になっていた先輩から

「理想と粘りを持って、挑戦し続けたやつだけが成功する」

という言葉をいただきました。いまだによく思い出して、やってやろう!と勇気をもらっています。

 

スタートアップでの初めての仕事

前置きとして、入社当時の組織環境は、

主に、業務委託の方がメインとなり、プロダクトがリリースされていました。

組織に在籍しているエンジニアは存在せず、代表とデザイナーとCSの3人。

4人目のメンバー、そして組織に常駐する1人目のエンジニアとして、入社しました。

わたしの入社と同時に、業務委託でお手伝いいただいていたエンジニアの方は、契約終了となっており、入社時点でエンジニアはわたしだけでした。

そんな状況の中、実際に one visa で働きはじめて、一番最初にした仕事は

  • プリンターの設定

続けて、

でしたww

エンジニアが1人もいない組織でしたので、ハードだろうがソフトだろうがお構いなしで、「技術」でくくることができるものは全てわたしのタスクだったと思います。

プリンターも wifi ルーターも初めてだったので、説明書を見ながら本当に合ってるのか不安になりつつ、設定していたのが懐かしいです。

その後も、わたしが請け負うタスクとしては、

少々漠然としたものが多く(たとえば、セキュリティに不安を感じているので、なんとかして欲しいみたいな感じ....)、

当時のわたしは、リーンスタートアップや起業の科学を読んで、スタートアップでのエンジニアをイメージしていたこともあり、そのギャップに相当ストレスを感じていたと思いますwww

「んなこと、おれもわからんわ!!」と叫びたくなることもありましたがw

なんとか受け入れ(受け入れきれずに漏れ出していた可能性もあります。すみません。)

wifi つながらない ルーター」とかでググってましたww

 

開発したいからスタートアップに入社したのか?

キラキラした気持ちでスタートアップに入ってすぐ、なんとも冴えない日々を過ごしていたわけですが...

少しずつ自分の中で整理がついてきました。

(本来であれば、しっかり整理したうえで入社しなければいけないのですが...)

自分が one visa に入社した理由を見直していき、その中でも

  • Webアプリケーションの開発がしたいから、one visa に入社したのか?

という問いが心の整理ができるきっかけだったように思います。

エンジニアという職種柄なのか、

『開発』をすることでプロダクトを作り、価値貢献することに固執し、

自分の中で会社に対してできる価値貢献の幅を狭めていたように思います。

もちろん、開発は大好きなので、四六時中アプリを作っていたいですし、エンジニア同士で技術の話に花を咲かすことができるランチ等は最高だと思っています。

ですが、 わたしが one visa に入社した理由は、

  • 「世界から国境をなくす」というビジョンを達成したい
  • 魅力ある開発(プロダクト)チームを1から創りたい
  • プロダクトで世界を変えたい

であり、

  • Webアプリケーションの開発がしたいから

ではありませんでした。

 

といいつつも、考えを改めることですぐに

「仕事が全て楽しくてたまらなくなった!!」なんていうキレイ事はないですw

ただ、自分がやっている仕事に対して、意味付けはできるようになりましたし、

目的が明確になることで、創意工夫もできるようになってきました。

そうなると自然と楽しくもなってきます。(もちろん、苦痛なことだって当たり前のようにあります。)

 

価値提供すること全体をハックすることが仕事

意味付けを行う上で、特に意識しているのは、

「バリューストリーム(価値の流れ)」と「価値の最大化」です。

上記を意識することで、

エンジニアという職種と、バリューストリームの一つの工程にしか過ぎない『開発』を

同一視して見ていたこと自体が間違いだなと思うようになりました。

f:id:tobb422:20190303172017p:plain

バリューストリーム(価値の流れ)

(図は、クライアントに価値を提供する上で、『開発』が一つの工程にしか過ぎないということをご理解いただくために作成しました。非常に簡略化しているものなので、あまりじっくり見ないでくださいw)

 

そう考えると、

例えば、プリンターの設定では

当時人数が4人しかいない中で、「IPアドレス」といった言葉に一番抵抗なく、そして毎日のようにググりまくっているエンジニアであるわたしが担当することが、適任だなwと感じるようになりました。

 

いまでは、こうした価値の流れを意識した上で、

one visa が世の中(クライアント)に対して、よりスムーズに価値を届けるために自分のリソースをどう使うべきか。そして、その価値を最大化するには...

と考える癖を身につけられるようになってきた気がします。

スタートアップだからこそ、価値提供のためのあらゆる工程を効率的に行うべきですし、エンジニアとしては、そうしたあらゆるオペレーションをハックする必要があります。そして、メンバーが価値創造に集中できる環境を作ることも、エンジニアとしての大きな価値貢献だとわたしは思っています。

 

 

チームという存在の大きさ

話は変わりますが、 one visa は、現地点で社員9名と着実に人数が増えています。開発陣も、わたしが1人で開発を行っていたところから、4人まで増えました。

エンジニア採用に関しては、プロダクト開発と並行してわたしがメインとなり進めています。

プロダクト開発に専念するのではなく、エンジニア採用をわたしが行うのは、

エンジニアとして弊社に加わるのが、なぜ魅力なのかを一番伝えることができると思っていますし、

エンジニア目線で弊社でできること・できないこと、これからどう進んでいこうとしているのかを説明し、

求職者の方のやりたいこと・求めていることを具体的に聞くことで、ミスマッチを一番なくすことができると思っているからです。

(その代わり、他の開発メンバーには、開発面で負担をかけてしまっているかもしれません。いいように言えば、なるべく開発へ集中してくださっています。いつも感謝しております。。)

 

 

わたし1人であった開発から、人数が徐々に増え、チーム開発へと変化していきました。

1人で開発していたときに比べ、複数人の開発では、すり合わせを行うことが非常に重要だなと感じています。

SPA × API の形で開発しているので、APIのインターフェースをどうするかといった具体的なものもありますが、

特に大切なのは、開発チームが大切にしている価値観・思想のすり合わせだと思っています。

今年に入り、開発チーム4人で、そうした価値観・思想のすり合わせを行ったので

一部抜粋で共有いたします。

 

開発チーム思想

最も大切にしていること → 技術で問題を解決する

 

上記をより効率よく行っていくために、下記を意識する

- DXを高める

- 個々人がパフォーマンスを最大限発揮できる環境を作る

- チームとして成熟度を高める

- 組織やミッションにプライドを持つ(仕組みを作る)

 

といった具合です。

これが正解・不正解というものではなく、 one visa としては、こうした価値観を大切にしていますよと足並みを揃えることが非常に大切ですし、こうして明文化することで日々の行動の指針にもなると思っています。

実際の企画にもつながると思っていて、例えば

> 組織やミッションにプライドを持つ

という文脈でいうと、

開発チームだけで、弊社のプロダクトと密に関わりがある入国管理局に見学へ行ってみるという企画を立ち上げ、実際に行ったりもしました。

自分たちがどんなユーザーにどういった価値を届けようとしているのかは、チーム全員に知っておいてもらいたいし、こうした取り組みやそこから得た経験が、組織やミッションにプライドを持てる一部になると考えています。

 

正直、一機能を実装するという単位であれば、

エンジニア1人が黙々と開発するほうが早くリリースまで持っていけると思います。

チーム開発は、上記のようなすり合わせを行う必要もありますし、面倒は増えますw

それでもチームでやる意味は確実にあって、

圧倒的にデカいビジョンがあり、本気でそれを達成しようと思ったときに、足並みが揃ったチームがいることよりも頼りになることはありません。

実際、現在リリースしているβ版のプロダクトからほぼ刷新する形でプロダクトを作り直しているのですが、わたし1人では絶対に成し遂げることはできないと思っています。

1人で開発をするよりも、チームで開発をすることで視座が引き上げられますし、自分のアウトプットの質があがっていると実感しています。

チームで開発を行うことで、より遠くへ(高みへ)いけると信じています。

 

この1年では、1人で開発を行っていた経験があるからこそ、特にそういったチームの存在のありがたみをひしひしと感じることができました。

 

スタートアップでエンジニアに求められるスキル

上記のように、この1年で

  • エンジニアとして、どのように価値貢献をすべきか
  • チームで闘っていく大切さ

を学んできました。

わたし自身が行ってきた開発とすれば、

  • SPAプロダクトのコーディング, Vueを利用した開発
  • APIサーバーの開発
  • Heroku → AWS へのサーバ移行

と、フロント〜インフラまで多岐に渡っているのですが、中でも必要だなと感じているスキルが2つあります。

それは、

  • プログラミングの基礎
  • アンラーンできる勇気

です。

「プログラミングの基礎」というとざっくりしているかもしれませんが、やはり、基礎力は高ければ高いほうがいいと思います。

その時折で、求められる技術は変わります。しかも、すごい早さで...

この3ヶ月をみても、フロント〜インフラと幅広く担当をしており、必要なときに必要なものを学ぶことも求められます。

そのため、たとえば HTTP ってなんだってことでは困るわけです。。

精通している必要はないかもしれませんが、(わたしも精通できていません。)

ある程度、オブジェクト指向ってなんだとか、IPってなんだとか、コンポーネントってなんだみたいな部分は、理解しておくほうが良いと思います。

その上で、実際に利用する技術が変わってもなんとかなります。(たぶん)

 

2つ目が、「アンラーンできる勇気」なのですが、さらにざっくりしたな〜という印象かもしれませんw

学んだことや身につけたことを一旦リセットするという意味ですが、

今回の意図としては、「自分がやったことがあるやり方・うまくいっているやり方に固執しない」というのがより近いかなと思います。

これは技術だけにとどまる話ではないのですが、

1人でやっていたときに上手く行っていた方法が、2人チームになったときに上手く行くとは限らないですし、

2人チームで上手く行っていたことが、4人チームで上手く行くとも限りません。

状況が刻々と変わる中で、上手く行って「いた」方法に固執せずに、その時の新しい解を出し続けることが求められると思います。

上手くいっていたやり方を捨てるって、なかなか勇気がいるのですが、

思い切って判断できるスキルがあると非常に良いのではないかと考えています。

 

まとめ

長々と書いてきましたが、最後まで読んでくださり、誠にありがとうございます。

偉そうに述べてまいりましたが、所詮まだ1年しか経っておらず、なにも成し遂げられていないというのが現状です。

今後更に成長して、 one visa という会社をより素敵な会社にして、3年後くらいに

自分で読み直して、「こいつは若造だな〜」といまの自分に言ってやりたいと思います!w 

(そのためには、持病の腰痛が悪化しているので、しっかり通院して早く治しますw)

今年は、特にアウトプットを意識しておりますが、← あるある。

結局今回が初めてのブログになってしまいました。これを機に定期的に更新できるように頑張ろう。

 

 

最後に告知ですが、、

スタートアップで、エンジニアとして働くことに興味を持っていただける方がいましたら

絶賛募集中ですがので、お声がけいただけますと幸甚です。よろしくお願いいたします。

(こちらフロントの求人ですが、フロント以外も絶賛募集中なので、ぜひ。)

www.wantedly.com