Vue.jsで始めるComponent入門

Vue.jsとは

背景

業務ではphpやgoといったサーバーサイドの技術を扱っているが、現状フロントのjavascriptの部分がサーバーサイドエンジニアかデザイナーが分担して書いている。コーディング規約があいまいなことからフロントエンドをちゃんと学び始めました。cssも学び中...

なぜVueか

UXの需要の高まりから複雑な動きをまとめたりシンプル化に繋げてくれるJavascriptフレームワーク界隈 その中ではVue.js, React.js, Angularjs, ember.jsなど様々なフレームワークが溢れています。 業務でのjavascriptではJqueryをメインに扱っていたので個人OSS且つコミュニティが盛んで学習コストが比較的 高くない理由からVue.jsを学びはじめました。

今後はフロントエンド界隈の基礎的なことを学びながらメモがてらブログに起こそうかと思います。

開発環境構築

環境はjs Fiddleでもローカル環境でも大丈夫です。 一つのプロジェクトを作るならVue CLIでプロジェクトを作ることをおすすめします。

// vue.jsを扱うためのコマンドラインインタフェースをインストールします。
$ npm install -g vue-cli

// プロジェクトを作成します。(このあとプロジェクト名やes-lint、ユニットテストの設定など聞かれますが特になければEnterで進みます)
// 上記でvue-routerを入れるかと聞かれる箇所は本記事では Noで進んでいただけると良いです。
$ vue init webpack vue-component-tutorial
$ cd vue-component-tutorial

// プロジェクト作成後 `package.json` からパッケージをインストールします。
$ npm install

// buildすることでdistフォルダが作成されこの後表示される画面のhtmlが作成されます。
$ npm run build

// ローカル環境を立ち上げます
$ npm run dev // デフォルト8080のポートでブラウザでアクセスすることができます。

ここまでがローカル環境での環境構築です。 基本的な操作はVueの公式ドキュメントでまとめられております。 日本語でも訳されていて使い方がとてもわかりやすいです。

jp.vuejs.org

作成したプロジェクトのsrc/main.jsを確認すると vueのインスタンスが作成されていることがわかります。

import Vue from 'vue'

new Vue({
  el: '#app',
  render: h => h(App)
})

Componentとは

早速Componentについて説明します。 Componentは,定義したタグ名で親となるComponentのhtml上に記述できます。 以下の例では app-user というタグ名でtemplateタグ内に記述したhtmlのコードを挿入することができます。

<div id="example">
  <app-user></app-user>
</div>
<template>
    <div class="component">
        <h1>The User Component</h1>
        /** 中略  **/
    </div>
</template>

Componentの登録手法

ここでは先ほどのapp-user コンポーネントを登録する方法を記載します。

import User from './components/User.vue';

Vue.component('app-user', {
    appUser: User
})

全てのコンポーネントをグローバルに登録する必要はなく、 例えばコンポーネント名のグローバルでの衝突回避や コンポーネントの中にコンポーネントがあるようなネストした状態の時にはローカルに登録します。

import User from './components/User.vue';

export default {
    components: {
        appUser: User
    }
}

Component間の通信

コンポーネントの中にコンポーネントがある状態を コンポーネントの親子関係と呼びます。

コンポーネントにはdataを持つことができます。 例えばこんな感じでオプションをつけるとデータやメソッドを持つことができます。

以下の例では User.vueはユーザのプロフィール画面の親コンポーネントです。

User.vue(js部分)

    import UserDetail from './UserDetail.vue';
    import UserEdit from './UserEdit.vue';

    export default {
        // データを持つことができます。
        data: function () {
            return {
                name: 'Kosa',
                age: 24
            };
        },
        methods: {
            // メソッドを定義できます。
            changeName() {
                this.name = 'Anna';
            }
        },
        components: {
            appUserDetail: UserDetail,
            appUserEdit: UserEdit
        }
    }

User.vue(html部分)

