v-for 内でコンポーネント、クラス名、イベントハンドラまで動的に指定する

ROXX エンジニアの匠平(@show60)です。

同じようなタグが繰り返されているのを見ると、どうにかスッキリまとめられないもんかなと思いますよね。

今回はアイコンコンポーネントを例に、 v-for 内で動的な指定と、クラス名、イベントハンドラの設定までやってみようと思います。

v-for のループ内で任意のカスタムタグを指定する

ユースケースとしては、バラバラのコンポーネントを並べたいときなどでしょう。

1 つのアイコンのみを含んだコンポーネントがあるとして、並びに規則性のある場合には 1 つずつタグを置いてスタイルを設定するよりもコード量が減ってスッキリします。

下記のコードを例にすると、各アイコンコンポーネントが並ぶとき、コードの見通しはいいのですが同じクラス名が入ってくると冗長になりがちなのですっきりとさせたい気持ちです。

<icon-news-feed class="icon"/>
<icon-my-page class="icon"/>
<icon-settings class="icon"/>

配列の要素名と同じアイコンを指定する

下記のようなアイコンの名称が入った配列を用意します。

// 並べたいアイコン
icons: [
  'news-feed',
  'my-page',
  'settings'
]

テンプレートでこのように記述します。

// template
<ul>
  <li
    v-for="(icon, index) in icons"
    :key="index"
  >
    <component 
      :is="icon-${icon}"
    />
  </li>
</ul>

Vue の is 属性を使うことでコンポーネントを動的に生成できるため、単純な v-for だけで表現できます。

注意が必要なのは、当然ですがこれらのアイコンコンポーネントの import は個別に必要ということです。

ここでは 3 つのコンポーネントを例にしていますが、もっとアイコンが増えると利用価値も十分あるかと思います。

また、このアイコンを並べるコンポーネントを用意し、並べたいアイコン名を配列にして親コンポーネントから props で渡すだけで指定することができます。

API — Vue.js

component タグにクラス名を追加すると、呼ばれているアイコンコンポーネントにクラス名が追加されます。こちらも動的に指定できますので例を挙げてみます。

クラス名を動的に指定

ナビバーやフッターなどでは、上記のようにアイコンを並べることがあるかと思いますが、現在のページのアイコンのみ色を変えたいですね。その場合は、動的にクラス名を指定すればよさそうです。

// template
<ul>
  <li
    v-for="(icon, index) in icons"
    :key="index"
  >
    <component 
      :class="getClass(icon)"
      :is="icon-${icon}"
    />
  </li>
</ul>

...

<script>
// import で各アイコンコンポーネントを呼び出し、 export 内で components に記述する

export default {
  methods: {
    getClass(iconName) {
      const path = this.$route.path
      if (iconName === path) {
        return 'is_selected'
      }
    }
  }
}
</script>

// style で is_selected に任意のスタイルを当てる

v-for のループ内で任意のイベントハンドラを指定する

Vue.js 2.6 からディレクティブの引数を動的に指定できるようになりました。

テンプレート構文 — Vue.js

要するに v-bind:●●="something" , v-on:●●="something()" の●●を動的に指定できるのですが、ユースケースとしてはあまり多くないのかなという印象です。

上記で挙げている例でいうと、ある条件下ではアイコンの click 時にイベントが発火し、別の条件下では mouseover でイベント発火するようなケースですね。サイドメニューのサブメニューの開閉を切り分けたいときなんかには使えそうな印象です。

他には下記のように、アイコンが示すページ内にいるときにはイベントハンドラを与えないという処理には使えそうです。

// template
<ul>
  <li
    v-for="(icon, index) in icons"
    :key="index"
    @[getEvent(icon)]="handleEvent()"
  >
    <component 
      :is="icon-${icon}"
    />
  </li>
</ul>

...

