読者です 読者をやめる 読者になる 読者になる

MatchinGoodエンジニアブログ

MatchinGood株式会社のエンジニアリング事情を共有します

GitHubのメンションをDMで教えてくれるボット

f:id:takuseno:20161213175351j:plain

エンジニアアルバイトの妹尾(id:takuseno)です。

この記事はボット・クローラー Advent Calendar 2016の14日目です。

今回はGitHubのメンションをSlackのDMで教えてくれるボットを紹介します。

弊社でのボット

弊社ではHubotをHeroku上で動かしています。以前にもこのブログで紹介しました。

blog.matchingood.com

メンションをDMで教えてくれる機能は初期から用意していました。理由は弊社の開発フローでGitHubの存在が大きくなったのですぐにGitHub上での更新を知る必要が出てきたからです。

blog.matchingood.com

特にメンションはSlackでDMで伝えてくれると開発用のチャンネルの通知を出さないようにしていても通知してくれるのでとても便利であることは明らかでした。

コード

プルリクエストへのレビューコメントのhookに対するコードは簡単に書くと以下のような感じです。

users =
  'takuseno': 'seno'

module.exports = (robot) ->
  robot.router.post "/github/pr/review", (req, response) ->
    data = req.body
    action = data.action
    path = data.comment.path
    user = data.comment.user.login
    url = data.pull_request.html_url
    number = data.pull_request.number
    title = data.pull_request.title
    repo = data.pull_request.head.repo.name
    body = data.comment.body

    if action != 'created'
      response.end ''
      return

    attachment =
      content:
        fallback: body
        pretext: path
        mrkdwn_in: ["text", "pretext", "fields"]
        color: "#f1c40f"
        author_name: users[user]
        title: "\##{number} #{title}"
        title_link: url
        text: body

    mentions = body.match /@(\S+)\s*/
    if mentions?
      for index, name of mentions
        if users[name]?
          userId = users[name]
          robot.adapter.client.openDM userId, (data) ->
            attachment.channel = userId
            robot.emit "slack.attachment", attachment

  response.end ''

ポイントは上記のコードではusersGitHubのユーザー名とslackのユーザー名の対応表を入れておいて、コメントを正規表現でメンションを探してslackに投げているところです。

あとは、DMの見た目を良くするためにattachmentというのを使っています。slackのattachment自体は以下に載っています。

api.slack.com

実際の感じ

f:id:takuseno:20161213174233p:plain

これでGitHub上でのコミュニケーションがよりスムーズになりました!

弊社ではこういう取り組みに興味のあるエンジニアを探しています。一緒にサービス開発をしませんか?

en-gage.net

Laravelでファイルをデータベースで管理するライブラリを公開した

f:id:takuseno:20161206134747j:plain

エンジニアアルバイトの妹尾(id:takuseno)です。

今回はLaravel上で簡単にファイルをデータベースで管理できるようにするライブラリを公開したので紹介したいと思います。

laravel-eloquent-storage

github.com

このライブラリはEloquentモデルに

<?php

use MatchinGood\EloquentStorage\EloquentStorage;

class UserFile extends EloquentStorage
{

}

という感じで継承させるだけで、そのモデルでファイルを管理できるようになります。

仕組み

データベースにファイルを保存するのはデータベースの容量を圧迫するだけでなくパフォーマンスにも影響が出てきます。

なので、容量単位の単価がはるかに安いS3にファイルを保存するようにしました。このライブラリではこの辺りのファイル管理を隠蔽しています。

概念的には以下の図で表せます。 f:id:takuseno:20161206132835j:plain

S3にはファイル名の衝突を避けるためにユニークなファイル名で保存し、MySQLには保存したユニークなファイル名とオリジナルのファイル名を保存します。

これにより、ファイル名の衝突を避けつつダウンロード時にはオリジナルのファイル名を使うことができます。

実際に使うと

例えばユーザーがアップロードしたファイルをUserFileというモデルで保存しようと思った時は

<?php

$file = $request->file('file');
$userFile = new UserFile;
$userFile->saveFromUploadedFile($file);

たったこれだけでファイルを保存することができました。さらにそのファイルをダウンロードさせようと思ったらコントローラで、

<?php
$userFile = UserFile::find(1);

return response()->downloadEloquentStorage($userFile);

とするだけでダウンロードさせることができます。これはResponse Macroをサービスプロバイダーで定義することでdownloadEloquentStorageという関数を使うことができるようになっているからです。

laravel.com

開発環境時の工夫

