s平面の左側

左側なので安定してます(制御工学の話は出てきません)

Go言語 で Websocket サーバ(docker-compose.yml もあるよ)

プライベートで開発しているプロダクト用の技術調査。

とりあえず簡単に、受け取ったメッセージをそのまま返すサーバを実装した。

github.com

docker-compose up して client/index.html をブラウザで開くと、開発者コンソールから「Message from server echo {"msg":"hello world!"}」の出力が確認できる。

Golang の実装部分は下記の Qiita 記事をそのまま参考にしたので、解説はそちらを参照のこと。

余談

*-test ってリポジトリ名がなんだか微妙な感じがした。

practice とか、 training とかがいいのかな?

2018-06-09 追記

あまりよく確認していなかったが trevex/golem は長らく更新のないプロジェクトだった。

The project can be considered dead. There has been no development in recent years. It is still available on github for research and archiving purposes.

なので golem の 内部で使われていた gorilla/websocket を使うようにソースコードを書き換えた。

github.com

【資料あり】「Laravel/Vue.js 勉強会 #4」に参加して LT もしてきた

一昨日の4/25開催された Laravel/Vue.js 勉強会に参加してきた。

laravue.connpass.com

Laravel/Vue.js 勉強会は第1回に登壇枠で参加させていただいたこともある。

blog.okashoi.net

以下、参加しながら取ったメモ。

[登壇枠1] Laravel と Nuxt.js を業務で取り入れる際に得た知見

www.slideshare.net

  • 株式会社 IT プロパートナーズ
    • 戎島さん @isao_x /森山さん @frostndays
  • プロジェクト概要
    • toC の 大規模 SNS のようなもの
    • Nuxt.js → SSRモード/SPAモードが選べる
      • ogpタグの懸念から SSR モード
  • 認証方法
    • APIでやる Laravel Passport
      • ルーティングが自動生成
    • facebook
      • laravelpassport-facebook-login
      • デフォルトエンドポイントを変更
      • トークン有効期限チェック処理を追加した
  • Nuxt.js 環境設定読み込み
    • env.js/env.example.js というファイルを作った
      • .env と同じようなことがやりたい
    • nuxt.cofig.js で env.js を require する
      • 公式ドキュメントに載っているような定数の呼び出し方が使えるように
      • require するのは ES5 を使っているため
  • axios のラッパー
    • ヘッダにデフォルトの値をセットしたいから
    • SSR のときは Laravel のサーバにはプライベートネットワークでアクセス可
    • ユーザが見ている画面からはグローバルネットワークから
  • 役に立ったツール
    • Clockwork
      • Chrome 拡張
      • ブラウザだけで debugbar のようなことが実現できる
    • sequel-pro-laravel-export
      • migration 自動生成する sequelPro 拡張
    • SQL to Laravel Builder
      • SQL → クエリビルダの記法に変換
    • Vue.js devtools
    • PHP_CodeSniffer / PSR-2
    • ES6 は airbnb のものをベースに
    • Vue.js 公式スタイルガイド

[登壇枠2] AWSで構築する Nuxt.js のインフラ構築3選

gitpitch.com

  • 株式会社SCOUTER
    • 松本さん @kotamat
  • Nuxt.js
  • Nuxtjs の良さの記事は多いけど、インフラの記事は少ない!
  • Laravel + Nuxt generate
    • 一緒のリポジトリに置く
    • travis CI で NuxtGenerte する
    • Codedeploy
    • nginx でリバースプロキシ
    • ビルド時にチェックすべきポイント
      • client/ と nuxt.config.js を追加
      • resource/assets/js とか邪魔なので消す
    • プロビジョニングのために appspec.yml を tracis.yml と同じ階層に置く
    • dev と nuxt generate でフローが違う
      • CI の際に nuxt generate してエラーのとき落とさせるようにする
  • WordPress + Nuxt SSR
    • SEO 対策必須のため SSR
    • forever
      • node のプロセス
      • プロセスが切れたタイミングで再起動してくれる
        • forever restart で再起動できるが、つまりどころが多いらしい
  • express で構築する場合
    • デフォルトの nuxt/nuxt start が使えなくなる