<script>
export default {
  methods: {
    getEvent(iconName) {
      const path = this.$route.path
      // 今開いているページのアイコンにクリックイベントを与えない
      if (iconName !== path) {
        return 'click'
      }
    },
    handleEvent() {
      console.log('何かを発火!!')
    }
  }
}
</script>

叩いたメソッドの handleEvent 内で分岐することが多いかもしれませんが、このように書くこともできそう、という所感です。

PHPStorm と VSCode のデフォルト設定では、イベント名 ( handleEvent() のところ) にシンタックスハイライトが効かないので、コードの読みやすさの面ではあまり良くないですね。

この用途ではコード量は減らないため、別のイベントハンドラを与えたいときに使う、が有用のようです。

動的引数の値の成約

ドキュメント内にもありますが、動的引数の指定にはいくつか制約があります。上記のケースで、 method で @[getEvent(icon)] のように指定せず直接記述したいと思いましたが、下記のような記述はできません。

@[ ] 内には演算子は書けないようです。

// 不正な記述
@[$route.path !== icon ? 'click' : '' ]="doSomething()"

例えば icon: {event: 'click'} のようなオブジェクトが渡ってきたときに、 icon.eventclick を指定したいところですが、こちらも同様に記述できません。そのため上記のように method で与えてあげる必要があります。

// 不正な記述
@[icon.event]="doSomething()"

制約が多いため用途は限定的かもしれません。

動的引数の式には構文上の制約があります。というのも、スペースや引用符のような一部の文字は、HTML の属性名としては不正な文字だからです。また、in-DOM テンプレートを使う場合は、大文字のキーも避ける必要があります。 テンプレート構文 — Vue.js

さいごに

コンポーネントからクラス名、イベントハンドラまでを動的に配置してみましたが、イベントハンドラについては用途が限られる印象なので、開発の大きな助けになるイメージではなさそうでした。良い事例があれば教えていただけるとありがたいです。

最後に、弊社 ROXX では自社プロダクト一緒に成長させていくエンジニアを募集しています!

ご興味をお持ちの方はぜひご連絡ください!

www.wantedly.com

www.wantedly.com

理想の開発組織に沿った行動を表彰しました!

こんにちは kotamat です。

弊社の開発チームでは、理想の開発組織像というものを定義しております

techblog.scouter.co.jp

詳細は上記リンクを参考にしていただきたいですが、それを実現するために3つのバリューを設定しております。

  • ROCK
    • 個々のプレゼンスが組織のプレゼンスとなる
  • JAZZ
    • 自分史上最高のプルリクを出す
  • PROGRESSIVE
    • 好奇心を持って体系化する

弊社では3ヶ月に一回の評価を行っており、その評価において上記バリューを体現した人を選定し、表彰することにしました。

もともとはバリューの評価は給与に反映していたのですが、プロダクトが複数にまたがっていく中で、どういったバリューが評価されるのかが埋没化され、バリュー自体が形骸化されてしまうことを懸念しておりました。

他のメンバーにもわかる形で表彰することにより、他のメンバーも「こういうことをすれば評価されるのか」というのを認識できるようになるため、今Qからは表彰することになりました。

それでは早速ですが、受賞者を紹介させていただきます!

2019年第3Qの評価者はこの3名!

ROCK賞: @masaakikunsan

f:id:kotamat:20190807142218j:plain

SCOUTER Conference vol.01 - connpassを主体的に運用していたり、NuxtMeetup - connpassや外部勉強会への積極的な登壇、また他のプロダクトのフロントエンドの技術力底上げなど、多方面で存在感を発揮していたところが ROCK でした!

本人からのコメントです。

ROCK賞あざます!僕は僕らしく生きていただけですが、表彰されて嬉しいです!(イキり) 会社の今後と自分のやりたいことを上手く紐付けて今後もROCKしていきたいと思います! イベント系に関してはメンバーの協力がないとできなかったのでこの場を借りて感謝の気持ちを伝えたいと思います、ありがとうございました!

JAZZ賞: @ktraoy