システムを開発時にS3に繋げたくありません。そこでconfig/eloquentstorage.phpでドライバーを設定することができるようになっています。

<?php
return [
    // this is the same drivers as Storage facade on Laravel
    // ELoquentStorage uses Storage facade internally
    'driver' => 'local'
];

このライブラリは内部的にはStorageを使っているので、S3にあげたくない場合はドライバーをlocalにすることでローカル環境にファイルを保存します。

実際にデプロイするときにこのドライバーをs3とすることでS3に保存できるようになります。

まとめ

最近弊社では積極的にオープンソースのライブラリを公開してコミュニティに貢献しようという熱が高まってきました。このような軽量なものから、まだ公開していない大きなライブラリまであります。このような環境はエンジニアにとってはとても魅力的な環境であると考えているからこそ実践させてもらっています。

このようなエンジニアリングで会社の成長と飛躍にチャレンジしたいと思うエンジニアの方を募集しています!

是非一度話を聞きにきてください!

en-gage.net

PHPのGeneratorは本当にメモリ消費量が減るのか実験

modern phpという書籍の勉強の過程で、PHPのGenerator機能の実験をしました。 www.amazon.co.jp

Generator機能を使うことで、 大量のデータを繰り返しで処理するときに、 使用メモリ量が少なくなるということなのですが、 実際のコードを見てみましょう。

たとえば、1番から100,000番までの番号を、 順番にエコーする以下のコードがあるとします。

<?php
function makeNumbers() {
    $numbers = array();
    for ( $index = 0; $index <= 100000; $index++ ) {
        $numbers[] = $index;
    }
    return $numbers;
}

$startTime = microtime(true);
$startMemory = memory_get_usage();
foreach ( makeNumbers() as $number ) {
    echo $number;
}
$executionTime = microtime(true) - $startTime;
$usedMemory = (memory_get_peak_usage() - $startMemory) / (1024 * 1024);

echo "実行時間:{$executionTime}s";
echo "使用メモリ量:{$usedMemory}MB";

$numbersという配列に大量のデータが入るので、 メモリをたくさん使ってしまいますね。 Generatorを使えば、以下のように書けます。

<?php
function makeNumbers() {
    for ( $index = 0; $index <= 100000; $index++ ) {
        yield $index;
    }
}

$startTime = microtime(true);
$startMemory = memory_get_usage();
foreach ( makeNumbers() as $number ) {
    echo $number;
}
$executionTime = microtime(true) - $startTime;
$usedMemory = (memory_get_peak_usage() - $startMemory) / (1024 * 1024);

echo "実行時間:{$executionTime}s";
echo "使用メモリ量:{$usedMemory}MB";                             

yieldを使うことで、このように実装できます。 大量のデータが入っている配列の$numbersがなくなりましたね。

本当にメモリ消費量が減ったのでしょうか? 以下のような結果になりました。

種類 メモリ消費量[MB] 実行時間[s]
ジェネレータなし 13.972MB 0.286s
ジェネレータあり 0.004MB 0.401s

メモリ使用量が激減しました! 実行時間が増えているのが気になるところです。

当社ではGeneratorが使えるPHP7で開発をしています。 モダンな環境でPHP技術を磨きたい方は是非当社にご応募ください!

採用情報 | 人材紹介・人材派遣のシステム・パッケージソフトならマッチングッド

GitHubフローを助けてくれるボットを作った

f:id:takuseno:20161129200842j:plain

エンジニアアルバイトの妹尾(id:takuseno)です。

最近は転載の記事が多かったのですが、今回はGitHubフローを助けてくれる簡単なボットを作ったので紹介します。

ボット環境

弊社ではHubotを使ってSlack用のボットを作っています。

github.com

HubotはGitHub製のボットライブラリで、coffeescriptで簡単にかけて、そのままHerokuに簡単に上げることができます。

最初はSlackに移行したときにせっかくならボットを作ろうと思って始めて、ネタ機能が多かったり、新人研修の題材に使っていましたが、現在はユーザー数をログから計算して教えてくれたり、サービスにエラーが発生したら教えてくれたりなど実用的な機能を追加しています。

弊社でのGitHubフロー

開発のフローは基本的に以下のようになっています

  • 実装方針などをissueに書く
  • 実装する
  • プルリクエストを出す
  • レビューする
  • マージする

普通のフローですが、弊社ではブランチ名にもルールを設けています。

(feature|refactor|fix)-[issue No.]-description

例をあげるとfeature-100-implement_user_registrationという感じです。