<template>
    <div class="component">
        <h1>The User Component</h1>
        <p>I'm an awesome User!</p>
        <button @click="changeName">Change my Name</button>
        <p>Name is {{ name }}</p>
        <p>Age is {{ age }}</p>
        <hr>
        <div class="row">
            <div class="col-xs-12 col-sm-6">
                <app-user-detail
                        :myName="name"
                        :userAge="age"
                ></app-user-detail>
            </div>
            <div class="col-xs-12 col-sm-6">
                <app-user-edit
                        :userAge="age"
                        @ageWasEdited="age = $event"
                ></app-user-edit>
            </div>
        </div>
    </div>
</template>

この時に使うプロパティはpropsというプロパティを使うことで子は親からのデータを受け取ることができます。 propsの中のリテラルで型の制御もすることができます。名前は文字列なのでstringでしか許容されないので以下のようなコードになります。

UserDetail.vue

<template>
    <div class="component">
        <h3>You may view the User Details here</h3>
        <p>Many Details</p>
        <p>User Name: {{ switchName() }}</p>
        <p>User Age: {{ userAge }}</p>
    </div>
</template>

<script>
    export default {
        props: {
            myName: {
                type: String
            },
            userAge: Number
        },
        methods: {
            switchName() {
                return this.myName.split("").reverse().join("");
            }
        }
    }
</script>

この時に使うプロパティは$emitというプロパティを使うことで子は親へデータを受け渡すことができます。 $emitの第一引数に送るデータの名前、第二引数にはそのデータをつけて親コンポーネントのUser.vueにデータを渡し、 @ageWasEdited="age = $event" でイベントデータをageに格納します。

UserEdit.vue

<template>
    <div class="component">
        <h3>You may edit the User here</h3>
        <p>Edit me!</p>
        <p>User Age: {{ userAge }}</p>
        <button @click="editAge">Edit Age</button>
    </div>
</template>

<script>
    export default {
        props: ['userAge'],
        methods: {
            editAge() {
                this.userAge = 30;
                this.$emit('ageWasEdited', this.userAge);
            }
        }
    }
</script>

以上でコンポーネントの操作の基本ができました。 完成形はこちらになります。 応用するとコンポーネントのライフサイクルフックを利用してコードを書くことで コンポーネントの状態をより密に書くことができます。

github.com

まとめ

まだvueのチュートリアルをしたぐらいですがコードの理解がしやすそうな印象を受けました。

次回にはライフサイクルの紹介やカスタムイベント、vue-routerなどに関しても紹介することができたらと思います。

gRPCとgolangでブロックチェーンを作ってみる

長らく更新していなかったので雪で東京が4年ぶりの積雪に見舞われている中で久々の更新をしてみました。

作成した背景

近頃ビットコインや仮想通貨で盛り上がりを見せている日本にてブロックチェーン技術が注目を集めています。 ブロックチェーン技術は2009年にスタートし、今まで8年以上一度も落ちることなくサービスを稼働させていました。マウントゴックスの事件ではセキュリティでなく内部の人為的な行為が原因であることがわかり、注目を集めていました。 そんなセキュリティの高いブロックチェーン技術を作ってみたいと思ったことが今回の背景です。

tech-camp.in

環境

作成するための要件

  • gRPCの設定をする
  • goとgRPCでサーバーを立てる
  • ブロックの生成
  • ブロックのハッシュを次のブロックに含むデータ構造を実現する
  • ブロックチェーンのリストを表示する

gRPCのセッティングとgoでのサーバー接続の流れ

gRPCは、Googleによって開発されたRPCフレームワークで、HTTP/2を使用した通信層(ProtocolBuffersでシリアライズ)とProtocolBuffers(標準)としたテンプレートコードの生成がセットで提供されています。

最初にgRPCのドキュメントに沿ってパッケージを取得し .proto ファイルをコンパイルするコンパイラを用意します。

https://grpc.io/docs/quickstart/go.html

$ go get google.golang.org/grpc
$ go get -u github.com/golang/protobuf/protoc-gen-go

以下のように.proto ファイルを生成し、ルールを作成する

syntax = "proto3";

package proto;

// The blockchain service definition
service Blockchain {
    // Sends a AddBlockRequest Return AddBlockResponse
    rpc AddBlock(AddBlockRequest) returns (AddBlockResponse) {}
    rpc GetBlockChain(GetBlockchainRequest) returns (GetBlockchainResponse) {}
}

// The request AddBlockRequest containing the data
message AddBlockRequest {
    string data = 1;
}