f:id:kotamat:20190807142752j:plain

システムの大改修のプランニングをするために、先日の開発合宿をしていたのですが、大改修でやることの物量が読めない中、臨機応変に合宿でのアウトプットを最大化するために動いていた点が JAZZ でした!

本人からのコメントです。

Jazz賞の表彰いただきありがとうございます。 前Qからスクラムマスターとしての役割に取り組み始めた所なので、このように評価をしていただけたのは、チームのおかげだと思ってます。 また合宿の活動の前後で共に進行役を担ったPOとスクラムマスターの協力のおかげでもあります。ありがとうございます。 まずは、プロジェクトがまだ完了していないので、プロジェクトの成功に取り組みます。その後も事業の成功のためにチームとPOをサポートしていけるように、自己研鑽を積んでいきたいと思ってます。

PROGRESSIVE: @tsmd44

f:id:kotamat:20190807143013j:plain

SARDINEのモノレポ化やk8sの提案など、今抱えている技術課題を新しい技術の導入によって解決に向かえている点が PROGRESSIVE でした!

本人からのコメントです。

この度、Progressive賞をいただき大変うれしく思います! 業務委託にも関わらず、モノレポ化など通常の開発以外にも様々な業務を任せていただけているのは、POをはじめメンバーの理解とサポートのおかげです。 プロダクト的にも開発的にも技術で解決できる課題はまだまだあると感じており、開発スピードをさらに加速できるよう日々研鑽に励んでまいりたいと思います。 引き続きどうぞ宜しくお願いいたします!

最後に

f:id:kotamat:20190807143246j:plain

最後に表彰式に参加したメンバー全員で記念撮影。 次回は他の人も受賞できるよう、バリューの体現を期待しております!

株式会社ROXX ✕ ドラッカー風エクササイズ

はじめに

こんにちは株式会社ROXXのhirokinishizawaです。

いきなりですが現在SARDINE開発チームの体制はこの様になっています。

f:id:hiroki-nishizawa:20190730191203p:plain

もともと3月から開発チームは2チームあったのですが人数も少なかったというのもありスクラムマスターというロールはいませんでした。両チーム人数も増えてきて6月からスクラムマスターを入れるということになりました。

スクラムマスターになってから1ヶ月程たって、メンバー間やチーム間で価値観や期待することに対して疑問も上がってきたので「ドラッカー風エクササイズ」を行いました。

ドラッカー風エクササイズとは

ドラッカー風エクササイズはアジャイルサムライという本にある、チームにおける期待をすり合わせるための手法です。たった4つの質問を通じて、お互いの考えや価値観、期待のすり合わせを行います。

  • 自分は何が得意なのか?
  • 自分はどういうふうに仕事をするか?
  • 自分が大切に思う価値は何か?
  • チームメンバーは自分にどんな成果を期待していると思うか?

たった4つの質問と書きましたが回答する側は結構考える内容になっていると思います。

自分が得意なことは結構出てくるイメージですが、大切に思う価値やチームメンバーからどんな期待をされているかはすぐに出てこない人も多いかと思います。

株式会社ROXX ✕ ドラッカー風エクササイズ

スクラムチーム全体でのチームビルディングということもあり、今回は少しカスタマイズをして各チーム内と、ロール間という2パートを用意しました。

チーム内

以下の質問でチーム内での期待することを各々チームで認識をあわせました。

  1. 自分が大切に思う価値は何か
  2. 自分がどうやって貢献していくつもりか
  3. 他のメンバーに期待していることは何か

f:id:hiroki-nishizawa:20190801145522j:plain

各々発表する時間をとってメンバー間での期待すり合わせを行ってもらいました。「他のメンバーから期待されていたことを見て新しい発見があったか」や「メンバー間での大切にしていることの違い」など見てもらいます。

ロール間