基本的にプルリクエストはひとつのissueに紐付いているため、プルリクエストがマージされるとそのブランチとissueは不要となるため消すことになっていたのですが、人間はサボる生き物なので徹底されずに、ゴミのブランチやissueが多く残ってしまっていました。

ボットを作った

そこで、プルリクエストをマージしたら関連するブランチとissueを自動的に削除するボットを作りました!

apiUrl = "https://api.github.com/repos/xxxx"
querystring = require('querystring')

module.exports = (robot) ->
  robot.router.post("/github/pr/cleaner", (req, response) ->
    data = req.body
    action = data.action
    pullRequest = data.pull_request
    if action == 'closed' && pullRequest.merged
      repo = pullRequest.head.repo
      name = repo.name
      branchName = pullRequest.head.ref

      # delete branch
      deleteBranchRequest = robot.http("#{apiUrl}/#{name}/git/refs/heads/#{branchName}")
        .header('Authorization', "token #{process.env.GITHUB_TOKEN}")
        .del()
      deleteBranchRequest((err, res, body) ->
        if err
          robot.messageRoom(channel_name, "Deleting #{branchName} was failed")
      )

      # close issue
      issueNumber = branchName.match(/-(\d+)-/)
      if issueNumber[1] != undefined
        number = issueNumber[1]
        console.log("request #{apiUrl}/#{name}/issues/#{number}")
        closingIssueRequest = robot.http("#{apiUrl}/#{name}/issues/#{number}")
          .header('Authorization', "token #{process.env.GITHUB_TOKEN}")
          .patch(JSON.stringify(state: 'closed'))
        closingIssueRequest((err, res, body) ->
          if err
            robot.messageRoom(channel_name, "Closing \##{number} was failed")
        )

      url = require('url')
      query = querystring.parse(url.parse(req.url).query)
      channel_name = query.channel
      user = pullRequest.user.login
      robot.messageRoom(channel_name, "Good job, #{user}!\nLeave the rest to me!")
    response.end('')
  )

これをHerokuに上げた状態でGitHubのWebhookにhttps://xxx/github/pr/cleaner?channel=yyyというのを設定してプルリクエストのフックをとれるようにすればもう動きます。

プルリクをマージすると

f:id:takuseno:20161129200323p:plain

という感じで応えてくれます。

これにより100%ブランチとissueが整理されるようになりました。

まとめ

このような感じで弊社ではボットを使って開発や運用の効率を上げる取り組みを行なっています。ボットがフローの一部となって人間の負担を減らしてくれるととても助かりますし、ボットにもっとすごいことをやらせてサービスを加速させることもできます。

このような環境に興味があれば是非一緒にサービスを開発してみませんか!?

en-gage.net

Circle CI + AWS + Dockerでデプロイを自動化する

f:id:takuseno:20161124135129j:plain

この記事は以下の記事の転載です。

www.wantedly.com

エンジニアアルバイトの妹尾(id:takuseno)です。

今回はタイトルの通り、Circle CI + AWS + Dockerの組み合わせで簡単にサービスのデプロイを自動化した話をします。

弊社サービスWorkinGoodではGItHub flowを採用しており、masterへマージした後に自動的にElastic Beanstalkへ新しいバージョンを作るところまで行っています。依存サービスとの関係もありデプロイするタイミングはこちらが手動でAWSGUI上から行っています。

f:id:takuseno:20161124134910j:plain

また、弊社では開発環境構築にDockerを使っていますが、本番環境へのデプロイにも使用しています。

blog.matchingood.com

これを実現するために具体的には以下のものを使っています。

  • GitHub
  • Circle CI
  • Docker
  • AWS Elastic Container Registry (Docker registry)
  • AWS Elastic Beanstalk

この中でもデプロイを圧倒的に簡単にしているのがDockerとElastic Beanstalkの組み合わせです。Elastic BeanstalkとはAWSが提供しているサービスの一つで、あらかじめ用意しておいた環境定義ファイルによって自動的にAuto Scalingとデータベースを構築してくれるサービスです。

こちらに簡単な説明があったので見てみるといいと思います。

dev.classmethod.jp

かなりお手軽にこの仕組みを作ることができるので簡単に手順を示していきたいと思います。

circle.yml

最初にcircle.ymlにどのように書けばいいかの例です。

machine:
    timezone:
        Asia/Tokyo

    services:
        - docker
dependencies:
    pre:
        - sudo pip install --upgrade awscli

deployment:
    production:
        branch: master
        commands:
            - ./deploy.sh $CIRCLE_SHA1