// The request AddBlockResponse containing the hash
message AddBlockResponse {
    string hash = 1;
}

message GetBlockchainRequest {}

message Block {
    string hash = 1;
    string prevBlockHash = 2;
    string data = 3;
}
 
message GetBlockchainResponse {
    repeated Block blocks = 1;
}

上記生成した.proto をコンパイルして、サーバーとクライアントのひな形コード /proto/blockchain.pb.go が作成される

$ protoc --go_out=plugins=grpc:. proto/blockchain.proto

gRPCの設定が終わったので/server/main.goを作成しサーバーを立てていく

package main

import (
    pt "../proto"
    "./blockchain"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "log"
    "net"
)

type Server struct {
}

func main() {
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatalf("unable to listen on 8080 port: %v", err)
    }

    srv := grpc.NewServer()

    pt.RegisterBlockchainServer(srv, &Server{})
    srv.Serve(listener)
}

// TODO: 後ほどブロックを作成するために実装
func (s *Server) AddBlock(ctx context.Context, in *pt.AddBlockRequest) (*pt.AddBlockResponse, error) {
    return new(pt.AddBlockResponse), nil
}

// TODO: 後ほどブロックを取得するために実装
func (s *Server) GetBlockChain(ctx context.Context, in *pt.GetBlockchainRequest) (*pt.GetBlockchainResponse, error) {
    return new(pt.GetBlockchainResponse), nil
}

以下コマンドでサーバーが無事立ち上がっていることがわかります。

$ go run server/main.go

ブロックチェーンを作っていく流れ

ブロックチェーンを作る流れとしては以下のように組み立てていきます。 1. ブロックチェーン生成のための基盤を作る 2. ブロックを作る 3. ブロックチェーンのリストを表示する

まずはserver/blockchain/blockchain.go を作成する。

package blockchain

import (
    "crypto/sha256"
    "encoding/hex"
)

// ブロックの構造体
type Block struct {
    Hash          string
    PrevBlockHash string
    Data          string
}

// ブロックチェーンの構造体
type Blockchain struct {
    Blocks []*Block
}

// ブロックの構造体のHashにセットする
func (b *Block) setHash() {
    hash := sha256.Sum256([]byte(b.PrevBlockHash + b.Data))
    b.Hash = hex.EncodeToString(hash[:])
}

// 新しいブロックを作る
func NewBlock(data string, prevBlockHash string) *Block {
    block := &Block{
        Data:          data,
        PrevBlockHash: prevBlockHash,
    }
    block.setHash()

    return block
}

// ブロックチェーンにブロックを追加する
func (bc *Blockchain) AddBlock(data string) *Block {
    prevBlock := bc.Blocks[len(bc.Blocks)-1]
    newBlock := NewBlock(data, prevBlock.Hash)
    bc.Blocks = append(bc.Blocks, newBlock)

    return newBlock
}

// ブロックチェーンを作る
func NewBlockchain() *Blockchain {
    return &Blockchain{[]*Block{NewGenesisBlock()}}
}

func NewGenesisBlock() *Block {
    return NewBlock("Genesis Block", "")
}

/server/main.go のひな形のTODOの箇所を実装する

// AddBlockRequestを送るとAddBlockResponseのレスポンスを返す。(ブロックチェーンにブロックを追加し、追加したブロックを返却)
func (s *Server) AddBlock(ctx context.Context, in *pt.AddBlockRequest) (*pt.AddBlockResponse, error) {
    block := s.Blockchain.AddBlock(in.Data)
    return &pt.AddBlockResponse{
        Hash: block.Hash,
    }, nil

    return new(pt.AddBlockResponse), nil
}

// GetBlockchainRequestを送りGetBlockchainResponseのレスポンスを返す。(ブロックチェーンをリストで取得する)
func (s *Server) GetBlockChain(ctx context.Context, in *pt.GetBlockchainRequest) (*pt.GetBlockchainResponse, error) {
    resp := new(pt.GetBlockchainResponse)
    for _, b := range s.Blockchain.Blocks {
        resp.Blocks = append(resp.Blocks, &pt.Block{
            PrevBlockHash: b.PrevBlockHash,
            Hash:          b.Hash,
            Data:          b.Data,
        })
    }

    return resp, nil
}