以下の質問で各ロールに期待することを全体で認識をあわせました。

  1. 自分たちのロールが大切にする価値は何か
  2. 自分たちのロールが期待されていることは何か
  3. 他のロールに期待していることは何か

f:id:hiroki-nishizawa:20190801145553j:plain

ロール間で期待のすり合わせを行った理由はスクラムマスターの役割はなんなのかという話があがったためパートを用意しました。

スクラムチームの成熟度によってスクラムマスターの役割も変わっていくため、現段階でチームでどのような期待をされているのか。また「PO→スクラムマスター」「各開発チーム→スクラムマスター」だけではなく、「PO→各開発チーム」「各開発チーム→PO」も行うことで、チーム間で差分があるのかを改めて認識できました。

まとめ

今回はスクラムチーム全体でやりましたが、チーム毎にチームの成熟度は違うため、チームのフェーズによってカスタマイズを行っていくのが良いと思います。

新しいメンバーが入ってきた時などチームビルディングとしてドラッカー風エクササイズは有効なので、みなさんもぜひ試してみてください!

さいごに

チームが成長していくにあたり、これからもメンバーを増やしてもっと生産性の高いチームにしていきたいと思っています。

新規事業や既存事業の拡大も考えているため自分の力で事業を成長させたいエンジニアを絶賛募集中です!

興味のある方は下記からご応募いただくか、弊社CTO:kotamatまでご連絡ください!!

www.wantedly.com

www.wantedly.com

LaravelでIP制限機能の実装

はじめに

こんにちは、株式会社ROXXの開発責任者の小平(@ryotakodaira )です。 業務では、SARDINEという人材紹介会社向けの業務管理システムを開発・運用をしています。

規模の大きい人材紹介会社がSARDINEを利用するにあたって、システムの利用時に 自社のIPからのみアクセスを許可 したいという開発案件が発生したため、備忘録的に開発としてどのような対応を行ったのかを残そうと思います。

準備

今回の開発で達成したいこととしては、

「システムの利用時に 自社のIPからのみアクセスを許可 したい」となっており、

ユーザー毎に自社のIPアドレスを設定できるようにすることでした。

当然ですが、認証を先に済ませないとどのIPアドレスを許可するかの判断をシステム側で行うことができないため、認証済みのリクエストが来たときに必ずIP制限の評価が走るミドルウェアを実装することとしました。

早速、ミドルウェアを作っていきます。

Laravelのartisanコマンドでミドルウェアクラスのファイルを作ることができるので、そちらを利用します。

$ php artisan make:middleware CustomIpLimitation

ミドルウェアの実装

<?php

namespace App\Http\Middleware;

use App\Entities\User;
use Closure;
use Symfony\Component\HttpFoundation\IpUtils;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

/**
 * Class CustomIpLimitation
 * @package App\Http\Middleware
 */
class CustomIpLimitation
{
    /**
     * @var array
     */
    // ①
    const ACCEPTED_IPS_GROUP_BY_USER = [
        // user_id => ['127.0.0.1/0', '127.0.0.1/1']
        1 => [
            '127.1.1.1/32',
            '127.2.2.2/32',
        ],
    ];

    /**
     * @see https://ip-ranges.amazonaws.com/ip-ranges.json
     */
    // ②
    const CF_IPS = [
        // CloudFrontのIPレンジ
        '13.124.199.0/24',
    ];

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        // ③
        if (!app()->runningUnitTests() && !app()->environment('production')) {
            return $next($request);
        }

        /** @var User $user */
        $user = $request->user();

        // ④
        $allowedIps = $this->allowedIps4AuthenticatedUser($user->id);

        // ⑤
        if (empty($allowedIps)) {
            return $next($request);
        }

        // ⑥
        $request::setTrustedProxies(
            [$request->server->get('REMOTE_ADDR')] + self::CF_IPS,
            $request::HEADER_X_FORWARDED_AWS_ELB
        );
        $clientIp = $request->ip();