今回の話に関係のあるところだけ書きました。

deploymentにbranchをmasterに指定することでmasterブランチのCIが通った時にcommands以下が実行されます。Circle CI内ではGitHubのコミットIDが$CIRCLE_SHA1という変数に入っています。

aws-cliを使用するために$AWS_ACCESS_KEY_IDと$AWS_SECRET_ACCESS_KEYの環境変数を設定する必要がありますが、セキュリティ上の問題からバージョン管理に含めずにCircle CI上のUIから設定します。

deploy.sh

SHA1=$1
DOCKERRUN_FILE=$SHA1-Dockerrun.aws.json
EB_BUCKET= # Elastic Beanstalkのバージョンファイルが入るS3のバケット名

aws ecr get-login | bash

docker build -t {AWS ID}.dkr.ecr.us-east-1.amazonaws.com/{リポジトリ名}:$SHA1 .
docker push {AWS ID}.dkr.ecr.us-east-1.amazonaws.com/{リポジトリ名}:$SHA1

sed "s/<tag>/$SHA1/" < Dockerrun.aws.json > $DOCKERRUN_FILE

aws s3 cp $DOCKERRUN_FILE s3://$EB_BUCKET/$DOCKERRUN_FILE

aws elasticbeanstalk create-application-version\
  --application-name application \
  --version-label $SHA1 \
  --source-bundle S3Bucket=$EB_BUCKET,S3Key=$DOCKERRUN_FILE

Elastic Beanstalkのバージョンはただのjsonファイルで管理されているのであらかじめDockerrun.aws.jsonという名前で以下のファイルを用意しています。

{
    "AWSEBDockerrunVersion": "1",
    "Image" : {
        "Name" : "{AWS ID}.dkr.ecr.us-east-1.amazonaws.com/{リポジトリ名}:<tag>"
    },
    "Ports" : [{ "ContainerPort": "80" }]
}

やっていることの流れとしては

  • aws-cliでログイン
  • Dockerコンテナをビルド
  • Dockerコンテナをpush
  • バージョンファイルをS3に上げる
  • Elastic Beanstalkに新しいバージョンを作る

という感じです。 ここまで行えばElastic Beanstalkに新しいバージョンが作成されているのがわかると思います。

注意点

Circle CI用に適切な権限を持ったIAMを用意する必要があります。以下の操作ができるようにIAMを作りましょう。

  • S3のバージョンファイルが入るバケットへのアップロード
  • Docker Container Registryへのpush
  • Elastic Beanstalkのバージョンの作成

まとめ

簡単にデプロイの自動化ができそうなのがわかっていただけたと思います。弊社のような小規模のチームでの開発では自動化できるところは自動化を進めて効率を高める必要があります。 このようなモダンな仕組みもすべてアルバイトエンジニアによって構築されています。プログラミング初心者の方はこのような新しい仕組みを学ぶことができ、実力のある人はDevOpsをさらに磨き上げることもできる職場です。

是非一度お話を聞きに来ませんか!?

en-gage.net

PHP勉強会でLTしてきた話

f:id:takuseno:20161115154753j:plain

エンジニアアルバイトの妹尾(id:takuseno)です。

10月のPHP勉強会@東京でLTをしてきたのでその話をしようと思います。

PHP勉強会の雰囲気

社内の勉強会には複数の会社に参加したことがあるのですが、社外でいろいろな人が来るような勉強会は初めてでした。

しかし、この勉強会は毎回半数近くが初参加で、毎回最初に全員で自己紹介するというとても馴染みやすい雰囲気でした。また、年齢も上から下まで色々な方がいましたし、女性の方も数人いらっしゃいました。

ちなみに学生は僕の他にもう一人いました。

LT

勉強会初参加なので当然LTも初めてでした。5分間の発表時間なので技術的な話題をしようと思うとかなり辛いです。最初はコードのスライドが5枚くらいあって5分じゃ無理だとなったので何回もリファクタリングしました。

以下が実際のスライドです。

評判

最初の方はテンションを上げてなんとか場を盛り上げられてた感があったのですが、いざコードが登場すると一気に反応が悪くなって失敗したなという感じでした。

きている人の層が初心者から上級者までさまざまなので、全員に受けるような話をしようと思うともっとシンプルにまとめる必要がありそうです。

一応2次会ではエンジニアの方に良かったと言ってもらえたのでホっとしました笑

まとめ