client側からブロックを追加できるようにする /client/main.go を作成し以下コードを作成する

package main

import (
    "flag"
    "google.golang.org/grpc"
    "log"
    pt "../proto"
    "golang.org/x/net/context"
    "time"
)

var client pt.BlockchainClient

func main() {
    addFlag := flag.Bool("add", false, "add new Block")
    listFlag := flag.Bool("list", false, "get the blockchain")
    flag.Parse()

    conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure())
    if err != nil {
        log.Fatalf("cannot dial server: %v", err)
    }

    client = pt.NewBlockchainClient(conn)

    if *addFlag {
        addBlock()
    }

    if *listFlag {
        getBlockchain()
    }
}

// ブロックを追加する
func addBlock() {
    block, err := client.AddBlock(context.Background(), &pt.AddBlockRequest{
        Data: time.Now().String(),
    })
    if err != nil {
        log.Fatalf("unable to add block: %v", err)
    }
    log.Printf("new block hash: %s\n", block.Hash)
}

// ブロックチェーンを取得する
func getBlockchain() {
    bc, err := client.GetBlockChain(context.Background(), &pt.GetBlockchainRequest{})
    if err != nil {
        log.Fatalf("unable get blockchain: %v", err)
    }
    log.Println("blocks:")
    for _, b := range bc.Blocks {
        log.Printf("hash: %s, prev block hash: %s, data: %s", b.Hash, b.PrevBlockHash, b.Data)
    }
}

実行してみる

二つのプロセスを使って実行する。

1, server/main.goをbuildしサーバーを立てる

$ go build server/main.go
$ ./server

2, client/main.goを実行する。

オプションにaddをつけてブロックを追加し、オプションにlistをつけてブロックチェーンが作成されているかみてみよう

$ go run client/main.go --add
2018/01/20 22:05:32 new block hash: 5db0c1b5f14afc0085a3ac07221058dc100d4c5978c939842abef81eb65dae7e

$ go run client/main.go --list
2018/01/20 22:05:37 blocks:
2018/01/20 22:05:37 hash: 89eb0ac031a63d2421cd05a2fbe41f3ea35f5c3712ca839cbf6b85c4ee07b7a3, prev block hash: , data: Genesis Block
2018/01/20 22:05:37 hash: 5db0c1b5f14afc0085a3ac07221058dc100d4c5978c939842abef81eb65dae7e, prev block hash: 89eb0ac031a63d2421cd05a2fbe41f3ea35f5c3712ca839cbf6b85c4ee07b7a3, data: 2018-01-20 22:05:32.281834137 +0900 JST

無事ブロックが作成され新規で作成されたブロックに以前に作成したハッシュの情報を残していることが確認できました。

まとめ

簡易的なブロックチェーンを作ってみました。 gRPCを使うのも初めてで探り探り利用してきましたがなんとか作ることができました。 gRPC余裕があれば拡張をしてブロックチェーンを深く作ってみたいです。

github.com

Angular2で作る天気予報チュートリアルver.2

Angular2で天気予報サービス作成する

前回の「Angular2で作る天気予報チュートリアルver.1」の続きからです。前回は環境構築と、静的に都市を入力しその都市の名前から天気予報APIによって天気予報情報を得るところまでしました。

今回の目的

今回は都市の入力フォームから動的にデータを取得し、表示させることをやっていきます。

完成作品

f:id:suga-tech3:20161225171430p:plain

https://drive.google.com/file/d/0BzC949acVHdubU1jbFlpLWdtQmM/view?usp=sharing

完成ソース

github.com

ポイント

-- formを使って都市の天気予報を取得する

  • weather-search.component.ts
<form (ngSubmit)="onSubmit(f)" #f="ngForm">
    <label for="city">City</label>
    <input ngControl="location" type="text" id="city" (input)="onSearchLocation(input.value)" required #input>
    <button type="submit">Add City</button>
</form>
  • ①buttonのsubmitが押された時の処理
  • ②onSearchLocation()で都市の名前を取得する
  • ③ngOnInit()で予測予測変換機能作成する
export class WeatherSearchComponent
{
    private searchStream = new Subject<string>();
    data: any = {};