        // ⑦
        if (!IpUtils::checkIp($clientIp, $allowedIps)) {
            throw new AccessDeniedHttpException('IPNotAllowed');
        }

        // ⑧
        return $next($request);
    }

    /**
     * @param int $userId
     * @return array
     */
    protected function allowedIps4AuthenticatedUser(int $userId): array
    {
        return self::ACCEPTED_IPS_GROUP_BY_USER[$userId] ?? [];
    }
}

実装内容について順を追って説明していきます。

ACCEPTED_IPS_GROUP_BY_USER という定数に、 IP制限を設定するユーザーIDをkey、許可したいIPアドレスの配列をvalue 、とした配列を定義します。

本来はこれらの情報は何らかのデータベースで永続化し、都度データベースからデータを引いてくるべきですが、本投稿では定数に定義する形で進めます。

CF_IPS という定数に、CloudFrontのIPレンジを設定しています。

弊社のサービスはCloudFrontを使用しているため後々の処理でCFのIPレンジが必要となります。

こちらも本来は定数として定義するのではなく、都度、以下のURLを参照するなどをして最新のIPレンジを取得するようにした方が良いでしょう。

https://ip-ranges.amazonaws.com/ip-ranges.json

(体感ですが、CFのIPレンジは1,2週間に1度くらいのペースでアップデートがかかります。)

こちらはあってもなくてもどちらでも良いですが、開発中にIP制限に引っかかってしまい非常に面倒だったため、開発中はこの機能を無視するようにしています。

phpunit実行時, 本番環境以外では機能を無視するようにしています。

で定義したIPリスト( ACCEPTED_IPS_GROUP_BY_USER )をクライアントからリクエストを送信したユーザーIDで検索して、そのユーザーIDに対してIP制限が設定されていた場合は、許可するIPアドレスの配列を返却しています。

で取得した許可するIPアドレスの配列が空の場合は、IP制限を設定していないものとみなしそのままミドルウェアの処理を抜けます。

クライアントのIPアドレスを取得する前に setTrustedProxies を行い、サービス提供者が直接管理しているリバースプロキシのリストをセットします。

セットすべきリバースプロキシの内容はサービスのインフラ構成により異なってきますが、弊社のサービスの場合は CF -> ALB -> EC2 となっているため、

self::CF_IPS で最初に定義したCFのIPレンジを取り、 $request->server->get('REMOTE_ADDR') で直前(ALB)のIPレンジを取ってそれらをセットしています。 ここは完全にインフラ構成に依存していますが、そのことを最初は考慮できていなかったため割と躓いたポイントです。

その後、 $request->ip() で正確なクライアントIPアドレスを取得することができます。

クライアントのIPアドレスが許可されたIPアドレスの範囲内にあるかを検証しています。

SymfonySymfony\Component\HttpFoundation\IpUtils::checkIp メソッドを使えば一発で評価することができます。

許可されたIPアドレスの範囲内になかった場合はその時点でエラーをクライアントに対して返却しています。

クライアントのIPアドレスが許可されたIPアドレスの範囲内であれば、正常とみなしミドルウェアを抜けて終了となります。

後は app/Http/Kernel.php などで今回作成したミドルウェアを通るように設定してあげれば完了です。

最後に

事業・サービスが成長していくにあたって、これからもメンバーを増やしていきたいと思っています。

新規事業や既存事業の拡大も考えているため自分の力で事業を成長させたいエンジニアを絶賛募集中です!

興味のある方は下記からご応募いただくか、@ryotakodairaにご連絡ください!!

www.wantedly.com

www.wantedly.com

Nuxt.js の context をおさらいする

こんにちは、 ROXX の匠平@show60です。

Nuxt.js の context は色々な機能を内包してくれているためそれとなく使えている感覚でしたが、そもそも中身どうなってんの?と気になったので調べてみることにしました。

そもそも context とは

JavaScript では this という呼び名の context ですが、直訳すると 文脈、脈絡、前後関係 という意味となります。