PHP勉強会@東京は初心者や勉強会初めての人でも参加しやすいので時間があれば是非参加してみるといいと思います。技術的な話を聞くだけでなく色々な人と出会えて、普段はできないようは話ができるのもいいところだと思います。次回の募集が始まっているので参加してみてはいかがでしょうか。

開催予定イベントリスト - PHP勉強会@東京 | Doorkeeper

そして弊社では一緒にサービスを開発する仲間を募集しています!

是非一度話を聞きにきてください!

en-gage.net

DockerでLaravelの開発環境を爆速で構築する

f:id:takuseno:20161111145642j:plain

この記事は以下の記事の転載です。

www.wantedly.com

MatchinGood株式会社でエンジニアアルバイトをしている妹尾(id:takuseno)です。

弊社では人材紹介会社様と人材派遣会社様向けのシステムの開発をしており、今年初めごろに派遣会社スタッフ様向けの新サービスであるWorkinGoodを始めました。

www.workingood.com

主にPHPを使用して開発していますが、新しいサービスに関してはAWSやDocker、Reactなどトレンドとなっているものに追随して技術を選択しています。

そこで今回は弊社の開発環境構築にDockerを採用した話をしたいと思います。

なぜDockerを導入したか

弊社ではプロダクションへのデプロイでもDockerを使用していますが、先ほどもお話ししたように弊社では複数のプロジェクトがあり、それぞれが異なるPHPのバージョンで動いています。このため毎回環境構築しようと思うと複数バージョンのPHPを入れる必要がありとても手間となってしまいます。

そこでDockerを使うことで気軽に環境を構築できるようにしました。

Dockerに関しては様々な記事が出ているのでここでは説明しませんが、今ではDocker ToolboxやDocker for Mac、Docker for Windowsのおかげで手軽に導入することができます。

どうやっているのか

ここではLaravelとMySQLの標準的な環境を想定します。

まずはLaravelのコンテナを作る必要があります。

FROM php:7.0.8-alpine

RUN apk --no-cache add curl nodejs python make g++ &&\
    npm install -g gulp &&\
    curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer &&\
    docker-php-ext-install pdo_mysql curl

WORKDIR /var/www/localhost/htdocs

EXPOSE 8000

CMD ["php", "-S", "0.0.0.0:8000", "-t", "/var/www/localhost/htdocs/public"]

alpine linuxという軽量なイメージをベースにしたPHP7のコンテナを使用しています。

最後にPHP付属の開発用サーバーを起動して終わりです。

MySQLは特にカスタマイズしないので公式のものをそのまま使います。

Dockerコンテナ立ち上げ

bashスクリプトを用意して一発で立ち上げます。

# 用意したDockerfileのビルド
docker build -t matchingood/application:dev .

# 現在のパスを取得
path=$(cd $(dirname $0) && pwd)

# MySQLの公式イメージ
docker pull mysql

# 各イメージを動かす
docker run --name mysql -e MYSQL_ROOT_PASSWORD=mysql -d mysql
docker run -p 8000:8000 \
    --link mysql:mysql \
    --name application \
    -v ${path}:/var/www/localhost/htdocs \ 
    -t -d matchingood/application:dev

# Laravelの設定ファイル
cp .env.example .env

# applicationとリンクしているMySQLのホストを取得
dbhost=$(docker exec application env | grep MYSQL_PORT_3306_TCP_ADDR | cut -d '=' -f 2-2)

# Laravel設定ファイルのMySQLの情報をコンテナ内から見えるホストに置換
sed -i -e "s/DB_HOST=127.0.0.1/DB_HOST=${dbhost}/g" .env

# MySQLコンテナにアプリケーション用のデータベースを作成
docker exec mysql mysql -u root --password=mysql -e "create database application;"

# Laravelを動かすために必要なものたち
docker exec application composer install
docker exec application php artisan key:generate
docker exec application php artisan migrate
docker exec application php artisan db:seed

これでもうLaravelの開発環境を整えることができました!

Docker Compose

LaravelのコンテナにMySQLとElasticSearchのコンテナをリンクさせるときにlinkオプションで指定しています。ですが、Docker Composeと言うものを使うことで綺麗にこのあたりのオーケストレーションを行うことができます。ただ今回はそこまで複雑な構成ではないのでbashスクリプトで書いています。

まとめ

実際の例を見て、Dockerで簡単に環境を構築できることが分かったと思います。そして弊社では仲間を募集しています!プログラミング初心者から実力者までやりがいを感じることができる職場です! 是非一度話を聞きに来てみませんか!?

en-gage.net