[登壇枠3] Element のすすめ

speakerdeck.com

  • 株式会社 FABRIC TOKYO
    • 中筋さん
  • Element の概要
  • 使用した理由
    • セレクトボックスで選択順の制御などで楽したい → Cascader が使えた
  • 実装面の考慮
    • vaue, label の組を data(state) にもたせて options props に渡す
    • マスタは API から取得

[LT枠1] VueでJSXを使うのはありなのか

speakerdeck.com

  • 株式会社 SCOUTER
    • @ryotakodaira
  • あえて JSX を使うのも場合によってはあり
    • 部分的にメソッドに切り出して関心の分離が行える
  • JSX ではデフォルトで v-model ディレクティブが使えない
    • パッケージを入れれば使える

[LT枠2] Laravel 5.6 デフォルトの例外ハンドリング処理をまとめてみた

www.slideshare.net

  • 自分の発表
  • 直近でこの辺りの設定するときに迷ったこと・調べたことをまとめた
    • バリデーションエラー時、 abort() 時の挙動についてなど
  • 設定の詳細については後で別記事にまとめようと思う

感想

その後、懇親会で多くの方とお話させていただいた。

やり取りの中で挙がったのが、この勉強会に限らずに世の中の情報のほとんどは「Laravel と Vue.js それぞれの知見」という感じで「両者ががっちり噛み合って何かをやる」というの話は少ないな、ということ。

もともと「Laravel が Vue.js を公式サポート」というのも、Laravel Mix という webpack 設定の wrapper を提供しているだけなのでやむなし、といった感じか。

そもそもアーキテクトとして「バックエンドとフロントが密に結合する」というのがあまりよろしくない気がしているので、それはそれでいいのだろう。

また、今回の勉強会では Nuxt.js が推されていた(主催の @kotamat さんが NuxtMeetup も開催していることもあって)ので触れてみようと思った。

続・Laravel 5.4 におけるユーザ権限管理の実装に関する考察

このエントリーの続き。

blog.okashoi.net

このエントリーを書いた後早々に「Laravel のドキュメントを全部読め」という旨のツッコミを頂いた。

そして、Laravel の機能でやりたいことが実現できることを学んだ。

laravel.com

日本語訳はこちら→ 認可 5.4 Laravel

ということで、当初は続きのエントリーとして「それぞれの方法のメリット・デメリットの比較」を書こうと思っていたのだが、公式の方法がどちらの方法にも勝るので単に Laravel の認可の機能について説明をする。

実現したいこと

前回のエントリーの内容では説明のためには不充分なので、より具体的な例を挙げる。

前回のエントリーで示した条件(再掲)

前提:Laravel 5.4 で開発している web システム

ユーザごとに「このデータは編集できる」「このデータは閲覧のみ可」といった権限管理をしたい。

登場するテーブルは以下の2つ。

(テーブル名は実際のものとは異なる。あくまでイメージ)

  • users
    • ユーザのテーブル。後述する groups テーブルの id を外部キーとして持つ。
  • groups
    • 権限グループのマスタテーブル。id と「閲覧のみ」「管理者」といった表示用の名前 name のみを格納。

より具体的な実現したいこと

前提

以下に説明するような posts というテーブルがあるとする。

  • 「所有者」としてuser テーブルの id を外部キーとして持つ。
  • content, category というカラムを持つ。

目的

posts に対応する Post という Model に対して次のような権限管理したいを考える。

○:可 ×:不可

操作 閲覧のみ 所有者 管理者
閲覧
新規作成 × -
content の更新 ×
category の更新 × ×
削除 × ×