    // WeatherServiceを使う
    constructor (private _weatherService: WeatherService) {}

    // ①form内のボタンが押された時に天気情報を追加する
    onSubmit(form: ControlGroup) {
        this._weatherService.searchWeatherDate(form.value.location)
            .subscribe(
                data => {
                    const weatherItem = new WeatherItem(data.name, data.weather[0].description, data.main.temp);
                    this._weatherService.addWeatherItem(weatherItem);
                }
            );
    }
    // ②都市の名前を取得する
    onSearchLocation(cityName: string) {
        this.searchStream
            .next(cityName);
    }
    // ③予測変換してくれる機能
    ngOnInit() {
        this.searchStream
            .debounceTime(300)
            .distinctUntilChanged()
            .switchMap((input:string) => this._weatherService.searchWeatherDate(input))
            .subscribe(
                data => this.data = data
            );
    }
}
  • weather.service.ts

-- ①のsubmitが押された時にserviceにてAPIで都市のデータを取得する

searchWeatherDate(cityName: string): Observable<any> {
    return this._http.get('http://api.openweathermap.org/data/2.5/weather?q=' + cityName + '&APPID=任意のID&units=metric')
        .map(response => response.json())
        .catch(error => {
            console.error(error);
            return Observable.throw(error.json())
        });
}

-- ①のsubmitが押された時にserviceにてAPIで都市のデータをpushして追加する

addWeatherItem(weatherItem: WeatherItem) {
    WEATHER_ITEMS.push(weatherItem);
}

追加した都市のデータを取得し表示させる

  • weather-list.component
@Component({
    selector: 'weather-list',
    template: `
        <section class="weather-list">
            <weather-item *ngFor="#weatherItem of weatherItems" [item]="weatherItem"></weather-item>
        </section>
    `,
    directives: [WeatherItemComponent]
})
export class WeatherListComponent implements OnInit {
    weatherItems: WeatherItem[];

    constructor(private _weatherService: WeatherService) {
    }
    // 全天気予報情報を取得する
    ngOnInit():any {
        this.weatherItems = this._weatherService.getWeatherItems();
    }
}

上記でデータを追加する動きをすることができました。 他の機能やサイドバーは完成ソースを見てみてください。

まとめ

チュートリアルすべて終わりです。 componentの概念とAngular2のDIの使い方が複雑でなかなか理解するのに時間がかかった。すべて自分で作るにはなかなかの学習コストが必要になると感じました。

Angular2で作る天気予報チュートリアルver.1

背景

巷で流行っているReactとAngular2、今回はどちらも学習難易度が高いですが Reactより考え方が難しくないという理由でAngular2を自分で学んでみました。 今回は前回のlaravelでのショッピングサイトと同様に同じYouTuberのチュートリアルに沿って学習を始めていきました。

参考URL

Angular 2 Full App Tutorial - Weather App - #1 Introduction - YouTube

*事前にtypescriptをかじっておくことがおすすめです。

 環境構築

$ git clone https://github.com/mschwarzmueller/angular2-weather-app-tut.git
$ npm install
$ npm start

以下のような画面が表示されることを確認します。

f:id:suga-tech3:20161128145904p:plain

まずは構造を見ていきましょう。

├── LICENSE.md
├── README.md
├── app
│   ├── app.component.js
│   ├── boot.js
│   └── weather
├── assets
│   └── scss
├── dev
│   ├── app.component.js
│   ├── app.component.js.map
│   ├── app.component.ts
│   ├── boot.ts
│   └── weather
├── gulpfile.js
├── index.html
├── package.json
├── src
│   └── css
├── tsconfig.json
├── typings
└── typings.json

このようなディレクトリ構造がわかります

次のように複数の地域名と天気予報情報を表示してみましょう。

f:id:suga-tech3:20161128150003p:plain

作ってみよう

app/weatherにweather-item.tsを作成する。

  • WeatherItemのクラスを定義しコンストラクタにてそれぞれ入る引数と静的型付けを行う。
export class WeatherItem {
    constructor(public cityName: string, public description: string, public temperature: number) {
    }
}