日常では曖昧な使い方をされてしまうため一層理解に戸惑いますが、 MdN では outside of any function (どの関数の外側にもある) と説明されています。

Global context

In the global execution context (outside of any function), this refers to the global object whether in strict mode or not.

this - JavaScript | MDN

global object とありますが、つまるところグローバルで定義したオブジェクトであり、文脈という言葉を使うとすれば、「すでに定義したという文脈があるんだよー。知っておいてね。」という解釈になるでしょうか。

Nuxt.js の context

Nuxt.js の公式ドキュメントには context で使用可能なキーのリストが載っています。

API: コンテキスト - Nuxt.js

このリストを見ていくと、例えば routefrom といったものは Vue Router Route のインスタンスの型をとっています。 Nuxt.js のアプリ内の適当な箇所で context を呼び出し、 console.log(route) を行ってみると以下のような内容が表示されます。

{
  fullPath: "xxx",
  hash: "",
  params: {},
  path: "xxx",
  query: {}
  ...
}

これは Vue Router のルートオブジェクトプロパティがそのまま含まれているということが分かります。

API Reference | Vue Router

つまり context にはVue Router が定義してあり、 route などの名前で呼び出すことによってどこでも使えますよ、というのが context です。

上記のドキュメント内のキーのリストに app という項目が含まれているのですが、 NuxtAppOptions の型を持っていると説明してあります。

これはどのように使うのでしょうか。

Nuxt.js の context の app

物は試しで、 context の app を呼び出したところで console.log(app) してみましょう。

{
  $axios: ƒ wrap(),
  beforeCreate: ƒ beforeCreate()
  components: {NuxtLoading: {}}
  computed: {isOffline: ƒ}
  context: {isStatic: false, isDev: true, isHMR: true, app: {}, store: Store, …}
  ...
  store: Store {_committing: false, _actions: {}, _actionSubscribers: 
Array(0), _mutations: {}, _wrappedGetters: {}, …}
  watch: {nuxt.err: "errorChanged"}
}

コンポーネント内で使用するオブジェクトなども並んでいます。

この中の context を開いて見てみると、先程の routefrom も存在しており内容も同一となっています。

この context 内の app について、先程のリスト内では すべてのプラグインを含むルートの Vue インスタンス と説明されています。つまり、 app には Vue RouterVue Store など context に含まれるオブジェクトがすべて参照できるというわけです。

context の定義

念のために Nuxt.js の Github 上でコードも確認してみましょう。

GitHub - nuxt/nuxt.js: The Vue.js Framework