また、「閲覧のみ」ユーザのgroups.id は 1 、「管理者」の groups.id は 9 とする。

実装方法

Policy の作成

app/Policies/PostPolicy.php

<?php

namespace App\Policies;

use App\Models\User;
use App\Models\Post;

class PostPolicy
{
    /**
     * 全てのメソッドの前に呼ばれる
     * null を返した場合、各メソッドに委ねられる
     *
     * @params User $user
     * @return bool|null
     */
    public function before(User $user)
    {
        // 「管理者」は全ての操作が可能
        if ($user->group_id === 9) {
            return true;
        }

        return null;
    }

    /**
     * 閲覧が可能かどうか
     *
     * @params User $user
     * @params Post $post
     * @return bool
     */
    public function view(User $user, Post $post): bool
    {
        // 全てのユーザが可能
        return true;
    }

    /**
     * 新規作成が可能かどうか
     *
     * @params User $user
     * @return bool
     */
    public function create(User $user): bool
    {
        // 「閲覧のみ」ユーザでないこと
        return $user->group_id !== 1;
    }

    /**
     * content の更新が可能か
     *
     * @params User $user
     * @params Post $post
     * @return bool
     */
    public function updateContent(User $user, Post $post): bool
    {
        // 所有者であること
        return $user->id === $post->user_id;
    }

    /**
     * category の更新が可能か
     *
     * @params User $user
     * @params Post $post
     * @return bool
     */
    public function updateCategory(User $user, Post $post): bool
    {
        // (「管理者」ユーザ以外)全てのユーザが不可
        return false;
    }

    /**
     * 削除が可能か
     *
     * @params User $user
     * @params Post $post
     * @return bool
     */
    public function delete(User $user, Post $post): bool
    {
        // (「管理者」ユーザ以外)全てのユーザが不可
        return false;
    }
}

※実際は「管理者かどうか」といった判定を Model のメソッドにしておくのがよい

Policy の登録

app/Providers/AuthServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * アプリケーションにマップ付されたポリシー
     *
     * @var array
     */
    protected $policies = [
        App\Models\Post::class => App\Policies\PostPolicy::class,
    ];

    /**
     * アプリケーションの全認証/認可サービスの登録
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        // ...
    }
}

ロジック内で利用

<?php

use App\Models\Post;

$user = \Auth::user();

// $user が $post を閲覧できるか
$user->can('view', $post);


// $user が Post を新規作成できるか
$user->can('create', Post::class);

Middleware として利用

許可されていない場合は 403 を返す。

routes/web.php

<?php

Route::post('/posts', 'PostController@create')->middleware('can:create, App\Models\Post');

ControllerHelper

authorize() メソッドが利用可能。こちらも許可されていない場合 403 を返す。

app/Http/Controllers/PostController.php

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class PostController extends Controller
{
    /**
     * ポストの削除
     *
     * @param  Request  $request
     * @param  Post  $post
     * @return Response
     */
    public function delete(Request $request, Post $post)
    {
        $this->authorize('delete', $post);

        //...
    }
}

Blade テンプレート内で利用

@can('updateCategory', $post)
    {{-- カテゴリ編集可の場合 --}}
@else
    {{-- 不可の場合 --}}
@endcan

反省

勉強不足で本来不必要な考察・議論をしていた。

まずはドキュメントを調べる、というのは基本であるはずだが、つい気が緩んでいたようだ。

Vuetify.js でお手軽マテリアルデザイン + Atomic Design「風」のディレクトリ構成

Vue.js #4 Advent Calendar 2017 - Qiita の 19 日目のエントリー。

(先日の記事の続きを書こうと思っていたが、こちらの日が先に来てしまった)


最近、フロントエンドエンジニア・デザイナー不在のプライベートな開発において、フロントサイドの実装を任されることがあった。

その際に Vuetify.js を用いて比較的簡単にマテリアルデザインのページを実現できたので、その備忘録。