app/weatherにweather.data.tsを作成。

  • 地域名、天気予報、気温のデータが入っている定数(WEATHER_ITEMS)をインスタンスを作成し用意してあげる。
import {WeatherItem} from "./weather-item";
export const WEATHER_ITEMS: WeatherItem[] = [
    new WeatherItem('London', 'Rainy', 6),
    new WeatherItem('New York', 'Sunny', 10)
];

app/weatherにweather-list.component.tsを作成 - weather.data.tsで作ったデータを受け取って整形し、weather-itemのコンポーネントにデータを渡す。

import {Component} from "angular2/core";
import {WeatherItemComponent} from "./weather-item.component";
import {WeatherItem} from "./weather-item";
import {OnInit} from "angular2/src/core/linker/interfaces";
import {WEATHER_ITEMS} from "./weather.data";
@Component({
    selector: 'weather-list',
    template: `
        <section class="weather-list">
            <weather-item *ngFor="#weatherItem of weatherItems" [item]="weatherItem"></weather-item>
        </section>
    `,
    directives: [WeatherItemComponent]
})
export class WeatherListComponent implements OnInit {
    weatherItems: WeatherItem[];

    ngOnInit():any {
        this.weatherItems = WEATHER_ITEMS;
    }
}
  1. OnInitというインタフェースを実装し、WEATHER_ITEMSをthis.weatherItemsにて受け取る
  2. 受け取ったデータをコンポーネント定義のtemplateにてangular2のfor文を用いweatherItemsを回す。 さらにdataの属性にitemをつけてweather-itemのコンポーネントにデータを渡す。
<weather-item *ngFor="#weatherItem of weatherItems" [item]="weatherItem"></weather-item>

app/weather-item.component.tsを作成

import {Component, Input} from 'angular2/core';
import {WeatherItem} from "./weather-item";

@Component({
    selector: 'weather-item',
    template: `
        <article class="weather-element">
            <div class="col-1">
                <h3>{{ weatherItem.cityName }}</h3>
                <p class="info">{{ weatherItem.description }}</p>
            </div>
            <div class="col-2">
                <span class="temperature">{{ weatherItem.temperature }}°C</span>
            </div>
        </article>
    `,
    styleUrls: ['src/css/weather-item.css'],
})
export class WeatherItemComponent {
    @Input('item') weatherItem: WeatherItem;
}

@Input('item')を用いてweather-list.component.tsのデータ属性itemを指定して データを取得し、templateのhtml文に{{ weatherItem.cityName }}のように入れたい箇所に書いていけば複数表示されていることがわかります。

まとめ

コンポーネントの概念が初めてで書き方が慣れないのと特殊なfor文や属性の受け取りに少し戸惑いましたがなんとか理解しながら進めていけました。 Angular2を学ぶためには事前にtypescriptを学ぶことで理解が早くなるのでそちらから学ぶことが必要であることと 次回にはデータを直書きでセットするのではなく動的に取得し表示させることができればと思います。

vagrantのホスト・ゲスト間の共有フォルダの作り方

環境

背景

Angular2を学ぶためにtypescriptの環境構築をvagrant上でしているときにvagrant側でファイルを作成時にホスト側にもそのファイルを共有させるディレクトリ(フォルダ)が欲しいと思い、色々と調べてみた。

ホスト側 (mac)

  1. Vagrantfileのファイルがあるか確認する
$ ls /Users/kosachan/Typescript
Vagrantfile
  1. Vagrantfileを編集する
$ sudo vi /Users/kosachan/Typescript/Vagrantfile

以下のように修正する。

config.vm.synced_folder "ホスト側Vagrantfileあるディレクトリ", "ゲスト側ディレクトリ"

config.vm.synced_folder "/Users/kosachan/Documents/vagrant", "/home/vagrant/typescript"

ゲスト側 (vagrant)

ホスト側の設定ができたらvagrant を再起動する。

vagrant reload

以上で設定が反映されました。

では実際に反映されるかやってみましょう。

[vagrant@localhost typescript]$ sudo vim test.txt     // txtの中身はhello world!
  • ホスト側 (mac)にて
$ ls /Users/kosachan/Documents/vagrant
test.txt

$ view test.txt
hello world!が表示されると設定がうまくいっています。

まとめ