Nuxt.js の package/vue-app/template/index.js 、 58 行目あたりに async function createApp とあります。この処理内で const app = { してある箇所があり、どうやらここで app を定義しているようですね。

async function createApp(ssrContext) {
  const router = await createRouter(ssrContext)
  ...
  const app = {
    router,
    <% if (store) { %>store,<%  } %>
    nuxt: {
      defaultTransition,
      transitions: [ defaultTransition ],
      setTransitions(transitions) {...},
      err: null,
      dateErr: null,
      error(err) {...}
    },
    ...App
  }
  ...
  await setContext(app, {
    route,
    next,
    ...
  })

router はここで注入されているようです。setContext で context を定義しているようですのでこちらも見てみましょう。

nuxt.js/utils.js at dev · nuxt/nuxt.js · GitHub

export async function setContext(app, context) {
  // If context not defined, create it
  if (!app.context) {
    app.context = {
      isStatic: process.static,
      isDev: <%= isDev %>,
      isHMR: false,
      app,
      <%= (store ? 'store: app.store,' : '') %>
      payload: context.payload,
      error: context.error,
      base: '<%= router.base %>',
      env: <%= JSON.stringify(env) %><%= isTest ? '// eslint-disable-line' : '' %>
    }
    ...
  }
  ...
  app.context.params = app.context.route.params || {}
  app.context.query = app.context.route.query || {}
}

index.js が起動し、そこで context の中身を諸々注入していることが分かりました。この app がどこでも参照できる状態にあることで各コンポーネントから context 内のオブジェクトを扱うことができるわけですね。

実務では storeerror あたりを使うことが多いでしょうか。ページから store に保管してあるデータを参照したり、 API へのリクエストが失敗したときのエラーハンドリングを行うため自ずと頻度が上がります。

プラグインの注入

新たに context にプラグインを注入したい場合は、上記の Nuxt.js の utils をわざわざ編集するのではなく plugins ディレクトリと nuxt.config.js に記述するだけです。

プラグイン - Nuxt.js

プラグインではないですが、 app にテキストを入れて表示してみたいと思います。

plugins/sample.js

export default ({ app }) => {
  app.sample = 'sample'
}

nuxt.config.js

plugins: [
  '~/plugins/sample'
]

npm installyarn 等をして config.js を再読み込みすると、先程の console.log(app) の結果に sample が追加され、テキストが表示されました。

f:id:show-hei:20190729132411p:plain
plugins/sample の注入

とても簡単に扱えるようにしてくれてますね。

まとめ

外部プラグインは context だけでなく Vue インスタンスに注入しても使用することができます。

どちらもコンポーネント内で呼び出すことができるのですが、どちらの使用を選択するべきかについて、また改めて調べてみたいと思います。

最後に、私たち ROXX では一緒に開発を進めていただける仲間を募集しています。 ご興味お持ちいただいた方はぜひご応募ください!

www.wantedly.com

www.wantedly.com

Frontend de KANPAI! で登壇してきました

こんにちは、株式会社ROXXの石岡 将明( @masaakikunsan )です。

7月19日の Frontend de KANPAI! #7 - Going on 令和 で登壇させていただきました。

今回は、登壇した話について、ブログを書いていこうかなと思います。

登壇でのテーマ

今回は Going on 令和 というテーマがありました。 内容としては令和でも Frontend やっていきみたいなのを考えていましたが、Frontend を普段からやっている人や、ある程度スキルのある人達は勝手に成長していくので今回は令和から Frontend やっていきたい人に向けての発表にしようと思いました。

なのでゴールとしては、下記としました。

  • これからフロントエンドを始めたい、伸ばしていきたい人の背中を押す
  • フロントエンドエンジニアとしてのキャリアを悩んでる人の手助けができる

実際のスライド

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

slides.com

タイトルは 令和から始める Frontend としました。

アジェンダは、下記です。

  • これまでのフロントエンド
  • ここ数年のフロントエンド
  • これからのフロントエンド

スライド内では、僕のキャリア的な話を書いております。 ここでは、僕が3年ぐらいでこんな感じのことをやっているって話をしました。

詳しくは、スライドを見ていただきたいのですが、全体的にこれから始める人はこうしたほうがいいんじゃない?という話 + 僕が3年で普通に働けているのでみんな普通に1年本気でやればやっていきできるよっていった感じの発表になりました。

f:id:masaakikunsan:20190726135017j:plain

フロカンについて

フロカンでは、LT 枠はなく運営が登壇依頼をしその人達 + DeNA の社員が登壇する形になっています。 それもあり、毎回発表のクオリティはかなり高いものになっています。

また、スタッフもやりたくてやってるんだろうなぁといった感じが強く、ものすごく雰囲気が良いです。(あとコミュ力が高いと感じた)

今後も参加していこうと思っていますし、弊社イベントでも取り入れられることはかなりあるかなぁと。

また、登壇させてもらえるように日々頑張っていきをします。

さいごに

現在、株式会社ROXX では、エンジニア、デザイナーの募集をしております。

興味のある方は、是非下記からご応募お願い致します!

www.wantedly.com

SARDINE開発チームを支えるツールたち

こんにちは。2019年7月1日からジョインしました です。 入社当日に 株式会社ROXXへ社名変更 するというリリース作業に立ち会うレアイベントを経験できてラッキーでした。

この記事では SARDINE をどのように開発しているのか、SARDINEの開発チームを支えるWEBサービスやツールを紹介します。

SARDINE

開発体制

開発体制ではスクラム手法を取り入れています。月曜日に始まり金曜日まで、毎週1週間のスプリントです。 金曜日はスプリントレビューと振り返りや次スプリントのプランニング、ときには意識合わせのワークショップなどを行い1日が終わるので開発する時間は月曜日から木曜日までの4日間です。 スプリント期間が短いこと、しっかりプランニングを行うことで1週間の予測が立てやすいので、メリハリをつけた活動ができています。 トレードオフスライダーは納期と品質は毎スプリント一定を保つ、スコープで実装を調整するよう意識しています。

スクラムマスターの hirokinisizawaスクラムマスターの話 、またCTOの kotamat技術組織の話 として記事を公開していますのでそちらもご覧ください。

Jira

スプリントの管理には Jira をフル活用しています。たとえば朝会ではスクラムボードで今日やることの確認をします。スプリント中に発生した課題は随時バックログへ追加して必要に応じてスプリント中に対応したり次スプリントに回したりの判断をしやすくします。

秘匿性が高い情報も含まれているため黒塗りが多くなってしまいました...イメージだけでもご確認いただければ幸いです。

Jiraのスクラムボード ほぼ真っ黒でごめんなさい

チームのコミュニケーション・情報の共有や蓄積

リモートで作業しているメンバーも多数いるのでコミュニケーションは密にとるようにしています。

Google Meet, Discord

朝会では Google Meet を利用して会話しています。 Google Meetで画面共有をおこなってJiraのスクラムボードを全員で参照したり各自困っているコミットなどを共有して全員で認識を合わせられるように心がけています。 また、チームメンバーの顔や状況をわかりやすくするために外部カメラを繋いだり声が聞き取りやすいように外部スピーカーを使っています。

モニター上部に設置したカメラ

かんたんに声をかけたり話しかけたりしたい場合には Discord も使っていますね。

Slack

テキストコミュニケーションには Slack を利用しています。 Slackは開発メンバーに限らずROXXの全メンバーが活用しています。 出社退社時の打刻はスラッシュコマンドで行いますし、全社周知事項の案内、お客様からのお問い合わせ速報など様々な内容が通知されます。

ただし開発用のチャンネルだけでもが多岐にわたるので慣れないと追うのが大変でした。 このへんは課題があるなと感じています。

esa

esa もROXXの全メンバーが活用しています。 開発環境の構築手順、ステージング環境のアクセス方法などなどとりあえずesaを探せば情報に辿りつけるようになっています。

development and deploy

画面デザインの設計には Figma を、ソースコードの管理には GitHub を、 CIに Travis CI を利用しています。 サービスのインフラに AWS を利用しておりAPIサーバーはEC2、データベースはRDS、静的サイトの配信にはS3 + CloudFrontを利用しています。 静的サイトでもランディングページなどの一部のサイトには Netlify を利用しています。

イメージ

ここまで出てきたツールを画像にまとめるとこのような図になります。

SARDINEの開発ワークフローまとめ

ほかにも

ほかにもスプリントレビューやミーティングで利用する50インチ程度の大きなディスプレイが開発チームエリア近くに配置されています。 またディスプレイも申請せずとも1人1枚以上配給されます。私のチームでは27インチ程度のモニターを2枚利用しています。

...以上のように、SARDINEサービスの運営開発をより円滑に行うために様々なツールを用いています。

SARDINEの開発体制がみなさんにイメージが伝われば幸いです。

最後に

株式会社ROXXでは一緒にSARDINEの開発に携わってくれるメンバーを、また新規サービスの back check にもメンバーを随時募集しています。 この記事を読んでROXXに興味を持ってくれた方はぜひご応募ください。  

www.wantedly.com