TECH PLAY

株式会社エニグモ

株式会社エニグモ の技術ブログ

247

Enigmo Advent Calendar 2018 の4日目の記事です。 この記事の目的 Enigmoが運営している BUYMA では古代から運用している jQuery の他に、2016年頃から一部ページのフロントエンドをReact/Reduxで構築しています。 私自身もEnigmoに入社してからの約三年間でReact/Reduxアプリケーションの開発に多数携わってきましたので、そこで培った知見を共有したいと思います。 React/Reduxの利点 まずはじめに、ReactとReduxを使うメリットを再確認しておきたいと思います。 それぞれのメリットをしっかりと認識しておくことで、実装する際どう書くか迷ってしまった場合などにそのメリットを最大限活かす選択をすることができます。 Reactの利点 コンポーネント 化が容易で再利用性が高い 状態をDOMから分離できる(Stateless) Reduxのような外部ライブラリを併用すると コンポーネント 自体からも分離できる (props) => DOM Tree という関数のように扱える(同じinputなら常に同じoutputを得られる) Propsとして イベントハンドラ を渡していく アーキテクチャ なので、ロジックをまとめて 疎結合 にしやすい Reduxの利点 Reducer、ActionCreator、Componentといった利用側が書くべきモジュールはすべて純粋な関数で書ける ほとんど関数の集合だけでアプリケーションが完成する 副作用のある処理はすべて後述するMiddleware層に持っていける 上記の理由からテストが非常に書きやすい React/Reduxはこう書く!! では実際に私がReact/Reduxアプリケーションを実装する際指標としているプ ラク ティスを解説していきたいと思います。 Container Componentsの分割 Container Components とは Redux Store と connect している コンポーネント のことです。 詳細はReduxの公式Doc をご確認ください。 Container Componentsを適切に分割することでコードの見通しを良くします。 Containerの中にContainerが存在する、 Container in Container も許容します。 分割されていない例 import { connect } from 'react-redux' import * as React from 'react' import ComponentA from '../components/ComponentA' import ComponentB from '../components/ComponentB' import * as actions from '../actions' class BadContainer extends React.Component { componentDidMount() { this .props.fetch() } render() { return ( <div> <ComponentA name= {this .props.nameA } handler1= {this .props.handlerA1 } handler2= {this .props.handlerA2 } /> <ComponentB name= {this .props.nameB } handler1= {this .props.handlerB1 } handler2= {this .props.handlerB2 } /> </div> ) } } const mapStateToProps = state => { return { nameA: state.a.name, nameB: state.b.name } } const mapDispatchToProps = dispatch => { return { handlerA1() { dispatch(actions.actionA1()) } , handlerA2() { dispatch(actions.actionA2()) } , handlerB1() { dispatch(actions.actionA1()) } , handlerB2() { dispatch(actions.actionA2()) } , fetch() { dispatch(actions.fetchData()) } } } export default connect(mapStateToProps, mapDispatchToProps)(BadContainer) この例では一つのContainerで2つの コンポーネント 、ComponentAとComponentBを表示しようとしています。 今回の例では2つだけですが、今後3つ、4つと表示する コンポーネント が増えていくとその分必要なハンドラーやプロパティが増えていくため、 mapStateToProps と mapDispatchToProps が肥大化してしまい見通しが悪くなります。 分割されている例 // GoodContainer.jsx import { connect } from 'react-redux' import * as React from 'react' import ContainerA from './ContainerA' import ContainerB from './ContainerB' import * as actions from '../actions' class GoodContainer extends React.Component { componentDidMount() { this .props.fetch() } render() { return ( <div> <ContainerA /> <ContainerB /> </div> ) } } const mapDispatchToProps = dispatch => { return { fetch() { dispatch(actions.fetchData()) } } } export default connect(()=> { return {} } , mapDispatchToProps)(GoodContainer) // ContainerA.jsx import { connect } from 'react-redux' import * as React from 'react' import ComponentA from '../components/ComponentA' import * as actions from '../actions' const mapStateToProps = state => { return { name: state.a.name, } } const mapDispatchToProps = dispatch => { return { handler1() { dispatch(actions.actionA1()) } , handler2() { dispatch(actions.actionA2()) } , } } export default connect(mapStateToProps, mapDispatchToProps)(ComponentA) // ContainerB.jsx import { connect } from 'react-redux' import * as React from 'react' import ComponentB from '../components/ComponentB' import * as actions from '../actions' const mapStateToProps = state => { return { name: state.b.name, } } const mapDispatchToProps = dispatch => { return { handler1() { dispatch(actions.actionB1()) } , handler2() { dispatch(actions.actionB2()) } , } } export default connect(mapStateToProps, mapDispatchToProps)(ComponentB) 分割されている例では、もともと componentDidMount でデータフェッチしていた箇所だけを元のContainerに残し、 ComponentA と ComponentB に関するプロパティとハンドラーをそれぞれ ContainerA と ContainerB に分割しています。 こうすることでコードの肥大化を防ぎ見通しがかなり良くなったと思います。 ただし、全ての コンポーネント を connected にするとやりすぎなので適切な単位で分割する必要がありますのでご注意ください。 例えば ECサイト の場合、 商品名 、 商品コメント 、 配送方法 、のようなおおざっぱな単位で分割していき、それでもコードの見通しが悪いと感じたら更に分割するというような感じで実装しています。 またContainer Componentsのテストの仕方については以前書いた記事がありますので読んで頂けると幸いです。 https://qiita.com/hiyamamoto/items/281709cc2a98268fb6c2 Presentational Componentsの分割 前項に続き コンポーネント の分割についてです。 Presentational Components についての詳細は Reduxの公式Doc をご確認ください。 Presentational Components を分割する理由としては前項と同様に見通しを良くするため、テストのしやすさのためです。 また、Reactのメリットにも書いてある再利用性を高めるという利点もあります。 まずは分割されていない例を見ていきましょう。 適切に分割されていない例 function Page(props) { return ( <div> <div> <h1> { props.headerTitle } </h1> </div>> <div> <button>進む</button> <button>戻る</button> { props.value } </div> <div> <a href= "/path/1" >Link1</a> <a href= "/path/2" >Link2</a> </div> </div> ) } こちらの例では複数の div 、 button 、 a 要素が出てきています。 そんなに大きな コンポーネント ではありませんが既にちょっと見通しが悪くなっていると思います。 また、この コンポーネント のテストは以下になります。 ※ コンポーネント のテストは enzyme というライブラリの使用を前提としています describe( '<Page />' , () => { const wrapper = shallow(<Page />) it( 'render 進むButton' , () => { // button が複数出てくるのでうまく特定出来ない expect(wrapper.find( 'button' ).fisrt().contains( '進む' )).to.be. true } ) } ) このように複数の同一要素がある場合は wrapper.find('button').first() のように表示順を意識して コンポーネント の取得をする必要が出てきてしまい、ただ表示順が変わっただけでテストが壊れてしまいます。 では分割されている例を見てみましょう。 適切に分割されている例 function Page(props) { return ( <Container> <Header title= { props.headerTitle } /> <Body> <ForwardButton onClick= { props.onClickForward } /> <BackwardButton onClick= { props.onClickBackward } /> <BodyContent value= { props.value } /> </Body> <Footer> <Link1 /> <Link2 /> </Footer> </Container> ) } 分割されている例 では 分割されていない例 で見られた div などが一切出てきていないため見通しが良くなっているのがわかると思います。 また、それぞれの コンポーネント の意味が コンポーネント 名からひと目で分かるようになっています。 この コンポーネント のテストはこちらです。 describe( '<Page />' , () => { const wrapper = shallow(<Page />) it( 'render 進むButton' , () => { // ForwardButton というコンポーネントがあるかテストするだけで良い expect(wrapper.find(ForwardButton)).to.have.length(1) } ) } ) 進むボタン が コンポーネント 化されたため、 戻るボタン との表示順を気にする必要がなくなり、デザインの都合で表示順が変わったとしてもテストが壊れることがなくなりました。 ComponentをStatelessに保つ Reactの利点として状態をDOMから分離できるということを前述しましたが、 コンポーネント からも状態を分離することで本来の コンポーネント が持つViewとしての役割だけに専念させることができます。 Reactの基本として state というもので状態を管理することができますが、その state そのものを持たない コンポーネント を Stateless Functional Component と呼びます。 React/Reduxアプリケーションでは state は基本的に redux store で管理し、 コンポーネント では state を使わないようにすることで状態管理を一元化することができます。 ただし、ボタンクリック時にモーダルを表示したり、フォーカスしたときに値を加工するだけなど、その コンポーネント 内で完結するような state を持つことはさほど問題にはならず、全ての状態を redux store で管理しなければいけないというわけではないと個人的には考えています。 ある コンポーネント の状態を別のツリーの コンポーネント で参照したい場合や、 ドメイン ロジックに関わる状態は redux store で管理し、 コンポーネント 内部で完結する状態はその コンポーネント の state として管理するように柔軟に設計するのがベターです。 Stateless Functional Component の書き方 state や lifecycleメソッド を持たない場合は class シンタックス ではなく、関数を使います。 コンポーネント 名として関数名が使われるため、 arrow function より通常の関数として書くとよいです。 function FooComponent(props) { return ( <div> 名前: { props.name } 年齢: { props.age } </div> ) } 利点 ただの関数なのでテストしやすい stateを持ってないことが一発でわかる Reducerの分割 Reduxでは combineReducers という関数を使って通常Reducerを分割すると思います。 Reduxの公式Doc ではReducerを分割する際、 コンポーネント の レンダリング ツリーで分割するのではなく ドメイン データごとに分割することを推奨しています。 また、 DomainState 、 AppState 、 UIState という3つのStateに分割することが提案されています。 DomainState 例えば、商品や取引などの ドメイン 特有のstateのことです。 前述した レンダリング ツリーごとの分割と ドメイン データごとの分割の比較をしてみます。 レンダリング ツリーごとの分割 レンダリング ツリーごとの分割は簡単に言うと画面ごとの分割ということです。 下記は 新規画面 、 編集画面 、 一覧画面 といった画面ごとに分割している例です。 reducers ├── newReducer.js ├── editReducer.js └── listReducer.js ドメイン データごとの分割 変わって ドメイン データごとの分割では、 商品 、 配送方法 、 ブランド などの ドメイン データで分割しています。 reducers ├── productReducer.js ├── shipphingReducer.js └── brandReducer.js AppState アプリケーション全体の state は ドメイン データ用のReducerとは別のReducerとして用意すると、見通しが良くなります。 例えば、データをローディング中かどうかを管理する isLoading などの state はこちらに含めます。 UIState モーダルの表示状態などのUI特有の state も別のReducerで管理します。 ただし、前述のように コンポーネント の state として持つことも多いです。 stateは POJO (Plain Old JavaScript Object) state は基本的に immutable(不変) object として扱います。 下記のようなコードはNGです。 const defaultState = { foo: 'foo' , bar: 'bar' } const reducer(state = defaultState, action) => { if (action.type === 'Foo Action' ) { state.foo = action.payload // fooだけ変更したい return state } } immutable.js を使えばかんたんに不変オブジェクトを生成できます。 import { Map } from 'immutable' const defaultState = Map( { foo: 'foo' , bar: 'bar' } ) const reducer(state = defaultState, action) => { if (action.type === 'Foo Action' ) { return state.set( 'foo' , action.payload) // fooだけ変更したい } } 上記の Map.prototype.set は常に新しい Map の インスタンス を返します。 ただし、 Map は普通のオブジェクトのようにプロパティにアクセスできないので、 コンポーネント 内でアクセスする際には state.get('foo') のようにする必要があります。 また、 API などから取得してきた JSON を毎回 Map オブジェクトに変換する必要があるため結構面倒です。 それ、ES2015+でできるよ const defaultState = { foo: 'foo' , bar: 'bar' } const reducer(state = defaultState, action) => { if (action.type === 'Foo Action' ) { return { ...state, foo: action.payload // fooだけ変更できる! } } } state は色々な場所( コンポーネント 、 API クライアントなど)でアクセスするので、「immutableオブジェクトになってるか?」といちいち判定するのは面倒です。 常に POJO にしておくことでその手間を減らすことができます。 Reducerから ドメイン ロジックを分離したい場合 ドメイン データ用のReducerに ドメイン ロジックを書いてしまうとコードがどんどん肥大化していってしまうため、下記のように ドメイン データを models ディレクト リ配下に書きたくなると思います。 import MyClass from '../models/MyClass' const defaultState = new MyClass( { foo: 'foo' , bar: 'bar' } ) const reducer(state = defaultState, action) => { if (action.type === 'Foo Action' ) { // ドメインロジックはドメインクラスに委譲 return state.changeFoo(action.payload) } } このようにしたい場合は models 配下からクラスではなく関数をエクスポートすることで代替できます。 import { changeFoo } from '../models/MyDomainModel' const defaultState = new MyClass( { foo: 'foo' , bar: 'bar' } ) const reducer(state = defaultState, action) => { if (action.type === 'Foo Action' ) { // ドメインロジックはドメインモデルの関数に委譲 return changeFoo(state, action.payload) } } export const changeFoo = (model, value) => { // 複雑な処理 return { ...model, foo } } 他にも immutable.jsのRecordを使う方法 などがありますが、前述の通り変換の手間などを考えると個人的にはあまりおすすめしません。 FSA(Flux Standard Action)を使う Flux Standard Action とは https://github.com/redux-utilities/flux-standard-action actionの型が実装者によってまちまちになると読みづらいし不便だから標準化しましょうね、という話です。 割と界隈では デファクト になってる気がします。 非同期処理の成功、失敗ごとに LOAD_SUCCESS 、 LOAD_FAILURE のようにactionを分けるのではなく LOAD_FINISH のように一つにまとめることが出来ます。 { type: 'LOAD_FINISH' , payload: { id: 1, text: 'Do something.' } , meta: { ... // metadata } } { type: 'LOAD_FINISH' , payload: new Error(), error: true } アクションの型が統一されて書く方も読む方も負荷が減るのでおすすめです。 非同期処理 reduxの登場人物(component, reducer, actionCreator)は純粋な関数の集まりなので副作用のある処理を書く場所がありません。 一般的に副作用のある処理は Middleware層 を利用します。 非同期処理をする為のMiddlewareとしては redux-thunk と redux-saga が有名です。 redux-thunk vs redux-saga redux-thunk Pros API がかんたんで学習コストがほぼ0 サッと導入してサッと書ける Cons actionCreatorにロジックが入り込む actionCreatorから純粋さがなくなる そのため actionCreatorのテストが複雑になる redux-saga Pros 副作用のある処理が完全に分離できる API が豊富にあり並列化したりスロットルしたり色々出来る [redux-saga]フォームに高速で入力するとガックガクになる問題 - Qiita 高テスタビリティ Cons 学習コストが高い Generator関数はとっつきにくい API が多い 小さめのアプリケーションでは redux-thunk 、大きくて複雑なアプリケーションでは redux-saga 、 というように使い分けるといいでしょう。 まとめ どの フレームワーク にも言えることですが、見通しがよくメンテしやすいアプリケーションを書くためにはコードを適切な単位で分割することが非常に重要です。 React/Reduxアプリケーションでは コンポーネント を分割し再利用性を高めたり、状態を適切に分割することで、それぞれのメリットを最大限に活かせると思います。 Reduxの公式Doc では今回書いたReduxアプリケーションを設計する上での考え方が詳細に書かれていて非常に参考になるのでぜひご一読ください。 株式会社 エニグモ 正社員の求人一覧 hrmos.co
はじめに こんにちは。2018年9月入社でデータ分析担当の@Tawasshyです。 この記事は Enigmo Advent Calendar 2018の3日目 です。 弊社ではファッション ECサイト である BUYMA を展開しております。 売り手となるバイヤー(パーソナルショッパー)は世界中に在住しており、多種多様な商品を買い付けして膨大なSKUが存在します。 一方で、買い手側の購買行動も多岐に渡るのも必然となります。 そのような複雑な関係性を分析する状況においても、今回紹介するバスケット分析のような、基本的とも言える分析手法は解釈もしやすく、効果的な手段の一つになり得ると思っております。 バスケット分析について バスケット分析は何と何の商品が一緒に買われているかに着目する分析方法であり、マーケット・バスケット分析、アソシエーション分析とも呼ばれています。 一般的な話 有名な事例としては、「おむつとビール」が同時に買われやすい、というルールの発見があります。 また、Agrawal氏らが1994年に発表した論文「Fast algorithms for mining association rules」( IBM Almaden Research Center)が、この分析手法が普及したきっかけとなったことが有力な説のようです。 なぜ SQL でやるのか バスケット分析は R言語 のarulesというライブラリを使うのが、もっとも手軽な方法であると思いますが、今回は SQL での実行を紹介します。 弊社ではエンジニアだけではなく、ディレクターやマーケター、カスタマサポートといったビジネスサイドのメンバーも SQL を日常的に叩いており、 SQL での分析の幅を拡げることに貢献できたら良いなと思ったことが SQL でやる理由です バスケット分析詳細 まず、二つの商品X、Yがあったとします。 商品Xが買われた場合、商品Yという商品を買う確率を扱います。その指標として以下の指標を扱います。 今回算出する指標 信頼度(confidence) 商品Xが買われた場合に、商品Yを買う確率。 リフト値 商品Yが買う確率が、商品Xを買った場合に商品Yを買う確率がどれだけ変化したかの倍率。 (信頼度➗商品Yが買われる確率) Xを買ってYを買う確率がどれだけ持ち上がったかと解釈できる。 今回算出しない値 支持値(support) XとYが同時に買われている確率を示します。バスケット分析においてはこれも重要な指標ですが、今回は説明を割愛します。 環境構築について 手元のローカルマシンで簡単に再現できるので SQLite でやってみます。 macOS の場合は以下のように brew で一発です。なお、 Windows でも手軽にインストールできますが、ここでは割愛します。 $ brew install sqlite3 データの準備について SQLite を起動します。カレント ディレクト リにbasket.sqlite3というの名のDBが生成され、DBに接続した状態となります。 $ sqlite3 basket.sqlite3 無事に起動できたらこのようにterminalにこのように表示されます。 sqlite > まず、にテーブルの定義を行います。 create table sales_history( item_category char ( 12 ), user_id char ( 8 ) ); 購買履歴を作成します。 insert into sales_history values ( ' トップス ' , ' UID100001 ' ), ( ' 財布 ' , ' UID100001 ' ), ( ' 財布 ' , ' UID100002 ' ), ( ' トップス ' , ' UID100002 ' ), ( ' 靴 ' , ' UID100002 ' ), ( ' トップス ' , ' UID100002 ' ), ( ' アウター ' , ' UID100002 ' ), ( ' アウター ' , ' UID100102 ' ), ( ' ワンピース ' , ' UID100103 ' ), ( ' 靴 ' , ' UID100104 ' ), ( ' 財布 ' , ' UID100104 ' ), ( ' 靴 ' , ' UID100105 ' ), ( ' 靴 ' , ' UID100106 ' ), ( ' トップス ' , ' UID100107 ' ), ( ' トップス ' , ' UID100108 ' ), ( ' 靴 ' , ' UID100109 ' ), ( ' 財布 ' , ' UID100110 ' ), ( ' 靴 ' , ' UID100111 ' ), ( ' トップス ' , ' UID100111 ' ), ( ' 靴 ' , ' UID100112 ' ), ( ' 靴 ' , ' UID100112 ' ), ( ' トップス ' , ' UID100113 ' ), ( ' 財布 ' , ' UID100113 ' ), ( ' ワンピース ' , ' UID100114 ' ), ( ' ワンピース ' , ' UID100114 ' ) ; これでデータの準備はできました。 バスケット分析の実行 信頼度の算出 select combi_count.item_category, combi_count.item_category2, cast (combi_count.order_count as real)/item_count.order_count as confidence from ( select item_category, count ( distinct user_id) as order_count from sales_history group by item_category )item_count inner join ( select X.item_category, Y.item_category as item_category2, count ( distinct X.user_id) as order_count from sales_history as X inner join sales_history Y on X.user_id = Y.user_id and X.item_category Y.item_category group by X.item_category,Y.item_category )combi_count on combi_count.item_category = item_count.item_category ; 以下のように出力されます。 商品X、商品Y、信頼度という順番です。 商品X 商品Y 信頼度 アウター トップス 0.5 アウター 財布 0.5 アウター 靴 0.5 トップス アウター 0.166666666666667 トップス 財布 0.5 トップス 靴 0.333333333333333 財布 アウター 0.2 財布 トップス 0.6 財布 靴 0.4 靴 アウター 0.142857142857143 靴 トップス 0.285714285714286 靴 財布 0.285714285714286 財布を買ったユーザーがトップスを買う信頼度がもっとも高いことが分かります。 リフト値も追加して算出 上の sql が書ければ、全体でYが買われている確率を算出し、joinして信頼度を割ればリフト値が算出できます。 select combi_count.item_category, combi_count.item_category2, cast (combi_count.order_count as real)/item_count.order_count as confidence, cast (combi_count.order_count as real)/item_count.order_count / AllBuyY.order_count as lift from ( select item_category, count ( distinct user_id) as order_count from sales_history group by item_category )item_count inner join ( select X.item_category, Y.item_category as item_category2, count ( distinct X.user_id) as order_count from sales_history as X inner join sales_history Y on X.user_id = Y.user_id and X.item_category Y.item_category group by X.item_category,Y.item_category )combi_count on combi_count.item_category = item_count.item_category -- 全体でYが買われている確率を算出してjoinする inner join ( select item_category, cast ( count ( distinct user_id) as real) / ( select count ( distinct user_id) from sales_history) as order_count from sales_history group by item_category ) AllBuyY on combi_count.item_category2 = AllBuyY.item_category ; 以下のように、出力されます。 商品X、商品Y、信頼度、リフト値を出しております。 商品X 商品Y 信頼度 リフト値 アウター トップス 0.5 1.25 アウター 財布 0.5 1.5 アウター 靴 0.5 1.07142857142857 トップス アウター 0.166666666666667 1.25 トップス 財布 0.5 1.5 トップス 靴 0.333333333333333 0.714285714285714 財布 アウター 0.2 1.5 財布 トップス 0.6 1.5 財布 靴 0.4 0.857142857142857 靴 アウター 0.142857142857143 1.07142857142857 靴 トップス 0.285714285714286 0.714285714285714 靴 財布 0.285714285714286 0.857142857142857 信頼度、リフト値について ある商品を買う確率としての指標として、どちらが大事というのはケースバイケースとなりますが、 両方が高い値でないとユーザーの傾向を示す効果的な分析にならないことが言えます。 リフト値が高く、信頼度が低い場合 全体で商品X, あるいは商品Y自体を買われている確率が全体で低い場合に起きます。 商品Xが買われることが少ない為、リフト値が高い値を示してもYを買う確率は小さいままになります。 また、商品Y自体を買われている確率が全体で低い場合は、リフト値が掛けかれる元の確率が低いことになる為、Yを買われる確率は低いままということになります。 リフト値が低く、信頼度が高い場合 何もしなくてもYを買われる確率が高く、Xを買っても買わなくても、Yを買う確率の増減が小さい状態を示します。 そもそも購買履歴が少ない場合… 偶然、信頼度、リフトが高く出るということが起こり得るので、Xを買ったユーザーYの購買促進を行うキャンペーンなどの施策を行っても有効に働かない場合があります。 実際にDBに接続して行う場合は 同じテーブルへの問い合わせが散在しているので、 PostgreSQL , MySQL , SQLServer 等ではwith句は必須であると思います。 SQL でやるのがめんどくさいなと思ったら.. データを抽出する必要がありますが、 R言語 でarulesというライブラリを使えばもっと手軽にできますし、無料です。 さいごに 弊社ではデータ分析に取り組む一方、データ分析基盤を整え、データ活用の効率化とアクセスビリティを推進しております。 エンジニアはもちろん、データを活用しながら「世界を買える」ビジネスに挑戦してみたいメンバーのジョインをお待ちしております! やんちゃであれ! ENIGMO7より 参考 https://www.albert2005.co.jp/knowledge/marketing/customer_product_analysis/abc_association https://codezine.jp/article/detail/10284
こんにちは、Enigmo 新卒エンジニアの @sean0628_i です。 Enigmo Advent Calendar 2018 2日目の記事です。 初めに Accelerated Mobile Pages の略であり、高速でスムーズにWeb ページを表示するためのライブラリー、或いはその仕組みのことです。 通常のHTML に比べて、制限が多く存在します。 今回は AMP 公式のドキュメントの仕様に関する箇所 が英語だったので、読解し要点をまとめます。 AMP 制限 特定の要素を マークアップ に含める。 JavaScript は利用不可。 inline CSS のみ利用可。 -> 50KB の制限付き 特定のHTML tag は利用不可。 HTML 内、コメント不可。 特定の CSS properties は利用不可。 必要な マークアップ <!doctype html> でHTML を書き始める。 <html ⚡> tag を含める。(<html amp> でも代用可能) <head> tag と<body> tag を含める。(通常のHTML では任意) <head> tag 内の一番初めに、<meta charset=" utf-8 "> を含める。 <head> tag 内に、<link rel=" canonical " href="$SOME_URL"> を含める。( $SOME_URL は通常版のHTML のURL、通常版のHTML が存在しない場合はAMP のURLを入れる。) <head> tag 内に、<meta name="viewport" content="width=device-width"> を含める。 minimum-scale=1 および、 initial-scale=1 も含めることが勧められる。 <head> tag 内に、<script async src=" https://cdn.ampproject.org/v0.js "></script> を含める。 <head> tag 内に、AMP Boilerplate Code(head > style[amp-boilerplate] と noscript > style[amp-boilerplate])を含める。 HTML tags tag 適正 script ×(type が application/ld+json の場合のみ○) noscript ○ base × img amp-img にて代用 video amp-video にて代用 audio amp-audio にて代用 iframe amp-iframe にて代用 frame × frameset × object × param × applet × embed × form ○( amp-form extension を含めることで利用可) input elements 一部(<input[type=image]>、<input[type=button]>、<input[type=password]>、<input[type=file]>)を除いて○<fieldset>、<label> は○ button ○ style <head> tag 内に、一つのみ追加可。 amp-custom attribute を持たせる必要あり。 link microformats.org に、登録されている rel は○ meta http-equiv attribute が特定の値に利用可。詳細: AMP validator specification a ○ href attribute を javascript: から始めてはいけない。 target を設定する場合、 _blank を利用。 svg 殆どの SVG は利用可 Comments HTML 内でコメントは利用できない。 CSS 50,000 bytes の上限を超えてはいけない。 !important は利用できない。 transition および、animation 関連は opacity 、 transform properties のみ利用可。
こんにちは、18年新卒エンジニアの @sean0628_i です。 早いもので今年ももう12月、Enigmoにジョインしたのが今年の春なので、気がついたら入社してから半年ほどが経っていますね。。。 さて、12月といえば Advent Calendar の季節ですねー笑 今年はEnigmoも初めてAdvent Calendar を公開することとなりました。 というのも、新卒の私が「面白そうだなぁー。」、とボソッと呟いたところ、部長殿から「やってみたらー。」、とOKをいただきましたので、 他のメンバーにもご協力をいただき、 Enigmo Advent Calendar 2018  を公開することになりました。 記念すべきEnigmo Advent Calendar 2018の1日目は、文系学部から新卒でエンジニアになった私が今までに読んで、実際に業務で役に立った技術書5選をお届けします。 そもそもAdvent Calendarとは? さっきから、「”Advent Calendar”とか、横文字多用しやがって。」、という方、大変失礼しました、、、ご説明します。 Advent Calendar とは、元々クリスマスまでの日数を数えるためのカレンダーで、日ごとに小さなポケットを設け、そこにお菓子を入れて楽しんだりするものだそうです。 数年前からWeb 業界の企業ではではそれに倣って、クリスマスまでの25日間記事を公開するようになりました。 つまり、我々が意味するAdvent Calendar とは、25日間記事を公開し続けるという言わば苦行のことです。 ただ、エンジニアとして新たな知識の習得、既知の知識の共有は避けられません。 ということで、今回は苦行を断行します。 * なお、苦行ゆえ時々お休みすることもあるかと思いますが、その点はご容赦いただければと思います。 オススメの技術書5選 1. プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで (Software Design plusシリーズ) こちらの本は、知人の勧めで入社する前に読みました。当時は本当に Ruby 初心者、というか プログラミング言語 初心者でした。 そんな私でも途中で挫折せず、 Ruby を学べたのはこの本のおかげかなぁと。 この本では実際に手を動かして学習することが出来ます。 ひたすら読むだけだと飽きてしまいますし、言語習得のためには実際に手を動かすのが一番かとも思います。 そういった意味でこの本はオススメです。 2. Ruby on Rails 5アプリケーションプログラミング 上記の本で Ruby を一通り学んだ後、こちらで Rails を学びました。 もし、アプリ作成の一連の流れを知りたいということであれば、 Rails tutorial がオススメです。 一方で、 この本では、 Rails 組み込みのメソッドや、 Rails の仕組みが事細かに紹介されています。 Rails を体系的に学びたいという方や、 Rails tutorial やったけどもう少し深く Rails を知りたいという方にはこの本がオススメです 。 3. Everyday Rails - RSpecによるRailsテスト入門 この本は入社直後、研修の一環で読みました。 当時は、午前中に本を読み、午後に実務でアウトプットするという形式でした。右も左もわからず、テストを書くのが本当に嫌いでした笑 しかし、この本で基礎を学び、実務でのテストコードの実装を重ねた結果、今ではテストを書くことが苦ではなくなりました。 さらには、テストコードがない ソースコード を見ると不安を覚える身体になってしまいました笑 とてもわかりやすいのでオススメです。 4. スッキリわかる SQL 入門 ドリル215問付き! (スッキリシリーズ) 実務では主に Ruby on Rails を使用しているのですが、事あるごとに SQL の記述に迫られます。 ちなみに、Enigmoのメンバーは、エンジニアに限らず殆どの人が SQL を扱う事ができます。 もともと マーケティング 業界で インターンシップ をしていた私には、これがとても衝撃でした。 ともかくも、この本が良かった点はブラウザ上に環境が整備されているので事前の準備が不要で、すぐに SQL を学習できたということです。 実践問題も付いているので、この本一冊で SQL の基本をしっかりと身に付けることができると思います。 5. リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice) プログラミングを進める上で必要な 命名規則 、コード記述の作法に関して学びました。 正直に話すと、プログラミングを初めて1ヶ月くらいの時にこの本を読みました。 その時は、大した気づきもなく「レビュー高い割に、ふーん。」、くらいな感想しかありませんでした。 しかし、つい最近読み返してみると、当たり前だけど現状できていないことが多く記述されていることに気がつきました。 Enigmoのエンジニアの卓上でもちらほら見かけますし、一読の価値はあると思います。 所感 駆け足になりましたが、新卒の私がオススメする実務で役立った技術書5冊上げてみました。 これまでに読んだ本で他にも紹介したい良書はたくさんあります。 オブジェクト指向 の話とか、DB設計の話とかたくさん。 しかし、今回は日々 Ruby を扱うエンジニアとして、言語系で実務に役立った本に的を絞り紹介してみました。 また機会があれば、他の良書も紹介したいと思います。この辺で、Enigmo Advent Calendar 2018 1日目終わりたいと思います。 最後まで読んでいただきありがとうございます。
こんにちは。エンジニアの Steven です。 広島で9月18日から 20日 まで行われた RubyKaigi 2017 に山本さんとエンジニア二人で行ってきましたので、どうだったのかについてレポートさせていただきます。 RubyKaigi は毎年 Ruby 言語を中心に行われる会議です。 最近は日本を巡って毎回違う都市で行われてて、今回は広島で行われました。 日本語のセッションもあれば、英語のセッションもあって、外国人の参加者が少なくはないイベントです。 Ruby のカンファレンスの中でレベルが一番高いと言われています( Ruby の作者曰く)。 会議は月曜日から水曜日まで行われてて、事前の週末に広島まで行けば、東京に住んでいる私達にとっては、広島で少し観光をする機会でもあって、そうできるように準備していましたが、残念なことにこの週末はちょうど台風がその近くまで通ってきて、観光するどころか、新幹線ではそもそも行けるのかと、逆に心配することになりました。 ですが、会議の3日間自体は天気は極めてよくて、日焼け止めが必要になるのではないかという逆転にもなりました。 1日目 朝起きて、 元安川 (今調べました)を渡って、会議が開催されてる 広島国際会議場 まで行ってから、入場券を受けて、会議スタート! (目をつぶっていてすみません。後から気づきました。) 初日で見てきた トーク は 基調講演、Nobuyoshi Nakada Fiber in the 10th year、 Koichi Sasada How Close is Ruby 3x3 For Production Web Apps?、Noah Gibbs Gemification for Ruby 2.5/3.0、Shibata Hiroshi How to optimize Ruby internal、Shizuo Fujita I quit my job to write my own language: Goby、Stan Lo Ruby Committers vs the World 今見返してみると、毎日の仕事ですぐに活かせそうな トーク は初日はそんなになかったかと思いますが、 Ruby のコミュニ ティー はどう構成されているのか、どんな課題を抱えているのか、何をやっているのかはより具体的に理解できて、他の Rubyist の毎日に触れて勉強になりました。 この日の最後のセッションとして、 Ruby のコミッターが壇上に上がって、全員で議論をしながら、聴衆の質問に答えてたというイベントがありましたが、この日の中で一番記憶に残ってるところです。 質問に答えるためにコミッターの皆さんが議論にのめり込んで、質問一つだけで1時間が立ってしまいそうな状況に数回なってしまいましたが、その議論を聞くのは単に面白くて、話題はなんであろうと、専門的な技術力を持ってる人が周りにいれば、それだけで面白くなると感しました。 初日のセッションが終わってから、晩御飯が目的で街に出ててたのですが、なぜか赤色のシャツを着ている人が多くて、最初はよくわかりませんでしたが、なるほど、広島の カープ が日本一になった直後でした。 広島市 は全体的に盛り上がっていて、東京に戻ってきたのではないかと、ところどころ人混みができてました。 その夜は広 島風 お好み焼 きを食べる機会になったのですが、やはり美味しくて、2日目も3日目ももう一度食べようという衝動に耐える必要が出ました。 2日目 翌日は朝に スターバックス によってから、会議場に戻って、2日目スタート! 2日目で見てきた トーク は 基調講演、 Yukihiro Matsumoto The Ruby Module Builder Pattern、Chris Salzberg Improve extension API : C++ as better language for extension、Kouhei Sutou Automated Type Contracts Generation for Ruby 、Valentin Fondaratov Type Checking Ruby Programs with Annotations、Soutaro Matsumoto Ruby Language Server、Fumiaki Matsushima Write once, run on every boards: portable mruby、Yurie Yamane Lightning Talks 基調講演は今回は Ruby の作者によって行われてて、やはりまつもとさんは本当に実在する人で、自分が普通の人間でも実際に会える方だとわかりました。 基調講演でも少し紹介されましたが、この日の注目は Ruby での型の導入の トーク だったかと思います。 最近新しくできた言語の中では静的型付けの言語が多くあって、どんどん人気になってますが、動的型付けである Ruby でも型のいいところを適切な形で活かせるのかという考えがあって、今回はそれについての トーク は2つもありました。 Ruby には型が実際に入ってしまうのかどうかはまだわからないのですが、型のチェックをしてくれるツールを Ruby に入れることでどうなれそうなのか、 Ruby の未来を少し見れました。 個人的には Lightning Talks はその日の一番面白いセッションでした。 1時間で5分だけの トーク が12あって、何かをゆっくり学ぶという目的より、いろんな情報を短時間で伝えて、面白話を交えながら、聴衆を楽しませるのがそのライトニング トーク の一つの印象でした。 ビデオは公開されましたので、技術の話で少し笑いたければ、その トーク をおすすめします。 その日は昼休みに 原爆ドーム を見に行く時間は少しあって、日本の 世界遺産 の一つを写真にとどめることができました。 一言でまとめさせていただきますと、感動しました。 その夜の晩御飯は海鮮料理で、少し高めな食事でしたが、広島のもうひとつの名物を食べれてよかったです。 3日目 いよいよの最終日! ホテルからチェックアウトして、会議場に戻って、最終日スタート! この日見てきた トーク は Compacting GC in MRI 、Aaron Patterson Ruby for Distributed Storage System、Satoshi Tagomori Pattern Matching in Ruby 、 Yuki Torii Memory Fragmentation and Bloat in Ruby 、Nate Berkopec Busting Performance Bottlenecks: Improving Boot Time by 60%、Julian Nadeau How to write synchronization mechanisms for Fiber、Masatoshi Seki Towards Ruby 3x3 performance、Vladimir Makarov この日は基調講演は特にありませんでしたが、注目はパフォーマンスに関する トーク でした。 Ruby の GC をどう改善できるか、メ モリー 関連の問題をどう調査・解決できるか、 Ruby をどう3倍早くに改善できるか、違う種類のパフォーマンスの問題についていろんな トーク があって、基礎知識としても実践力としても役に立ちました。 この前、 Ruby の作者が設定した Ruby 3x3 という目的は、設定されただけで実際にそれを簡単に実現できるのかどうかは個人的には少し疑問に思ってたのですが、最後の トーク でそれを実現できそうなプロジェクトを紹介してくださった方が現れて、そんなに遠い未来じゃないと思うようになりました。 面白い演説で聴衆の笑いを集めた閉会の辞の後、お土産を買って、無事に東京に帰りました。 今回会社で広島までこれたのは私達二人だけでしたが、得てきた知識を東京に残った他のエンジニアに共有する予定です。 終わりに イベントが多い出張でしたが、RubyKaigi に参加できてよかったです。 新しい基礎知識と Ruby コミュニ ティー に関する知識を積めて、勉強になったカンファレンスでした。 Ruby 言語のカンファレンスに参加するのはこれで初めてでしたが、 Ruby に限らずこれからも他に多くある プログラミング言語 を中心にしたカンファレンスに参加したいと思います。 今まで実際に参加してきたのは、大学で開催されたカンファレンスだけであって、それ以外の トーク はすべてビデオで見てきましたが、前の私と同じく、まだカンファレンスに出席したことがない方に出席をおすすめします。一度やってみたら、中々やめられなくなってしまうかもしれません。
こんにちは。エンジニアの山本です。 5/23に開催された、 【 ヒカ☆ラボ 】大規模サービスがリスクをとってまでモダンな開発環境にリプレイスした理由~ここだけの苦労話や手法を交えお話します~  というイベントに参加 & 登壇してまいりましたのでレポートします。 今回のイベントのテーマは レガシー改善 という、長年継続しているサービスでは避けては通れないものでした。 弊社のサービスである BUYMA もローンチから10年以上経ち、溜まりに溜まった技術的負債を日々返済しています。 Reactを導入した話 弊社からは2人登壇させて頂きまして、私、山本は "React導入時の苦労話とこれからについて" と題しまして、ある機能のリプレイスプロジェクトでReactを導入した話をしました。 既に デファクト・スタンダード になったと言えるReactですが、導入当時の一年前は大規模なサービスに導入したという事例もまだ少なく、如何にして非SPAな Webサービス にReact/Reduxをマッチさせるかという点でとても苦労しました。 当時の苦労話を共有することで、少しでもこれから大規模サービスにReactや他の JavaScript フレームワーク を導入しようとしている方々のお役に立てればと思います。 こちらが発表資料です。 商品検索を改善した話 木村の方は、 " BUYMA の商品検索システムの改善の取り組み" というテーマでした。バイマの商品検索システムをSolrCloudへリプレースし、耐障害性を向上させた取り組みについてや、レガシーな検索ロジックを、BigQueryなどを駆使しながら数値で検索精度を計測しつつ、新しいロジックへと改善していく取り組みについてお話しました。 発表資料はこちらです。 BUYMAの商品検索システムの改善の取り組み from 慎太郎 木村 当日の様子 木村 山本 質疑応答時 まとめ 一緒に登壇させて頂いたJapanTaxiさん、 一休さん のお話も非常に共感できるところが多く、また今回は最近開催されたヒカ☆ラボの中でも来場者数は多かったようで、"みんなレガシー改善に苦労しているんだなぁ"としみじみ感じました。 新しい技術がどんどん生まれる中、如何にして技術的負債を返済してくかはこれからますます重要になってくると思います。 みなさんもガンガン知見を共有して共にレガシーと戦っていきましょう。
エニグモ でWEBエンジニアをやっております、大宮です。 今回は、先日英語版 BUYMA で行った、AMP対応についてまとめた記事をお届けしたいと思います。 そもそもAMPとは? Acceralated Mobile Pages の略です。 その名称が示す通り、モバイル端末で高速なWebページを表示させるためのプロジェクト、またはそのための フレームワーク (AMP HTML)の事です。 フレームワーク は Google と Twitter により共同開発されています。2016年の2月にローンチされて以降、AMP対応を行っている企業は増え続けています。 Google からは今のところは明言されていませんが、メインの開発に Google が入っているということで、いずれ SEO の上位表示に影響してくるのではないかという予測も立てられているようです。 現に、一部の記事ページでは スマホ で検索するとカ ルーセル で検索結果の上位に表示されます。 近年は 新興国 でも スマホ 文化が普及する一方、回線速度の遅さが問題視されることもあり、AMPはこうした時代の流れにも対応したものと思われます。 国内を見ても 格安SIM や通信容量制限による低速化などにより、WEBページの体感速度の向上はますます重要な要素になってきています。 ページが速く表示されるだけユーザーの満足度は向上するというのは、想像に難しくないでしょう。 実際に見てみましょう 実際に対応された英語版 BUYMA のサイトを見てみましょう。 AMP未対応のページ(ベンチマーク計測:11.5秒) AMP対応のページ(ベンチマーク計測:3.5秒) AMP対応のページをキャッシュしたAMP Projectページ(ベンチマーク計測:0.76秒) ベンチマーク は低速な回線で撮ったものです。 体感でも表示速度が一番下のキャッシュ速度が速い事がわかるかと思います。 なお、上記AMPのページはPC版で見ると崩れているように見えますが、 CSS をモバイルに最適化した(PC対応の CSS を排除した)結果です。後述する実装でAMPを別URLとした場合、AMPのページがPCからみられることはありませんのでOKとしています。 なぜ速いのか? AMP対応のHTMLは読み込みが非常に速いです。その理由を見てみましょう。 - AMPでは、非同期のAMP専用JSしか使用出来ない。 - CSS のサイズ制限+外部ファイルに置く事を禁止している - 画像のLazy Load+幅高さ指定の強制でブラウザの負荷を軽減 特に一番大きいのは最後のLazy Loadでしょう。 通常 Webブラウザ はページの表示時でページ内のすべての画像を読み込もうとしますが、AMPでは表示領域のみ非同期で画像をロードしてきています。 それを可能にしているのが <amp-img />タグ です。ampページでは通常の<img />タグは使用できず、すべてこちらに置き換える必要があります。 また、 CSS のサイズ制限があるので、リッチなコンテンツの量は増やしづらく、AMPページとしては余分なモジュールを削りおとすことになり、全体として読み込むファイルサイズが軽減されやすいということです。 さらに、これらのHTMLは、 AMP-Projectの CDN サーバーにキャッシュされ、検索結果での表示時にはキャッシュされたページを表示するようになります。 このキャッシュ時に、画像もモバイル用に圧縮をし、ファイルサイズを軽減しています。 さらに検索結果の表示時には、ユーザーが見ている検索結果の部分で、裏側でPreconnect / Prerenderingが走っています。 ユーザーが検索結果からページにアクセスしようとしてタップした時には、すでに裏側でページが読み込まれているため、ユーザー体験としては爆速に感じるということです。 AMPの三大制約 AMPを実装するにあたって、設計の段階で留意すべき制約は3つあります。 - JSが使えない* - CSS は50KBまで - Cookie が使えない ということ。言い換えれば リッチなページは作れません。 ログインユーザーに応じて出しわけ...などの機能は使えません。 ということです。 JSが使えないというのは、自前のJSは一切使用不可ということで、JSでよく使用される機能について、ある程度はAMP-Projectの方で用意されています。 例えば、サイドバー、カ ルーセル 、フォームのバリデーション等。詳細は 公式のこちらのページ に記載されています。 また、JSに関しては、2017年6月現在、 amp-bind という機能が開発中です。 リリースされればJSで自前の スクリプト を書いた時ある程度は同じような挙動を実現できるようになります。 具体的には、イベントトリガーでclassやInnerText, attributesの値を動的に変更するということが可能になります。 が、それでも既存のJSの流用はできないため、amp-bind用に書き直す必要があります。 URL方式の設計 まずは上述のように、できることとできないことをただしく把握する必要があります。 基本的に上の項目を把握されていれば問題はありません。 次に、AMP用に新規URLを作るのか、既存ページをAMP化させるのかを決めます。 モジュールを分けるかわけないかという選択ですね。 可能であれば、メンテナンス性を考えて同一モジュール...つまり今あるページをAMP化し、新規では作らないというアプローチが望ましいです。ただ前述の通り、AMPには制限がありますので、オリジナルページを残したい場合もあるかと思います。 その辺りはページのボリュームと、JS書き直しの 工数 (既存機能への影響)を加味して判断する必要があります。 ちなみに新規でページを作る場合には、最初からAMPに対応したものと作成してしまえば、既存ページへの差し替えで悩むことはなくなります。 画面の設計 基本的にはオリジナルページから何を残して何を削るのかを判断する必要があります。 オリジナルページをそのままAMP化しても、 CSS の容量超え+JSの リッチコンテンツ で実現可能なものと不可能なものが出ると思います。 なので AMPページの要件として絶対に必須なものを厳選して残し、 CSS 容量や 工数 に余裕があれば別の機能の対応も行う というアプローチが良いでしょう。 実際に開発してみてできること、できないことが分かるという事もあります。 なお、前述の弊社サイト(商品詳細ページ)では、商品情報+カート購入機能だけを残して、グローバルナビや検索バーといった要素は排除しています。これらの要素はAMPでも代替は可能ではありましたが、 CSS の容量制限の関係で断念した要素でした。 実際にAMPページにしてみる AMP専用の雛形がありますので、ご自身のサイトにあててみましょう。 bodyタグやheadタグや CSS は適宜調整していただければ良いかと思います。 これで、サイトはAMPとして扱われます。 なおAMP用に新規でURLを作る場合には、オリジナルページのHTMLのheadタグ内に下記を記述しましょう。 <link href="AMPページのURL(フルパス)" rel="amphtml"> これで Google にもAMPページが認識されます。 ただし、エラーが出る(多分) Chrome の開発者モードを開きURLの末尾に#development=1とつけましょう。雛形をそのまま使っていない限り、このように大量のエラーとなるはずです。 弊社サイトの対応途中で怒られていた内容としては、主に下記のような内容です。 img -> amp-imgに変換+幅・高さ指定してください scriptタグの使用してる所を全部削除してください a href=" javascript :void(0);"など css をインラインで読み込みしてください css のファイルサイズを削減してください このエラーを解消しないと、 Google からAMPページとして認識されませんので、すべて対応しなければいけません。 根気よく対応していけば、いずれは下記のように「AMP validation successful.」となりますので、頑張って対応しましょう。 WEBアプリケーションの場合はVIEWにIF文があることも多々あるので、当然ながらその条件分岐は全パターンみておく必要があります。 晴れてエラーがでなくなれば、本番環境にデプロイして、完了となります。 この時本番環境でしか動いてない監視用のJSなどがあると、またエラーになってしまいますので、注意深く観察しましょう。 終わりに AMPはそれなりに 工数 もかかる上に、完成する画面としては真新しいものではありません。 むしろ既存の画面より機能が削ぎ落とされたものなので、パッとみた完成品は物足りなさを感じるかもしれません。 なかなか成果のわかりにくい改修ではありますが、実際にAMP対応によって数字の伸びた事例も挙がってきています。 何より低速回線のユーザーにも、快適な挙動のページをお届けするという意味で重要な要因となりますので、ぜひとも導入を検討していただければと思います。 本記事がその際の一助となれば幸いです。
みなさんはじめまして! BUYMA で iOS アプリのエンジニアを担当している、松本と申します! 先日6月2日に行われた Repro User Meetup #1 に登壇してまいりましたので、その様子をお伝えします!! Reproとは? Reproとはアプリに特化した、アナリティクスツールです。従来のツールと大きく違う点は、ユーザーの行動を 動画で確認 をできる点と リテンション分析・ファネル分析 といった機能によってユーザーの重要な行動を細かくト ラッキング できる点です。詳しくは、 ReproさんのHP をご覧ください 当日の様子 当日はReproを活用されている10社様が登壇され、様々なReproの活用法や成功事例が発表されていました。 各社様の発表内容はコチラから BUYMA でのRepro活用 新規ユーザーにまず継続的にアプリを使用していただくことは、どのアプリにおいても非常に重要な事と言えると思います。 Reproのリテンション分析では、特定のアクションを行ったユーザーのリテンションを測定するといったことも可能であり、今回のRepro User Meetupでは新規ユーザーに対して最も効果的なイベントをどう判断するかという事について発表を行わさせていただきました。 詳しい内容はこちらのスライドをご覧ください! BUYMAにおけるRepro運用 from Takashi Matsumoto Repro株式会社取締役 CMO の中濱 康広様(中央)と弊社松本(左)と松永(右)
お久しぶりです。アプリケーションエンジニアの木村です。 BUYMA では、この記事を書いている時点で世界中から出品された約155万件の商品が検索可能となっていて、商品検索機能は世界中から自分の欲しい物を探すことを実現する、まさに「世界を買える」を実現するための重要な機能の1つとなっています。今日はそんな BUYMA の検索機能の裏側を支える基盤部分についてご紹介いたします。 BUYMA では検索機能実現のためにはSolrを導入していて、さらにSolrCloudを構成しています。 SolrCloudとは SolrCloud は、高信頼性、耐障害性、拡張性を運用コストを抑えつつ実現するSolrの クラスタリング の仕組みです。紙面の都合上あまりSolrCloudについて詳しく説明できませんが、下記リンクが参考になるのではないでしょうか。 https://cwiki.apache.org/confluence/display/solr/SolrCloud http://www.slideshare.net/kenhirose547/10solr-solr-cloud あまりWeb上に詳説された記事がないので、下の書籍も参考にしつつ構築にとりかかりました。 -『Solr in Action』Trey Grainger-Timothy Potter (2014) Manning Publishing. -『改訂新板 Apache Solr 入門』大谷純-他(2013) 技術評論社 . -『Scaling Apache Solr』Hrishikesh Vijay Karambelkar (2014) Packt Publishing. -『ZooKeeperによる分散システム管理』Flavio Junqueira-Benjamin Reed [著]、中田 秀基 [訳] (2014) オライリー -ジャパン さらに、上の2つ目の本も監修されている ロンウィットさんのトレーニングも受講 しました。最後の本はSolrCloudに組み込まれるZookeeperの本です。 BUYMA でのSolrCloudの構成 BUYMA のでのSolrCloudの構成を表したものが下の図になります。 更新のしくみですが、Solrを更新するバッチが常に動いていて、DBから更新がかかった商品情報を取得し、leaderのSolrノードへ更新リク エス トを送ります。するとSolrCloudの仕組みとして、leaderノードが他のreplicaノードへ更新を伝えて全ノードが更新されます。 検索は Rails のWebサーバーから直接SolrCloud内のSolrノードへリク エス トします。特に ロードバランサー 用のサーバー等は挟まず、SolrノードのIPをランダムに選び、そのIPへリク エス トを飛ばすように、 Rails アプリ側でロードバランシングしています。 耐障害性への取り組み Rails のWebサーバーから直接SolrノードのIPへ検索リク エス トが飛びますが、各Solrノードの IPアドレス はそれらを監視しているZookeeperから取得しています。したがって、いずれかのSolrノードで障害が起こった場合でもそれをZookeeperが感知し、リク エス ト可能なIPの一覧からダウンノードのIPを外してくれ、ダウン中や リカバリ 中のノードへは常にリク エス トが振られない仕組みになっています。 更新時も更新バッチがZookeeperからleaderであるSolrノードのIPを取得し、それに対して更新リク エス トを飛ばしています。もしleaderノードで障害が発生し、フェールオーバーして別のノードがleaderとなった場合でも、Zookeeperがそれを感知して更新バッチ側へleaderの変更が伝わる仕組みです。 実は、Zookeeperと連携してそこらへんの面倒な処理をやってくれる SolrJ というSolrCloudにも対応した Java 用のSolrクライアントがあるので、 JVM のWebアプリではそれを使ったり、そうでない場合はSolrJを使った API サーバーを検索クライアントとSolrとの間に挟む構成が通常なんだそうです。ただ、そのためのサーバーの運用保守もコストなので、 Ruby からZookeeperと連携するSolrCloud用の Ruby クライアントを自分たちで作りました。gemとして公開していますので、ご自由にお使いいただいてフィードバック等をお待ちしております。 https://github.com/enigmo/rsolr-cloud BUYMA の検索基盤クロニクル BUYMA がシステム面で今の形に近いものに生まれ変わったのは2008年ごろ(らしいの)ですが、その当時公開されていたSolr 1.3系の時代から検索機能にはSolrが導入されていました。その後2011年に3.x系へとバージョンアップがなされ、2015年6月まで稼働していましたが、商品数の増加とマスキャンペーンにより大量のアクセスが予想されたため、マシンリプレースと合わせて2015年リリースされた5.x系へとバージョンアップしました。さらに、運用し易さを求めて、構成をSolrCloudとしました。 SolrCloud導入以前は、マスタースレーブ構成でもなく、Solrノードを並列にならべてそれぞれの IPアドレス を更新-検索クライアントとなる全サーバーに固定値で持たせていました。更新バッチは全Solrノードへ同じ更新リク エス トを何度も送る必要があり、1ノードでも更新に失敗すれば検索結果がノードによってズレてしまいました。Solrノードでの障害発生時やメンテナンス時にもそれぞれのサーバーのSolrノードのIPを書き換えてやる必要がありました。商品数やアクセス数の増大に伴って、この先ノード数を増やす場合にこの構成では限界を感じていました。また、仮にマスタースレーブ構成にしたとしても、マスターが単一故障点になってしまったり、障害対応の時の手間は変わらないというのが難点で、マスタースレーブ構成への変更にも二の足を踏んでいました。 SolrCloudはそういった煩雑さから開放してくれ、まさに自分たちにとっては渡りに船でした。結果、 スキーマ 変更や設定ファイルのチューニング、ノードの障害発生時の対応やメンテナンスが圧倒的にやりやすくなりました。マシンリプレースの効果もありますが、パフォーマンスも、導入前より向上しました。 今後 Solrのバージョンが3.xから5.xとなり、機能面でも新たに使えるものが増えたので、 BUYMA の検索の機能面としても向上していければと思います。
はじめましてデザイナーの篠原です。 ECサイト のデザイナーならではのトピックをお伝え出来ればと思います。 はじめに私の担当は主に運営しているサービス「 BUYMA 」に関してのデザインになります。 「 BUYMA 」でのデザイン業務は大きくわけて、 1.トレンドと連動したキャンペーンページやバナーの作成 2.機能改善・新規開発に伴うページやUIの作成 になります。 細かく分類すると他にもありますが、大きく分けるとこんな感じだと思います。 今回は「トレンドと連動したキャンペーンページの作成」時に気をつけている事を、順序立てて書いていければと思います。 ざっとページ作成の流れはこんな感じ STEP1「構成」 STEP2「 ヒアリ ング」 STEP3「デザインイメージを探す、集める」 STEP4「デザインを考える (頭の中で)」 STEP5「プロトタイピング→デザイン(グラフィックソフトを使ったデザイン)」 STEP6「コーディング」 STEP7「ブラッシュアップ(もうひと頑張り)」 STEP8「公開」 STEPを通して、それぞれどんな事を考えているのかを書いてみようと思います。 STEP1「構成」 BUYMA では日頃からトレンドをウォッチしているMD担当のスタッフがいるので、 企画概要やデザインへ盛り込む要素などは、そのスタッフがあらかじめ決めることが多いです。 企画の規模によっては最初からデザイナーも入り共に構成を考えたりもします。 キャンペーンページでは特に売れ筋や定番人気の商品を BUYMA としてどう見せていくかを意識して考えていきます。 STEP2「 ヒアリ ング」 前STEPとほぼ同時の事は多いのですが、あらかじめ構成をもらっている場合にはこの段階で、 「テーマ」「ターゲット」「トレンド」「企画を考えた時に感じた事」「データからのユーザー分析」「スケジュール」 あたりをしっかり ヒアリ ングします。 内容を整理しデザイン面で懸念があればしっかり認識合わせをしておきます。 STEP3「デザインイメージを探す、集める」 ヒアリ ングで得たものから、同じテーマのデザインをネットで集めたり、 過去の経験から似たようなイメージを引っ張りだしたりします。 色が似ているとかフォントが似ているとか、なんでもヒットしたものを探して、 イメージを広げていきます。 印刷して実際にデスク上にバラバラと広げたり、 ホワイトボードとかにぺたぺた貼っていくとかも良いと思います。 企画を聞いてから色々と探してもいいのですが、 やはり普段から準備しておく事でスピードアップできます。 私の場合はとにかく「パッと見」でたくさんのものを見ておくことを大切にしています。 断片で覚えている方がよりクリエイティブな感覚をもたらすからと考えています。 ちなみにこの「パッと見」の際は、こちらのサイトとかよく見ます。 designspiration.net とか pinterest ですね。 BUYMA というサービスは海外との繋がりを楽しむ、または感じてもらうサービスなので、 海外のデザインからインスピレーションを得ることも多いです。 特にファッション性の高い見せ方、トレンドなどは常にチェックしています。 ついでに、ファションだけでなくカルチャー、経済、ゴシップネタまで、ユーザーの取り巻く環境を幅広く把握しておく様に、雑多に情報収集するのも良いデザインに繋がったりすると思います。 STEP4「デザインを考える」 前STEPで集めたものに対して、 「自分だったらこうする」という軸で考えていきます。 自分の審美眼をあてにしながら、色々と感じるままに考えていきます。 自分の関わるサービスに しっかりとした ブランディング がある場合は、「サービスに似合うのは何か」と考えていきます 。 BUYMA だとこちらの方が多いです。 この場合はサービスの審美眼をあてにしながら、という具合でしょうか。 STEP5「プロトタイピング→デザイン(グラフィックソフトを使ったデザイン)」 イメージがある程度まとまったら手を動かしていきます。 徹頭徹尾まとめてからだとうまくいかない事が多いので、ある程度がいいと思います。 アイディアの鮮度を大切に。頭がフレッシュな朝に回して手を動かすのでも良いと思います。 ちなみに手を動かすという事は文字通り手書きじゃなくても良いと思いますが、 イメージを素早く出すのは手書きが良い場合が多いなと感じます。 大事なのはイメージをどんどん頭から出す事 だと思っています、 頭からイメージを出していく事で新たなアイディアが入るスペースが出来るので、 短時間で様々なパターンを出す事が出来ると思います。 そもそもイメージが浮かばない場合はSTEP3に戻ってやり直します。 その後、 デザインが軽くまとまったらプレビューの場を設けます。 注意したいのはこのプレビュー時にデザインが出来上がりすぎている事 です。 意見の入る余地がないものを仕上げてからだと、 デザインへのこだわりが強くなっていて引けなくなっちゃったりしますし、 その思いが伝わって良い意見ももらえなかったりします。。。 なので、批判を恐れずにプレビューする事を心がけると良いと感じます。 方向性を定めてデザイン的に問題ないものが出来たら、 そのデザインに合わせたバナー作成もこのタイミングで行っていきます。 STEP6「コーディング」 ここは分業の場合も多いと思いますが、 僕らはデザイナーがコーディングまで担当する様にしています。 Webデザイナー は、各デ バイス 、ブラウザの特徴を把握し、 特性や利点を活かし、欠点を補うデザインが求められる職業だと考えています。 それはデ バイス 問わず成立するデザインを考えるということでもあると思います。 なので良いデザインをするためにコーディングの知識は必要と考え、極力担当をする様にしています。 また BUYMA ではエンジニアと一緒になってページを作る機会も非常に多いので、 HTMLだけでなく PHP や Ruby についてもデザイナー側に知識がないと厳しい 、 という経緯もあり普段からソースに触れる様にしているのもあります。 STEP7「ブラッシュアップ(もうひと頑張り)」 実際にはこのタイミングなのか、もしくは途中のタイミングでもあり得ますが、ページ公開に向けてブラッシュアップをしていきます。 ここで良く考えるのは(というより言い聞かす様にしているのは)、 ここまで試行錯誤しながら作ったものであろうが、今の完成度は80%だと思う事 です。 UI作成の場合はこの時点では改悪しないものが出来た、と思う様にしています。 誤解を恐れずに書くと、 ブランディング を考慮し、トンマナを守った上で出来たものはまだ完成度が80%程度なのだと思います。 ここからどこまでやるかは周りの反応だったり、それこそ個人の感覚が大きいと思いますが、 人を感動させるもの、気持ちを動かすもの、UIにおいての改善と呼べるレベルのものは、 ここからのブラッシュアップによって達成出来るものと考えています。 STEP8 「公開」 BUYMA では各担当者から公開依頼を一度システム管理者へ回します、 そこでサイトにクリティカルな問題がないかを確認した上で公開されます。 公開されたらもう一度自分が考えたデザインになっているかサイト上で確認を行い完了となります。 ページ公開後は、 良い反応があるかどうかをGAなどでチェックしたり、 実際にインタビューしたりなど、効果を追っていく流れになりますが、 こちらは担当Dirから共有してもらう事が多いので、デザイン作成としてはここで一旦完了となります。 という事で、だいぶ文字ばかりになってしまいましたが、 以上がデザインが出来るまでの基本的な流れです。 この様な基本となる流れを「素早く」「精度高く」こなす事で余裕が生まれ、 よりクリエイティブなキャンペーンページのデザインが出来る様になると思います。 最後になりますが、たまに休憩を取る事も大切なので、時折コーヒータイムでも挟みましょうね。 ではでは、良いデザイナーライフを。
はじめまして。エンジニアの小金澤です。 つい最近、類似画像検索という言葉がふと耳に入ってきたので、調査してみました。 意外と参考となる記事が少なかったので(というか...小難しい記事ばかりでした)、纏めるのに少々苦労しましたが、最終的には技術的検証まで行いたいと思います。 概要 画像検索には、TBIRとCBIRがあるらしい。 そしてTBIRとCBIRの両方を用いて画像検索を行うものがあるらしい... また、この2つ、TBIRとCBIR両方を用いて検索する方法が最も目的の画像を抽出することができる...らしい...... ということで本当に類似検索できるのか、まずは簡単そうな??CBIRについて調査・検証してみたいと思います。 また、軽くTBIRにも触れていきます。 1. 類似画像検索ついて TBIR (Text-Based Image Retrieval) 主にテキスト情報と画像を紐付けて画像を検索する手法のこと。つまり画像に対してテキスト情報を付け加えて情報を保持する必要がある。 CBIR(Content Based Image Retrieval) 画像の形状特徴や色特徴を基に類似する画像を分類し検索を行う。  2. 検索 アルゴリズム Earth Mover's Distance (EMD) 2つ ユークリッド 距離( ヒストグラム )を距離尺度を計算する。 EMDとは、A画像とB画像が持つ2次元 ユークリッド 距離(画像なので2次元としました。)比較しその距離間を計算します。 もっと簡単にちょー分かりやすくいうと、画像の特徴をハッシュ化しお互いの ハッシュ値 を比較する。※大分語弊ありありですが... また、特徴としては、画像を決められたY/X座標毎に分割し、分割した画像の ヒストグラム を計算しハッシュ化します。 そして分割したハッシュの総和を比較するというわけです。 ※ 画像に紐付いたテキスト情報も含めて、計算することもできるらしい。TBIR/CBIR2つの情報を元に計算することも可能。 Histogram Intersection 色の類似度を計算する アルゴリズム 。 こちらは、EMDに比べもう少しわかりやすいものとなってます。 色の比較です。単純ですね。赤(R)、緑(G)、青(B)の各成分が画像中に何 ピクセル あるかを計算し、比較するというものです。 Average Hash(aHash) Perceptual Hashを使い計算する アルゴリズム Perceptual Hashの生成方法は以下のとおりです。 画像のリサイズする(8x8=64ビット/16×16=256ビット) 色を削減する(グレースケールに変換) ピクセル 毎の色の平均値を算出 ハッシュを構築(各 ピクセル 毎に平均値以下か以上かを設定しハッシュ化する) 以下、この アルゴリズム の作成者(Dr. Neal Krawetz)のブログです。 http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html 本格的なPerceptive Hash(pHash)を使って計算する場合は、離散コサイン変換(DCT)をする必要があり、また、コストがとても高い(重い)ため、現在は、Average Hashが話題になっているようです。  3. 次回予告 次回は、このAverage Hashを使い実際に以下のGemと画像を使い Ruby で検証していきたいと思います Gem github.com 画像      最終目標 aHashより算出した ハッシュ値 をDBへ保存 フロントから入力された画像のaHashより ハッシュ値 を算出 DBにある画像 ハッシュ値 と入力画像の ハッシュ値 を比較し類似画像を抽出!! 乞うご期待!!!
はじめまして、エンジニアの高松です。 今回は先日リリースした、「色・サイズ改修」でのフロントエンド開発についてお話したいと思います。 概要 「色・サイズ改修」は、主に以下を目的としたプロジェクトです。 購入可能な色やサイズが、ひと目で分かるようにする 次期リリースで、色とサイズを選択して購入できるようにすることで、誤注文を減らす 商品の色・サイズのデータを蓄積し、運営に活かす リリース時のお知らせが こちら です。 BUYMA はC2Cのショッピングサイトなので、改修は大きく出品側と購入側に分かれます。 開発は私と部長の2人体制、さらにデザイナーのチームで制作しました。 私: 出品側開発 部長: 購入側開発 デザイナー: 全般的なデザイン 開発の始まり 部長が先行して開発した部分を引き継ぐことから、開発は始まりました。 引き継いだのは、500行ほどの JavaScript でした。 検討中の画面の仕様と併せて見ると、動的な制御の多いポップアップ画面のコードの一部です。 $と_の量が多い事と、「ここは Ajax で取ってくるか?」というコメントが気になりましたが、続きを書き始めました。 行き詰まり 少しずつ画面が動き始め、行数にして1300行を超えた頃でしょうか、開発が日に日に辛くなってきました。 ちょっとした修正にも時間がかかり、どこかを直せば、どこかが壊れます。 私(と部長)の書いたコードが読みづらく、 子泣きじじい のように重くのしかかってくるのです。 「このまま作りきれるのか...」不安が募ってきたところで、一度書いたコードを捨て、Backbone.jsで書きなおすことを決めました。 一部ですが、既に BUYMA の中にはBackbone.jsで書かれた部分があったのが理由です。 Backbone.jsの導入 結果、Backbone.jsを使って書き直したコードは、以前よりずっと可読性が高いものに生まれ変わりました。 開発で使ったのは、View・Collection・Modelといった、Backbone.jsのベーシックな部分で、 ルーター などの機能は使っていません。 テンプレートエンジンには、 Handlebars.js を使いました。 Backbone.jsを利用して得られた、主なメリットは以下の部分です。 Viewとロジックが分離でき、イベントも所定の場所に書くので、処理が追いやすくなった。 CollectionがUnderscore.jsをラップしていて、使いやすい。eachやmapなどのコレクション系のメソッドや、findなどのファインダーメソッドが Ruby ・ Rails ライクで馴染みやすかった。 フレームワーク としてコンパクトで、学習コストは低かった。 結果として、ロジックの実装に集中できるようになったのが、一番のメリット。 逆にハマった部分です。 Backbone.jsのViewをappendすると、空のタグが入る。 jQuery のappendと、Backbone.jsのView::setElementを使い分ける必要がありました。 Viewが GC に入らず メモリリーク する。いわゆるゾンビビュー問題です。 ここは、ノウハウを貯めていかないといけないですね。 API だいぶ調子が出てきたタイミングで、部長と立ち話がてら、実装の話をしました。 部長: 「 API って Rails だよね?」 私: 「いえ、出品側は既存もあるので、全部 PHP でやってます。」 部長: 「いや、新規 API だから Rails で書けるじゃん。」 私: 「へっ???」 そうです、「ここは Ajax で取ってくるか?」のコメントを動かすために、 API を3本作りました。 以前の 部長のポスト に記載がありますが、 BUYMA のコードは少しずつ PHP から Rails に移行しています。 API は独立して作りやすいので、 Rails で書くのが社内では定石になっているのです。 新規でレガシーコードを増やしてしまいました、すみません... まとめ Backbone.jsの導入で、フロントエンド開発は随分捗りましたが、まだ課題は残ります。 共 通化 できる部分はexport、requireして読み込みたい サーバ側のModelとBackbone.jsのModelがほぼ一緒になるので、重複をなくしたい View -> Model、Model -> Viewの双方向のやりとりを多く書かなければいけない。 フロントエンドもテストが必要じゃないか? そうです、どれも最近のライブラリや フレームワーク が解決しようとしている問題ばかりです。 モダンなツールを導入して、解決していきたいですね。
Fluentdによるログ可視化が話題になってからだいぶ経ちますが、 エニグモ でも(念願の)ログの可視化を本番投入しましたのでその内容を紹介したいと思います。(完全系ではないですが、実用段階です!) 主な使用技術 Fluentd Elasticsearch Kibana AWS 構成図 構成の説明 各WEBサーバーが出力したログをFluentdが拾ってログ集約サーバーに転送、ログ集約サーバーが AWS にたてたElasticsearchサーバーにデータをPOSTするオーソドックスな構成となっています。 Kibana またログの可視化ツールではKibanaを使ってます。 Kibana4を使ってますが、Kibana3より格段に分かりやすくて気に入っています。 細かい話 使用しているFluentdのPlugin fluent-plugin-elasticsearch データをElasticsearchに送るため fluent-plugin-record-reformer ログを加工をするため 使用しているElasticsaerchのPlugin elasticsearch-cloud-aws AWS のEC2にElasticsearchをインストールして クラスタ 構成を組む場合、EC2 インスタンス 同士で通信しないといけないが、それを設定すれば簡単にやってくれる プラグイン 。あとS3へのSnapshotをとる機能のために使っている。 elasticsearch-head Elasticsearchを管理するための プラグイン 。一番良く使う。 elasticsearch-kopf 同じくElasticsearchを管理するための プラグイン 。TOPページで、"heap","disk","cpu"の使用率が横棒グラフで見れるのでそれは重宝しているが、他の用途には使っていない。。 Elasticsaerchの スキーマ 設定 Mapping Templateを使ってフィールドのデータ型や要素解析などを指定しています。 Elasticsaerchの定期的なSnapshotの作成/削除、定期的なIndexの削除 curator という大変便利なツールがあるので、 それを使って、Elasticsaerchの定期的なSnapshotの作成/削除、定期的なIndexの削除をおこなってます。 ログ可視化をして捗ったこと 平均レスポンスタイムの分析が容易。 レスポンスタイムが悪いURLが分かる。 端末別(PC or スマホ or タブレット or ガラケー )のアクセス数が把握が容易。 時間ごとの HTTPステータスコード の割合。 特定の時間だけ500が多いとかが分かる。 ある会員がどの画面遷移をしたかがすぐに分かる。 障害調査が素早くできる。特にこれまでは数十台のWebサーバーのログを grep していたので結果がでるまで何十秒もかかっていました。 等など 最後に 昔はログファイルを grep & AWK で頑張って解析していたのが、今は lucene queryでサクッと特定が出来ますし、遅い画面の集計や ステータスコード の集計も簡単にできるようになりました。 今後はもっとログ可視化システムを強化してもっと便利にしていきたいと思います。
こんにちは。 エンジニアの木村です。先日行われました ECNight#1 に参加しました。 発表しました。 ああいった場で発表するのは初めてで、発表順はくじで決まってまさかのトップバッターでしたが、時間も10分ということでちょうど良かったです。 発表資料ですが、当日のものをよりスライドだけで伝わるように改変してあります。 迷っている人を後押ししてCVRを上げる from 慎太郎 木村 かなりユルい感じの、しかも上手く行かなかった取り組みについての発表でしたが、発表中の皆さんのいいリアクションを見たり、 Twitter 上や後の懇親会で良いコメントをいただけたりすると、発表しといてよかったなと思いました。勉強会マジックかもしれませんが。 この発表で少しでも社内の雰囲気が伝わればと思いますが、もちろん、もっとしっかりした取り組みもやってますということを申し上げときます。 感想 ライトな感じのイベントだったとは思うのですが、その中でも物流や決済についてのコアな話、ファッションECの歴史についての発表もあり、自身の仕事に直に関わるテーマばかりで、割と得るものが密に詰まったイベントでした。 懇親会も同じような悩みを持つ同業者間でア イデア やノウハウを共有できた貴重な機会でした。 それから、過去の 大川さんの記事 の最後も同じようなことを言っていますが、勉強会ドリブンというか、そういった気持で日々の開発に取り組もうという気持ちになりました。
こんにちは、デザイナーの 細田 です。 ECサイト のデザインにおいても、バナー制作は欠かせません。 今回は、私がバナーを作る際にいつも意識している「足し算と引き算」について書きたいと思います。 まずはじめに:「ECバナー3箇条」 私がバイマのバナーを作る際に、特に大事にしている3点があります。 通称「ECバナー3箇条」です。(私しか呼んでいませんが) Ⅰ アイテムが魅力的に見える・買いたくなる Ⅱ パッと目をひく Ⅲ しかしずっと見ていても疲れない 商品を売るサイトなので、Ⅰは大前提です。 そして今サイトが一番PRしたいもの=バナーなので、 しっかりユーザーの目を惹きながらも、(Ⅱ)広告のようなギラギラと押し付けがましい印象にならないよう心がけています。(Ⅲ) バイマは「ファッションサイト」であるため、 “あくまでオシャレ、しかし目を引くバナーで商品をPRする” これを目指すゴールとしています。・・・なかなか難しいですが。 その1:バナーを「背景」「アイテム」「タイトル」の3要素に分ける 私はバナー制作をする際、「背景」「アイテム」「タイトル」の3つに分けて考えます。 通称「バナー3兄弟」です。(こちらも私しか呼んでいませんが) そうする事で、今回のテーマである「足し算と引き算」が行いやすくなります。 といわれても文章だけではイメージしずらいと思いますので、実践編を見てきましょう。 その2:実践してみよう 『背景が派手なバナー』 ジミーチュウの新作紹介バナーを、「華やかなイメージで」という依頼がきました。 指定されたアイテムを見ると、国旗など派手めなものもありつつ、黒や茶など若干ベーシックな印象を受けたので、背景は思い切ってピンクのラメを敷いてみました。 後はタイトルを入れれば完成ですね。 ・・・ 「華やかに、華やかに」と念じながら、タイトルも思いっきりゴージャスにしてみました。 結果、確かに華やかにはなったかもしれませんが、アイテムの印象が薄れ、見ていて疲れてしまいますね・・。 「まずはじめに」で挙げた3箇条で言えば、Ⅰ とⅢ が達成されていません。 これを3兄弟で表すとこうなります。 ここで登場するのが、「足し算と引き算」です。 ずっと見ていて疲れないバナーにするためには、「3兄弟で派手なのは1人だけ」 と私は考えています。 今回はタイトルを引き算してみます。 ・・・ タイトルを黒地にし、ぐっとクールにしてみました。 こうすることでアイテムが引立ち、背景の派手さもほど良く抑えられました。 3兄弟はこうなります。 ECサイト では、アイテムはディレクターから指定される事が多いと思いますので、残る背景及びタイトルの「派手〜地味」を足し算・引き算していくことになります。 場合にもよりますが、「派手」「中間」「地味」がそれぞれひとつずつ  というのが いいバランスなのではないかと感じています。 その3:実践してみよう 『アイテムが派手なバナー』 ミッソーニ のコラボバナーの依頼がきました。 ミッソーニ のアイテムはどれもカラフルで派手だったので、背景やタイトルは極力シンプルにしました。 3兄弟だとこちら。 これでもシンプルで見やすいのですが、パッ見の印象を強くするため、もう ワンメイク してみましょう。 タイトルの色はそのままで、フォントを変えてほんのすこしの華やかさを足しました。 3兄弟でいえば、タイトルを「地味」から「中間」にするくらいの足し算です。 こちらも、「派手」「中間」「地味」がそれぞれひとつずつ、となりました。 以上が、バナー3兄弟を使用した「足し算引き算」となります。 冒頭に述べた「ECバナー3箇条」 Ⅰ アイテムが魅力的に見える・買いたくなる Ⅱ パッと目をひく Ⅲ しかしずっと見ていても疲れない を達成するために、「なにを足そう、なにを引こう」と考えながらバナー制作をしています。 同じく ECサイト のバナーを制作している方の参考になれば、幸いです。
こんにちは。 エンジニアの木村です。 先週末、社内有志でBBQをやりました。BBQって人数は多ければ多いほど楽しいですよね。でもどうやってメンバーを集めるか、すごく悩みました。 (´-`).。oO(全社MLに流してもすぐ他のメールに埋もれて忘れられそう。。。) (´-`).。oO(とはいえ日頃話さない人に仕事以外で直接声かけるのもなぁ。。) しかし、外が気持ちいいこの季節、BBQやらずに冬なんか迎えられません。そこで考えたのは、みんなが1日何回も見てる開発環境に広告を出すことでした。 BUYMA の開発では、エンジニアじゃなくても、デザイナもディレクタも少々のHTMLの修正なら自分でやっていて、みんな開発環境を見ています。そこに広告を出せば目に留まらないわけがありません。 どんなふうに出るかというと、こんなかんじです。 クリックすると開きます。 こちらは開催1週間前。 結果、無事、程よく、男女比もいい感じに集まりました。この日は会場も天気も、集まったメンバーも最高でした。Couldn't be better. もちろん、やったよ〜の報告も忘れません。 それでは、次のBBQもお楽しみに!
メインサービスである BUYMA のシステム的な話がいままでなかったので書きます。 (2014/09 現在) PHP , Ruby , Java 主に使う言語は PHP , Ruby , Java です。 BUYMA のほとんどの部分は PHP / Zend Framework / Smarty で書かれています。なかなか年季の入っているもので、見通しが悪く保守性がアレなコードがあったり 、誰も PHP が好きではない などの理由で、絶賛 Ruby on Rails で書き換え中です。 "絶賛" と書いてしまいましたが、既存の PHP への機能追加・変更もしながらなので苦労もありますが、新しいものは Ruby で、古いものも手を入れるときにはできれば Ruby で書き換えたい、みたいな感じでやってます。そうはいっても既存機能に少し手を加えるくらいなら PHP でやってしまっています。 BUYMA のメイン機能とは別に、レコメンドシステムがあります。 こちらは Java / Struts2 /Spring/ Hibernate な構成でできています。レコメンドは独自ロジックで行っています。 また、社内で使う管理画面も同じ フレームワーク でできています。 この2つのシステムは同じ頃にできているので、その時のエンジニアの判断だったのだと思います。 MSSQL , MySQL , Percona 歴史的経緯により、メインのデータベースは SQL Server です。 その他、 MySQL もあったり最近ではPerconaも使っています。 最近 Percona を入れるときには、「 MySQL じゃだめなのか? PostgreSQL じゃだめなのか?」といった話し合いをしつつ、最終的には担当エンジニアが Percona がいいというならそれで。みたいな感じで決まりました。 Memcached , Redis 当たり前ですが Memcached もあります。当たり前ですね。 Redis は Rails から Resque でつかったり、キャッシュサーバとして使ったりもしています。 Apache Solr 検索エンジン として Apache Solrを利用しています。 いまは 3.2 をつかっていますが、そろそろ 4 にしたいですね。 Solr使ってるんですが、あまりうまく使えている感じがしないので、ノウハウを蓄積していきたいところです。 CentOS , Windows Server SQL Server があるので Windows Server もありますが、それ以外は CentOS です。5と6が混在しております。 Subversion , Git 古いものはまだまだ Subversion にはいっています。 Rails は Git/GitLab に入っています。 Subversion を使っているのがエンジニアだけじゃないこともあったり、履歴が膨大にあったりなどで、なかなかエイヤッと Git に移行できないでいますが、そのうちやりたいと思っています。 Virtual Box, Vagrant , Chef 開発環境は Vagrant を使って簡単に作れるようになりました。( 栗山さんの記事参照 ) それまでは Mac に PHP , Apache , ・・・・ って1日かかっていたものが30分寝ているだけで出来上がるようになりました。神様仏様栗山様です。 またこれに触発されてインフラエンジニアも Chef レシピを積極的に書くようになりました。Chef や Chef サーバは結構前からあったのにうまく活用できていないなぁと思っていたのでグッドバイブスですな。 その他 その他 Jenkins さんがいたり、情報共有には Slack や Qiita:Team を使ってたりします。 採用情報の中にも利用している技術 & ツールが載っています のでご確認ください。 まとめ このようにさまざまな歴史的経緯があり、日々、技術的負債を返したり、新たな負債を生み出したり楽しい日々を送っています。環境整備は一朝一夕には行かないので、少しずつですがモダンな環境を目指して改善して行きたいと思いつつ、人手不足で改善できていないところもたくさんあります。 サービス開発にも環境整備にも興味のある方がいましたら、お声がけいただければと思います。
エンジニアの栗山です。 最近になって、社内 CSS フレームワーク を作ったので、その共有をしたいと思います。   CSS フレームワーク ほしい… まず CSS フレームワーク と聞いて思い浮かべるのが、 Bootstrap ではないでしょうか。 これは非常に便利ですよね。デザインが苦手なエンジニアでも簡単に見栄えのいいサイトが作れます。 ぜひともこういった CSS フレームワーク を使いたいところですが、これをそのまま使うとデザインがBootstrapそのものになってしまうので、その会社そのサービスにカスタマイズした CSS フレームワーク が必要になります。 そこでベースとなる CSS フレームワーク を選定し、それをカスタマイズして社内 CSS フレームワーク を作ることにしました。(一から CSS フレームワーク を作るのは大変なので) ベースとなる CSS フレームワーク の選定 Semantic UI や Kube など、候補に上がった CSS フレームワーク としてはいくつかありますが、 人気とデザインの良さから Pure と Foundation が最終候補に残りました。 Foundationは機能が豊富でよさそうだったのですが、ゴリゴリのSassで書かれていたため、これをデザイナーさんがカスタマイズするのはちょっとハードルが高そう。 対してPureは CSS のみで作られていてカスタマイズが非常にしやすい。 ということでPureをベースの CSS フレームワーク に選びました。 ちなみに実際にPureをもとに CSS フレームワーク を作ったのはうちのデザイナーさんで、私は社内 CSS フレームワーク 周りの環境整備をやりました。 Sassが使いたい いまどき CSS プリプロセッサ 使ってないとかマジでなくね?ということでSassを使うことにしました。 Sassを選んだ理由はLESSやStylusよりもユーザ数が多く、またScss記法になって CSS に近い記法なりデザイナーさんも親しみやすい点からです。 Pureは CSS で書かれていますが、その css ファイルをSassファイルにしてSassで書けるようにしています。 (あと CSS フレームワーク でデザインが全て完結するわけではなく、そのページ特有のデザインのためにそのページ用の CSS を用意することがあります。 その CSS もSassで書くようにしました。) Sassといえば Compass ですが… Compass は有名ですが、ちょっと重厚です。 Compass でやれることのほとんどはGrunt プラグイン で実現できます。 また Compass はSassの コンパイル が非常に遅いです。 しかし Compass のMix-inは便利なので使いたい…。 そこで Bourbon を使うことにしました。 Bourbon は安心と信頼のthoughtbotが作っている"A simple and lightweight mixin library for Sass."です。 Mix-inも十分揃っていますし、ただのSassファイルの集合なので コンパイル も非常に速いです。 Grunt! Sassの コンパイル や CSS の結合圧縮、 CSS スプライトの作成等々、フロントエンドのもろもろのタスクを自動化してくれる便利な味方、Gruntも使うことにしました。 Gruntの プラグイン は色々入れていますが、主なやつは以下です。 Sassの コンパイル のためのgrunt-contrib-sass CSS Lintを実行するためのgrunt-contrib-csslint CSS を圧縮するgrunt-contrib-cssmin CSS を結合するgrunt-contrib-concat LiveReloadのためのgrunt-este- watch 、grunt-contrib-connect Gruntの実行に失敗したらポップアップで通知してくれるgrunt-notify grunt-este- watch はPCの負荷が少なくて非常に良いです。 あとgrunt-notifyはエラーにすぐに気付けて地味に便利ですね。 また巷で言われるように、タスクが多くなってくるとGruntfile.jsが結構きつくなってくるので、 gulp を入れようか考え中です。。 スタイルガイド、いいよね 格好いい社内 CSS フレームワーク を作っても、そのスタイルを適用したらどんなデザインになるのかが簡単に分からないと、みんな使ってくれません。 簡単に言えば http://getbootstrap.com/css/#forms みたいな画面が作りたい。 こういうのを巷ではスタイルガイドと呼ぶようです。 スタイルガイドは有名どころでは、 StyleDocco や、 KSS 、 Kalei があります。 エニグモ では、スタイルガイド自体のデザインも出来るKSSを使うことにしました。 KSSはちょっと導入が面倒なのですが、それを簡単にする kss-node というものを使っています。 CSS 命名 ルールを決めたい CSS のクラス名の付け方とか結構迷いますよね。 最近では OOCSS とか BEM とか SMACCS とかが出てきています。 エニグモ ではシンプルで分かりやすい(けどクラス名が長くなる) BEM を使うことにしました。 ただ厳密にBEMを適用するとクラス名が長くなってしんどくなるのでほどほどに取り入れています。 この辺の 命名 ルールはまだ模索中です。 まとめ 他の会社でも社内 CSS フレームワーク を作りたいという欲求はあると思います。 ただ一から作るのは大変なので、うちのようにベースとなる CSS フレームワーク を用意するとよいのではないでしょうか。 また今回いろいろ環境は整えて前と比べてモダンな感じになったのですが、実際に社内 CSS フレームワーク を使っていく運用はまだ始まったばかりです。 特に大変なのが、既存の CSS と今回作った社内 CSS フレームワーク の共存です。 既存の CSS が思わぬ影響を及ぼしたりして地道な調整が必要になるのですが、そこはデザイナーさんが頑張っております。 プロジェクトの寿命が長いとどうしても開発環境がレガシーになってきてしまいますが、そうなると生産性やメンテナンス性に影響が出てしまうので、随時新しい技術を取り入れてモダンな開発環境を保っていきたいと思います。
はじめまして。エンジニアの栗山です。 エニグモ でも、ついに、 Vagrant と Chef で 開発環境を構築出来るようにしました。 経緯 以前は、開発者が各々の Mac に手順書にそって Apache をインストールしたり PHP を コンパイル したり等々していました。 しかしこれだと以下のような問題が出てきます。 本番は CentOS で動いているので、開発環境と本番で差異が出てきてしまう。 何か新しい ミドルウェア を入れたり、 フレームワーク のバージョンを上げたり、言語のバージョンを上げたりということが気軽に出来ない 新しい ミドルウェア を入れると全員のエンジニアがそれを自分のPCに入れる作業をしないといけない Mac を新しくするたびに開発環境を構築するのが面倒 デザイナーさんは自力で開発環境を構築できない ということで、一念発起して、Chefレシピを書き、 Vagrant も入れ、簡単に開発環境を構築出来るようにしました。 構成や使っているツール 構成を説明するとまず Vagrant があって、 Vagrant プラグイン として vagrant -omnibusとsaharaを入れています。 vagrant-omnibus は、boxにChefが入っていなければ自動的にインストールしてくれる プラグイン です。 sahara は、boxのスナップショットをとったり、スナップショットの状態まで ロールバック したりすることができるようになる プラグイン です。 Chefを書いていると"前の状態に戻したい!"ということが頻繁に起きるので必須の プラグイン となっています。 Chefのcookbookを管理するツールとして、 Berkshelf を使っています。 Berkshelfを使うとBundlerのように簡単に サードパーティ のcookbookが管理できて非常に便利です。 ちなみにboxファイル自体は、時雨堂さんの packer-templates を使ってboxファイルを生成しています。 生成されたboxファイルに対し、Chefレシピを実行する流れです。 Mac とboxファイル内の ソースコード の同期の仕方ですが、 Mac 側から ソースコード を修正することが多いので、shared foldersではなく nfs を使っています。 nfs のほうが速くて快適です。 まとめ Vagrant + Chefで開発環境が構築できるようになったため、デザイナーさんでも簡単に環境が構築できるようになり、またChefによって常にみんな同じ環境が保てるようになりました。 そしてChefレシピによって何をインストールしてどんな設定をしているかが見えるようになりました。 これから 今後はChefレシピを修正してGitにpushしたら、自動的に、 「boxファイルの生成、Chefレシピの実行、serverspecでのテスト、テスト結果の通知」 がされるようにしたいと考えています。
はじめまして インフラ担当のたかやまです。 先日(と言ってもだいぶ前)、サーバマシンを購入し、サービスに組み込んだのですが、なかなか全開の性能を発揮させることができず、地味にハマったので その時の話を。 マシンスペックとサーバ構成 購入したマシンは以下。 FUJITSU / PRIMERGY RX200 S7 CPU : Xeon E5-2670 ( 2 .60GHz ) 2CPU 16core Rails サーバにするため、以下の構成にしました。 ホストOS : CentOS6. 4 + Xen4. 2 仮想OS : CentOS6. 3 + Unicorn4 + Rails3 + Ruby2 既存 Rails サーバは以下。 Dell / PowerEdge RX200 CPU : Intel Xeon X5650 ( 2 .67GHz ) 1CPU 6core ``` 構成は同じですが、ホストOSのOSとXenのバージョンが違います。 ホストOS : CentOS6. 3 + Xen4. 1 仮想OS : CentOS6. 3 + Unicorn4 + Rails3 + Ruby2 大きな違いはCPU。 世代が変わって、かなり性能が向上しているそうです。 参考: 次世代データセンターの新基準へ インテルが「Xeon E5-2600/1600」を発表 性能比較 : なぜか既存サーバより遅い新規サーバ 既存サーバと新規サーバで条件を揃えて新規サーバをサービスへ組み込み、 Rails の1アクセスに対する"平均処理時間"を比較しました。 ・新規サーバ : 0.32秒 ・既存サーバ : 0.26秒 あれ? 既存サーバより遅い。。。 Unicorn はシングルスレッドアプリケーションですが、CPUの性能UPで"平均処理時間"も少しは速くなる想定でした。 対策その1 : BIOS を性能優先に まず確認したのは BIOS 。 デフォルトでは"性能と消費電力のどちらも最適になる設定"になっていたので、性能が最優先になる設定に変更しました。 CPU Configuration ・Power Technology: Energy Efficient => Custom ・Energy Performance: Balanced Performance => Performance ・CPU C6 Report: Enabled => Disabled ・Package C-State limit: No Limit => C0 参考: PRIMERGY RX200 S7 環境設定シート 結果 多少改善されたのですが、既存サーバには及びませんでした。 対策その2 : Xen のVCPU設定をチューニング その次に Xen の仮想OSのVCPU設定をチューニングしました。 以下は Rails サーバをホストOS内に2つ起動し、各 Rails サーバの Unicorn のworkerを32個起動した場合に最もパフォーマンスが発揮されたパラメータです。 ・VCPU数= Unicorn のworker数(物理コア数16コア×2CPUと同一にしました) ・VCPUへのコアの割り振り=1CPUのコアを全てを割り振る ( Rails サーバ1は全てのVCPUでコア番号:0-15、 Rails サーバ2は16-31) # vi /etc/xen/<仮想OS名> vcpus = 32 cpus = [" 0-15 " , " 0-15 " , " 0-15 " ,・・・ ] 結果 それでも既存サーバには及ばず。。 対策その3 : ボトルネック 調査 ホストOSや仮想OSのCPUやメモリ等を確認しましたが、特にリソース不足は発生していません。 そこで、turbostatというツールでCPU動作クロックを確認しました。 以下のサイトを参考にさせていただきました。 CentOSにおけるintel CPU ターボブースト動作の確認 turbostatを実行すると、動作クロックと、CPUの省電力状態を表示し続けます。 (デフォルトだと5秒間隔でフラッシュします) C0とC1の欄がちゃんと表示されませんでしたが、 TSC の欄が標準クロックで、GHzの欄で実際に動作している動作クロックです。 既存サーバでは、ほとんど2.6GHz以上で動作していますが、 新規サーバでは、ほとんど2GHz以下で動作していました。 結果 やはり、CPUが全開の性能を発揮できていないことが ボトルネック だと判明しました。 新規サーバ # ./turbostat CPU %c0 GHz TSC %c1 %c3 %c6 %c7 %pc2 %pc3 %pc6 %pc7 **** **** 2 . 60 **** 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 **** **** 2 . 60 **** 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 1 **** **** 2 . 60 **** 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 2 **** 1 . 2 * 2 . 60 **** 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 3 **** 1 . 2 * 2 . 60 **** 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 4 **** 1 . 2 * 2 . 60 **** 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 5 **** 1 . 2 * 2 . 60 **** 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 6 **** 1 . 2 * 2 . 60 **** 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 7 **** 1 . 2 * 2 . 60 **** 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 8 **** **** 2 . 60 **** 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 9 **** 1 . 2 * 2 . 60 **** 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 10 **** 1 . 2 * 2 . 60 **** 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 11 **** 2 . 1 * 2 . 60 **** 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 0 . 00 既存サーバ # ./turbostat CPU %c0 GHz TSC %c1 %c3 %c6 %pc3 %pc6 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 0 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 1 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 2 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 3 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 4 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 5 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 6 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 7 **** 2 . 9 * 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 8 **** 2 . 9 * 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 9 **** 2 . 9 * 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 10 **** 2 . 9 * 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 11 **** 2 . 8 * 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00   最大の原因が判明! CPUが全開の性能を発揮できていないということで、CPUの動作クロックをコン トロール している機能を調査しました。 サポートに聞いたり、ググったり、 BIOS でもない、 Xen でもない、OS(cpuspeed)でもない、くぁwせdrftgyふじこlp ・・・そしてついにCPU動作でクロックをコン トロール している犯人を発見。 Xen のCPUクロックをコン トロール する機能(cpufreq)が省電力モードになっていました。。 ※既存サーバでは Xen のcpufreqが無効になっていて、 BIOS のみの設定変更で全開の性能が発揮できていたようです。 性能優先にするため、親ホストで以下のコマンドを実行。(/etc/rc.localにも追加) # xenpm set-scaling-governor performance # xenpm set-scaling-minfreq 2601000 参考 Xen power management Xenpm command 結果 設定変更の結果、だいぶ改善されました。 # ./turbostat CPU %c0 GHz TSC %c1 %c3 %c6 %pc3 %pc6 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 0 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 1 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 2 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 3 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 4 **** 2 . 9 * 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 5 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 6 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 7 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 8 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 9 **** **** 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 10 **** 2 . 9 * 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 11 **** 2 . 9 * 2 . 66 **** 0 . 00 0 . 00 0 . 00 0 . 00 もう1度、平均処理時間を比較。 ・新規サーバ 0.23秒 ・既存サーバ 0.26秒 おおー (ちょっと)速い! それに コアが多いので、同時に3倍近い処理を捌くことが可能です。 めでたし めでたし! 終わりに サーバ構成で書きましたが、 BUYMA は Rails で動いています。 実際には PHP の フレームワーク と Rails が混在しており、現在、 Rails に移行中です。 enigmoでは Rails で BUYMA を速くしたいエンジニアを募集しています。 → こちらまで