簡単に共有フォルダを作ることができました。 typescriptのディレクトリを作っている通り次回はtypescriptでAngular2についての記事を書いていきます。

Laravel5.3とStripeAPIでECサイトを作ってみた。

Stripeとは

今あついベンチャー企業の台等となっている決済サービスのstripe。

シリコンバレーでもPayPal以上のペイメント企業になると言われている。 つい先日に日本でもサービスがローンチされたばかりで日本国内最大のクレジットカード会社、三井住友カード株式会社がStripeに資本参加するなどの注目のサービスである。

jp.techcrunch.com

背景

ECサイトを作ってみたかったこととlaravelで何かものを作りたいと思ったことからLaravelでECサイトを作ってみよう。 ただ、そんな簡単に作れるものじゃない...と思い何か材料がないかネットで探してみたところ あるYoutuberの人がlaravel5.2でshopping-cartを作ってみるというチュートリアルを発見!

https://www.youtube.com/watch?v=56TizEw2LgI&list=PL55RiY5tL51qUXDyBqx0mKVOhLNFwwxvH

これで実装してみよう!!

実装のポイント

  • resoucesのlayoutsフォルダにlayoutファイル,viewファイルを作っていく。
  • DBのproductsテーブルから商品一覧を表示させる。
  • 商品クリックでsessionに商品情報を保有する。
  • checkout(購入ボタン)で商品料金の合計と入力フォームを用意する。
  • stripeAPIを使って確定ボタンを押すとstripeのダッシュボードに購入金額が表示される。

f:id:suga-tech3:20161120201239p:plain

f:id:suga-tech3:20161120201301p:plain

*他にも会員か非会員かのAuthの認証もいれる必要があるのでそれの実装と分岐をさせる。

stripeAPIを使う流れ

1.stripeの会員になる

stripe.com

2.ダッシュボードからaccount settingボタンでAPIの情報が載っているので以下のように実装

レイアウトファイルで以下jsを呼び出す。 - checkout.js

Stripe.setPublishableKey('publish-key');

var $form = $('#checkout-form');

$form.submit(function (event) {
    $('#charge-error').addClass('hidden');
    $form.find('button').prop('disabled', true);
    Stripe.card.createToken({
        number: $('#card-number').val(),
        cvc: $('#card-cvc').val(),
        exp_month: $('#card-expiry-month').val(),
        exp_year: $('#card-expiry-year').val(),
        name: $('#card-name').val()
    }, stripeResponseHandler);
    return false;
});

function stripeResponseHandler(status, response) {
    if (response.error) {
        $('#charge-error').removeClass('hidden');
        $('#charge-error').text(response.error.message);
        $form.find('button').prop('disabled', false);
    } else {
        console.log(response);
        var token = response.id;
        $form.append($('<input type="hidden" name="stripeToken" />').val(token));
        // Submit the form:
        $form.get(0).submit();
    }
}
  • ProductConroller.php
        $oldCart = Session::get('cart');
        $cart = new Cart($oldCart);    // app/Cart.php

        Stripe::setApiKey('token');
        try {
            $charge = Charge::create(array(
                "amount" => $cart->totalPrice,
                "currency" => "jpy",
                "source" => $request->input('stripeToken'), // obtained with Stripe.js
                "description" => "Test Charge"
            ));

            $order = new Order();   // app/Order.php
            $order->cart = serialize($cart);
            $order->address = $request->input('address');
            $order->name = $request->input('name');
            $order->payment_id = $charge->id;

            Auth::user()->orders()->save($order);  // 保存
        } catch (\Exception $e) {
            return redirect()->route('checkout')->with('error', $e->getMessage());
        }

まとめ

ほぼ同じようにやっていくと完成できるので難易度は高くなかったが 若干laravelのバージョンが違うからか実装を変えていく箇所などがあった。 stripeAPIは数行のコードを加えるだけでオンライン決済機能を導入できるしECサイトを作ってみたくなったら動画なりドキュメントなりみてやってみてください。

github.com

新卒文系エンジニアIsucon6に参加してみた

Isuconとは

お題となるWebサービスを決められたレギュレーションの中で限界まで高速化を図るチューニングバトル、それがISUCONです。

ISUCON公式Blog