(意見・ツッコミなどは歓迎)

vuetifyjs.com

出来上がった画面のイメージはこんな感じ。

f:id:okashoi:20171218114611p:plain

※グラフ描画は Vuetify.js で提供していないので、 vue-chartjs を利用している。

このレベルであれば、Vue.js の基礎知識だけある状態から1日で辿り着くことが出来た。

概要

Vuetify.js はマテリアルデザインのコンポーネントを80種類以上提供している。

Bootstrap などでグリッドシステムに慣れていれば、部品を組み合わせる感覚でいい感じにページを作ることができる。

f:id:okashoi:20171218115307p:plain

導入方法として CDN からの読み込み、 npm でのインストールどちらにも対応している。

また、8種類の vue-cli テンプレートが提供されているので、新規プロジェクト作成時にはこちらを利用してもいい。

基本的な使い方の解説や、利用できるコンポーネントについては 公式ドキュメント や、世の中にある他の記事にお任せ。

ディレクトリ構成

前提

  • ビルドには Webpack を用いる
  • 1ページに対して、1 つの html が 1 つの JS ファイルを読み込む形(SPAではない)

また今回の試みとして、いい感じにディレクトリを切って .vue ファイルからなるコンポーネント郡を分類したかったので、その基準として Atomic Design*1 を参考にしてみた。

ただし、私が Atomic Design についてきちんと理解・正しい解釈ができていなかったため、本来の Atomic Design 考え方からは外れてしまっている点に留意。 *2

+ .babelrc
+ webpack.config.js
+ package.json
+ src/
  + entries/        # Webpack のエントリーポイント
  | + landing.js
  | + main.js
  | :
  + Components/     # コンポーネント郡
  | + Templates/
  | | + Footer.vue
  | | + Header.vue
  | | - Contents/
  | |   + Landing.vue
  | |   + Main.vue
  | |   :
  | + Pages/
  | | + Landing.vue
  | | + Main.vue
  | | :
  | - Organisms/
  |   + AForm.vue
  |   + ATable.vue
  |   + AChart.vue
  |   :
  - common.js       # 共通の処理を記述したモジュール

コンポーネント.vue ファイル)名は仮

ディレクトリの説明

以下ソースコードでは、 src/App というalias を設定している。

また前述の通り おそらく、本来の Atomic Design 考え方からは外れてしまっている という点に留意。

entries

Webpack のエントリーポイントが配置される。

エントリーポイントでは Vuetify.js の読み込みと、Pages コンポーネントの render を行っている。

例) src/entries/main.js

import Vue from 'vue';
import Page from 'App/Components/Pages/Main.vue';
import Vuetify from 'vuetify';
import 'vuetify/dist/vuetify.css';

Vue.use(Vuetify);

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

Pages

「このページはヘッダがあって、フッタがあって、このコンテンツがある」といった具合にページ全体の構成を表す。

ヘッダやフッタ、コンテンツにあたる部分が Templates コンポーネントとなり、これを配置する。

例)src/Components/Pages/Main.vue

<template>
  <v-app id="inspire" v-cloak>
    <my-header></my-header>
    <my-content></my-content>
    <my-footer></my-footer>
  </v-app>
</template>

<style>
  [v-cloak] {
    display: none;
  }
</style>

<script>
    import Header from 'App/Components/Templates/Header.vue';
    import Footer from 'App/Components/Templates/Footer.vue';
    import Content from 'App/Components/Templates/Contents/Main.vue';

    export default {
        components: {
            'my-header': Header,
            'my-content': Content,
            'my-footer': Footer
        }
        // ページ全体に及ぶロジックを記述
    };
</script>

Templates

共通のヘッダやフッダ、各ページのメインコンテンツにあたる部分など、ページを構成する各要素。

各ページのメインコンテンツにあたるコンポーネントは Contents/ というディレクトリを作成し、その下に置いた。

Organisms コンポーネントを、グリッドシステムに基づいて配置する。

例)src/Components/Templates/Contents/Main.vue

<template>
  <v-content>
    <v-container grid-list-lg>

      <v-layout row>
        <v-flex md12>
          <my-chart
            :chart-data="chartData"
          ></my-chart>
        </v-flex>
      </v-layout>

      <v-layout row>
        <v-flex md4>
          <my-table
            :table-items="tableItems"
          >
          </my-table>
        </v-flex>

        <v-flex md8>
          <v-layout row>

            <v-flex md6>
              <my-form
                v-model="formValue"
              >
              </my-form>
            </v-flex>

            <v-flex md6>
              <my-card
                :card-text="cardText"
              >
              </my-card>
            </v-flex>

          </v-layout>
        </v-flex>
      </v-layout>

    </v-container>
  </v-content>
</template>

<script>
    import MyCard from 'App/Components/Organisms/ACard.vue';
    import MyChart from 'App/Components/Organisms/AChart.vue';
    import MyForm from 'App/Components/Organisms/AForm.vue';
    import MyTable from 'App/Components/Organisms/ATable.vue';

    export default {
        components: {
            'my-chart': MyChart,
            'my-table': MyTable,
            'my-form': MyForm,
            'my-card': MyCard
        },
        data: () => ({
            chartData: [],
            tableItems: [],
            formValue: {},
            cardText: ''
        })
        // ロジックを記述
    };
</script>

Organisms

特定のフォームや表など、機能を持つ個別の部品。

ルート要素を <v-layout> にすることで Templates 内で利用する際に、グリッドシステムの恩恵を受けられる。

v-layout > v-flex の構造をネストさせてもレイアウトに影響しないよう)

Molecules を組み合わせて作る。

Vuetify.js が提供しているコンポーネントがその Molecules にあたる。

(下の例では v-data-table

例)src/Components/Organisms/ATable.vue

<template>
  <v-layout row>
    <v-flex md12>
      <h2 class="ml-3 mb-2">表1</h2>
      <v-data-table
        :headers="headers"
        :items="tableItems"
        hide-actions
        class="elevation-1"
      >
        <template slot="items" slot-scope="props">
          <td class="text-xs-center">{{ value1 }}</td>
          <td class="text-xs-center">{{ value2 }}</td>
          <td class="text-xs-center">{{ value3 }}</td>
        </template>
      </v-data-table>
    </v-flex>
  </v-layout>
</template>

<script>
    export default {
        props: {
            tableItems: {
                type: Array,
                required: true
            }
        },
        data: () => ({
            headers: [
                {
                    text: '要素1',
                    value: 'value1'
                },
                {
                    text: '要素2',
                    value: 'value2'
                },
                {
                    text: '要素3',
                    value: 'value3'
                }
            ]
        })
    }
</script>

Molecules / Atoms について

原則、 Vuetify.js が提供するコンポーネントがこれらに該当するので今回は自分では作成しなかった。

他のライブラリを用いるなどして部品を作る際には Molecules を作るかもしれない。

感想

今回は慣れないフロントエンド周りの設定・実装方針策定を1からやったので、いろいろ試行錯誤しながら進めた。

Webpack の設定含めて迷うことも多かったが、一旦納得行く形には落ち着いた(Atomic Design をきちんと取り込めていないという問題はあるが、現時点でこのプロジェクト内での破綻は起きていない)。

システムが肥大化するに従って新たな問題は出てくるとは思うが、そのときにきちんと対応できるようにフロントエンドの設計思想を学んでいきたい。

*1:http://atomicdesign.bradfrost.com/chapter-2/ を参照

*2:今回の例で言えば、例えば Pages と Templates の関係が逆なような気がしている。

Atomic Design にあわせたコンポーネント分割のしかたは次のリポジトリが参考になるとのこと(ただし React の例)。