環境

  • サーバ
  • Webサーバ
    • nginx
  • OS
  • 言語
    • php7
  • DB
    • mysql5.7

ルール

  • 制限時間
    • 10時〜18時

チーム名簿だけ見れば気持ち悪い団体ですが偉大な先輩方2人と参加しました。

結論

これまでパフォーマンスの部分をあまり意識せず開発してきた身なので まぁ無理だと思って終了間際にスーパーで買ってきた92円のカップやきそば ×3 を振舞うことを念頭において参加 結論から申し上げるとやはり無力でした。 大きな課題はパフォーマンス部分もそうですがmysqlの氷山の一角部分しか知っていなかったことが明らかになりました。

Isucon(午前)

環境構築から
enomotodev先輩がAzureを起動・設定してデプロイする
鍵のバトンを渡されtakedajs先輩とsshで接続しにいく


その間にenomotodev先輩はnginxにてphpが動くように構築
自分とtakeda.js先輩はソースを見たり、gitbucketとsourcetreeの連携させたバージョン管理の設定をする。 無事scpコマンドでローカルに落とせたソース。 ブランチを切り、いざソースを見てみることに 以下が本問題のソース(今大会にて実装したソースです)

github.com

え〜と、isuda?isutar?
なにがなんやら view側ではhtmlではなくphpテンプレートであるtwigが使われていること

php側でisudaとisutarというファイルが似たソースがあること
一通り眺めてみると何やらmysqlの設定を見つけ とりあえずmysqlに入ってみる
構造はシンプルであるがとりあえずdumpをとっておく


無事とれたのを確認しdumpファイルを見てみるとIndexがついていないことが明らかに。
これはかなりの効果が出るのではないかと判断し、いろんな施策を考える。

とりあえずなんやかんやで午前の部はなんとか7位までつけたところでお昼休憩へ


Isucon6 (午後)

あれ?俺ら7位、いけるんじゃね?となった午前の部

から一転
構築された環境で様々な施策を打つことに


ただよくわからない
自分は「mysql パフォーマンス」などパフォーマンスに関わることを知ることからスタート
とりあえずInnoDBのメモリやQueryのデータ量などを知ることができたがここからどうパフォーマンスをどう上げるか

takeda.js先輩は細かい施策をどんどん実行していく。enomotodev先輩はAPIの改修という大きな施策を打ち出す。
自分はtakedajs先輩の出した施策からインフラ側をチューニングする

特にmysqlInnoDBのログファイルサイズを変更するために そのままの変更ではエラーが出るので


  • 一度mysqlを止める
  • 元にあるログファイルをリネームし、ログファイルサイズを設定
    • mysqlを起動させる

この施策だけで心肺蘇生法のような緊張感に包まれる。(多分自分だけ)

とにかくソースを見てもよくわからないので簡単な施策、計測、やきそばのタイミングを中心に行ってみた。
施策を打ち出すことができない無力さに心が折れかけるもなんとかパフォーマンスを元の状態までもっていくことに成功

ここでtakeda.js先輩の施策が当たる。
SQLのクエリのチューニングを行い、パフォーマンスが10000を超えかなり上がる。
それ以降は大きな施策を打ち出すことができなかったがベストスコアを更新していく

自分は17時以降はやきそばのチューニングにとりかかる
むしろ16時以降はやきそばのことしか考えてなかった。


結局最終結果は14620とtop10にはまだまだ遠い数字となりました。
ただ課題は明確にわかったのと外の世界のエンジニアのすごさに圧巻されたこと。
正直isuconは自分にとっては早かったが良い経験になりました。

これまでパフォーマンスにあまり興味を持てなかったが今回でもっと知りたくなったし、ソースを組む段階からパフォーマンスを意識した開発ができることが大切であると実感させられました。

来年はphpより高速な言語でベストを更新できるようになること 施策を生み出し自分がボトルネックに気づくことができることが来年に向けての課題となりました。

そのためにmysqlの根本的なところ(リレーショナルデータベースの本買いました)を理解しレベルアップしていくしかないと感じました。

Amazon CAPTCHA

それにしても学生で一般も交えた中でも上位のチームがあってとてもすごいとしか言いようがなかった。

みなさまおつかれさまでした。