github.com

Laravel 5.4 におけるユーザ権限管理の実装に関する考察(途中まで)

追記:2018/01/27

続きのエントリ。

Laravel が提供している機能で実現できることが分かったので、結論だけ知りたい人はこちらを読めば OK。

blog.okashoi.net

-- 追記ここまで --

背景

業務において、同じチームのメンバーがプルリクエストを WIP で出し「こんな方法でいいか?」という旨の相談を投げかけてきた。

それについて議論になったことがあるので、それについて書いておく。

そのときの論点は大きく分けては 2 つあったのだが、今回はその 1 つについて書く。 もう 1 つは気が向いたときに書くかもしれない。

※以下はあくまで私個人の意見であり、「こうすべき」と言っているのではないことに留意。意見・ツッコミ等は歓迎。

実現したいこと

前提:Laravel 5.4 で開発している web システム

ユーザごとに「このデータは編集できる」「このデータは閲覧のみ可」といった権限管理をしたい。

登場するテーブルは以下の2つ。

(テーブル名は実際のものとは異なる。あくまでイメージ)

  • users
    • ユーザのテーブル。後述する groups テーブルの id を外部キーとして持つ。
  • groups
    • 権限グループのマスタテーブル。id と「閲覧のみ」「管理者」といった表示用の名前 name のみを格納。

プルリクの中身

いろいろ端折っており、あくまでイメージ。

実際にはもう少し細かい条件分岐などがある。

論点は「Model 内に Controller のアクションのリストが存在する」という点である。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Group extends Model
{
    protected $fillable = [
        'id',
        'name',
    ];

    /**
     * 権限リストを取得
     *
     * @return array
     */
    public function getPermissions()
    {
        //----------
        // 閲覧のみ
        //----------
        if ($this->id === 1) {
            $permissions = [
                'UserController@index'   => true,
                'UserController@update' => false,
                'UserController@delete'   => false,
            ];
        }

        // 以下略

        return $permissions;
    }

}

この Model を介して、各 Controller のアクションが実行できるのかを判定する。

私の意見

MVC 各層を次のような階層関係として捉える。

View
| ↓
|  Controller
↓ ↓
Model

※矢印は「呼び出し元→呼び出し先」という関係

このとき前述の「Model 内に Controller のアクションのリストが存在する」という状態はすなわち「呼び出される側が呼び出し元について知っている」ことになり Group モデル が「知りすぎている」状態になっている。

実装例

ユーザを弾くかどうかの処理は View → Controller までの間(Controller を含む)の責務であり、Laravel においては Middleware がそれにあたる。

権限グループごとにアクセスを弾く Middleware を作成し、routes にてアクションごとに必要な Middleware を設定してあげればよい。

<?php

namespace App\Http\Middleware;

class DenyReadonlyUser
{
    /**
     * 「閲覧のみ」ユーザがアクセスしたとき 403 エラーを返す
     */
    public function handle($request, \Closure $next)
    {
        if (\Auth::user()->group_id === 1) {
            abort(403);
        }

        return $next($request);
    }
}

(Http\Kernel.php の設定は省略)

routes/web.php

Route::get('/users', 'UserController@index');
Route::post('/users/{id}', 'UserController@update')->middleware('deny.readonly');
Route::delete('/users/{id}', 'UserController@delete')->middleware('deny.readonly');

※ただし、この方法ではブラックリスト方式になってしまうので安全ではないという懸念がある。

考察……はまた今度

本当はそれぞれのメリット・デメリットを比較しようと思ったのだが、このあと私用で出かけてしまいアドベントカレンダーの期限に間に合いそうになくなるので、いったんここまで。

残りの考察についてはまた別途書きたいと思う。

続きのエントリ(2018/01/27 追記)

Laravel が提供している機能で実現できることが分かったので、上記の考察はせずに Laravel の機能を説明した。

okashoi.hatenablog.com