CYDAS Developer's Blog

サイダス技術者ブログ

【Go言語】 構造体、スライス、マップ、ポインターの組み合わせで割とつまずく

こんにちは。もうすぐ終了するGoogle+に未だに投稿するよく訓練されたGoogle信者の長谷川です。

今回はGo言語の話。Go言語は最近では珍しいポインターがある言語です。
ポインターというとC言語初心者がつまずく鬼門とされて忌み嫌われてる感がありますが、メモリの使用量を抑えて高速化するにはやっぱりポインターがあると有効です。
そうはいってもちょくちょくポインター関連でやらかします。特にスライス、マップ、構造体が絡むとよく失敗するんでここで書き留めておきます。

配列は実体、スライスはポインター

Goでは配列とスライスがありますが扱いが異なります。
配列は値がコピーされますが、スライスはポインターとして扱われます。

package main
import "fmt"

func main(){
    ar := [3]string{}
    sl := make([]string, 3)
    mp := make(map[int]string, 3)

    setFunc(ar, sl, mp)
    fmt.Println(ar) // [  ]
    fmt.Println(sl) // [star  ]
    fmt.Println(mp) // map[0:sun]
}

func setFunc(ar [3]string, sl []string, mp map[int]string) {
    ar[0] = "moon"
    sl[0] = "star"
    mp[0] = "sun"
}

ついでにmapも比較してます。
スライス、マップは変更内容が反映されてますが、配列は変更内容が失われます。

スライスはポインターだけではない

しかし、スライスはポインターとしてアドレスを持っていますが長さとキャパシティーの要素も持っており、これらは変更が反映されません。

// 省略
func main() {
    sl := make([]string, 3)
    sl[0] = "uno"
    fmt.Println(len(sl), cap(sl), sl) // 3 3 [uno  ]

    setFunc(sl)
    fmt.Println(len(sl), cap(sl), sl) // 3 3 [uno due tre]
}

func setFunc(sl []string) {
    sl[1] = "due"
    sl = append(sl, "quatro") // appendは反映されない
    sl[2] = "tre"

    fmt.Println(len(sl), cap(sl), sl) // 4 6 [uno due tre quatro]
}

関数内でappendしてスライスを拡張してますがその内容は失われています。

for文で取得した要素はコピー

あと構造体のスライス操作しようとしてやってしまうんですが、for文で取得した値はコピーなんで反映しません。 ポインター関係ないですね。

// 省略
type Person struct {
    name string
    age  int
}

func main() {
    persons := make([]Person, 3)
    setFunc(persons)
    fmt.Println(persons) // [{ 0} { 0} { 0}]
}

func setFunc(persons []Person) {
    for _, p := range persons {
        p.name = "fool"
        p.age = 1
    }
  // そもそもスライスには反映しない
    fmt.Println(persons) // [{ 0} { 0} { 0}]
}

インデックス指定して操作すればいいんですけどね。

for i := range persons {
  persons[i].name = "fool"
  persons[i].age = 1
}

mapではキー指定で操作できない

しかし、これもmapだとできません。

// 省略
func main() {
    persons := map[int]Person{
        1: Person{},
        2: Person{},
        3: Person{},
    }
    setFunc(persons)
    fmt.Println(persons)
}

func setFunc(persons map[int]Person) {
    for key := range persons {
        // ここがエラーになる
        persons[key].name = "fool" // cannot assign to struct field persons[key].name in map
        persons[key].age = 1 // cannot assign to struct field persons[key].age in map
    }
}

他の言語の連想配列みたいにkeyで指定してメンバーにアクセスしようとするとエラーになってコンパイルが通りません。
下のように一旦コピーした構造体にセットしてmapに戻すという方法もありますが、

for key, p := range persons {
  p.name = "fool"
  p.age = 1
  persons[key] = p
}

操作するならそもそもポインターのmapのほうが間違えないし処理的にも負荷が少なくなります。

func main() {
    persons := map[int]*Person{
        1: &Person{},
        2: &Person{},
        3: &Person{},
    }
    setFunc(persons)
    fmt.Println(persons[1]) // &{fool 1}
    fmt.Println(persons[2]) // &{fool 2}
    fmt.Println(persons[3]) // &{fool 3}
}

func setFunc(persons map[int]*Person) {
    for k, p := range persons {
        p.name = "fool"
        p.age = k
    }
}

それならスライスのほうもポインターのスライスにしたほうがいいんじゃないかという気がします。
大きい構造体ならいちいちコピーしないで操作できたほうがいいですよね。

それではよいGoライフを!

【AWS SAA合格への道】RDS

AWS ソリューションアーキテクト - アソシエイト合格に必要な知識をまとめていきたいと思います!
今回は「RDS」になります。

RDS(Relational Database Service )

フルマネージドなデータベースサービス

  • OSへログインして操作することはできない
  • IPアドレスを固定することはできない(DNS名による接続)

マルチAZ

ハードウェア障害などが発生した場合自動的にフェイルオーバーし、耐久性の向上や可用性の向上を実現できる。

f:id:aym413:20190313231558p:plain
マルチAZ配置のイメージ

  • マスターとスレーブ間は同期レプリケーション が行われている(マスターはスレーブにデータ更新が反映されるのを待つ)
  • 2台分の料金がかかるので、費用が高くなる
  • フェイルオーバー時、エンドポイントの切り替えは不要
  • スレーブ側のDBインスタンスにはアクセス不可
  • バックアップの取得はスレーブ側から行われるので読み書き操作は停止しない
フェイルオーバーが発生タイミングは以下の3つ
  • インスタンスやハードウェア障害
  • パッチ適用などのメンテナンス時間
  • 手動リブート時に強制フェールオーバーするよう設定していた場合

リードレプリカ

読み込み専用のデータベース

f:id:aym413:20190313233112p:plain
リードレプリカの作成イメージ

  • マスターとリードレプリカ間は非同期レプリケーションが行われている(マスターはスレーブにデータ更新が反映されるのを待たない)
  • 参照が多いアプリケーションの場合に使用することでパフォーマンスが向上する

バックアップ

RDSの標準機能により、自動で1日1回フルバックアップ+トランザクションログを取得できる

  • 自動バックアップにより、ポイントインタイムリカバリが利用できる
  • 任意のタイミングでバックアップを取得することも可能
  • マルチAZに比べ、安価にデータ復旧が可能

ポイントインタイムリカバリ

自動バックアップにより任意の時点までRDSを復元することができる

  • マネジメントコンソールの「最新の復元時間」として表示されている時刻まで復元可能

その他のDBサービス

DynamoDB

NoSQLのデータベースサービス

利用用途

  • メタデータの保存
  • JSON ドキュメントの保存
  • セッション情報

Redshift

データウェアハウスサービス

利用用途

  • 大量のデータの集計と分析

ElastiCache

インメモリキャッシュサービス

利用用途

  • セッション情報
  • キャッシュ

【AWS SAA合格への道】VPC

AWS ソリューションアーキテクト - アソシエイト合格に必要な知識をまとめていきたいと思います!
今回は「VPC」になります。

VPC(Virtual Private Cloud)

AWS上に仮想ネットワークを構築できるサービス

f:id:aym413:20190305172237p:plain
VPC作成イメージ

  • 単一リージョン内に作成する(リージョンをまたいで作成することはできない)
  • 他のVPCとは論理的に切り離されている
  • VPCのサイズ(CIDRブロック)は変更不可

ネットワーク

サブネット

VPCをさらに分割したネットワーク空間

f:id:aym413:20190306204318p:plain
サブネット作成イメージ

  • 単一AZ内に作成する(AZをまたいで作成することはできない)
  • サブネット内で利用できるIPアドレスは最初4つ最後1つのアドレス(合計5つ)はAWS側で予約されているため、使用不可
  • サブネットのサイズ(CIDRブロック)は変更不可
  • サブネットは1つのルートテーブルに紐付ける必要がある(複数のルートテーブルに紐付けることはできない)

パブリックサブネットとプライベートサブネットの違い

サブネットを作成する際の設定として、
「パブリックサブネットを作成する」や「プライベートサブネットを作成する」といったものはありません!
パブリックサブネットなのか、プライベートサブネットなのかを判断するのはサブネットに紐付くルートテーブルによって変化します。
詳細は「ルートテーブル」にて説明します。

インターネットゲートウェイ

VPC内のEC2とインターネット間の通信を可能にする

f:id:aym413:20190306205024p:plain
インターネットゲートウェイ作成イメージ

  • インターネットゲートウェイはVPCに紐付ける必要がある
  • ルートテーブルにインターネットゲートウェイのルートを追加する必要がある

ルートテーブル

ネットワークトラフィックの経路を判断する際に使用される

  • VPCを作成するとルートテーブルが1つ作成される(メインルートテーブル)
  • メインルートテーブルはサブネットを特定のルートテーブルに明示的に関連付けなかった場合に、暗示的に関連付けられる
  • ルートテーブルは追加で作成することができる(カスタムルートテーブル)
  • 同一VPC内の通信はデフォルトで許可されている

f:id:aym413:20190307232242p:plain
パブリックサブネットとプライベートサブネットの違い

この図が示す「10.0.0.0/16:local」はVPC作成時に指定したVPCのネットワーク範囲を表しています。 つまり、VPC内で行われる通信はこの「10.0.0.0/16:local」を使用することになります。

「0.0.0.0/0 : インターネットゲートウェイ」はデフォルトルートと呼ばれ、全ての経路を示します。

  • 0.0.0.0/0 : インターネットゲートウェイを持つルートテーブルが紐づいたサブネット
     →パブリックサブネット
  • 0.0.0.0/0 : インターネットゲートウェイを持たないルートテーブルが紐づいたサブネット
     →プライベートサブネット

VPC内のEC2とインターネット間の通信ができるための条件

  • インターネットゲートウェイがVPCと紐付けられていること
  • ルートテーブルにインターネットゲートウェイへのルートが含まれていること
  • VPC に関連付けられているセキュリティグループが、インターネット間とのトラフィックを許可していること
  • VPC 内のEC2は、パブリック IP アドレス または Elastic IP アドレスのいずれかを持っていること

NATゲートウェイ

プライベートサブネットに配置されたEC2からインターネットへ通信(アウトバウンド)は許可したいが インターネットからの通信(インバウンド)は許可したくない場合に利用

f:id:aym413:20190307232333p:plain
NATゲートウェイ作成イメージ

  • NATゲートウェイはパプリックサブネットに配置
  • プライベートサブネットに紐付くルートテーブルにNATゲートウェイを追加

セキュリティ

ネットワークACL

サブネットに紐付くファイアウォール

  • デフォルトで全ての通信は許可されている
  • 戻りの通信も明示的に許可する必要がある(ステートレス)
  • 記載された番号の小さい順にルールが適用される

セキュリティグループとの違い

f:id:aym413:20190307231531p:plain
ネットワークACLとセキュリティグループの違い

セキュリティグループの特徴についてはこちらの記事でも説明しています。 tech.cydas.com

VPC Flow Logs

VPC内のIPトラフィックの監視が行える

PHP5系 から 7系に上がって出来なくなること

こんにちは。技術部の金尾です。
PHPといえば良くも悪くもゆるい書き方を許してくれる大らかな言語ですが、7系以降では お行儀のわるい書き方には(以前よりは)厳しくなりました。
廃止されたものやルールが変わった部分もあるのでまとめておきます。

split関数の廃止

split関数が廃止されました。以降はpreg_splitexplodeを使う必要があります。

<?php
// 呼び出しても undefined
var_dump( split(',', '1,2,3,4') );
//=> Fatal error: Uncaught Error: Call to undefined function split() in ...

// explode などに置き換えましょう。
var_dump( explode(',', '1,2,3, 4' ) );
//=> array(4) {
  [0]=>
  string(1) "1"
  [1]=>
  string(1) "2"
  [2]=>
  string(1) "3"
  [3]=>
  string(2) " 4"
}

E_STRICT 通知の深刻度の変更

PHP7以降から E_STRICT 通知が廃止されました。それに伴い今まで E_STRICT だったものは E_NOTICE E_WARNING E_DEPRECATED のいずれかに変更になります。E_ALL & ~E_STRICT でひとまず非表示にしておく、ということができなくなったのでその場合対応が必要です。
いくつかある変更対象のエラーのなかで、比較的うっかりやりがちな Signature mismatch during inheritanceOnly variables should be passed by reference について取り上げます。

Signature mismatch during inheritance

オーバーライドしたメソッドのシグネチャが継承元と異なる場合、E_WARNING エラーが発生します。

<?php
class Hoge {
    public function method(){
        return 'hoge';
    }
}

class ChildHoge extends Hoge {
    // 親の method とは異なる引数を指定してオーバーライド
    public function method($foo){
        return 'hoge' . $foo;
    }
}
//=> Warning:  Declaration of ChildHoge::method($foo) should be compatible with Hoge::method() in ...

Only variables should be passed by reference

引数に参照渡しを要求している関数にメソッドの戻り値を直接渡すと E_WARNING エラーが発生します。 array_poparray_shiftなどを使用する際には注意が必要です。

<?php
// 関数の戻り値を直接渡しては×
var_dump(array_pop( explode(',', '1,2,3,4') ) );
//=> Notice:  Only variables should be passed by reference in ...

// 一度変数に格納すればok
$hoge = explode(',', '1,2,3,4');
var_dump(array_pop($hoge));
//=> string(1) "4"

変数やプロパティ、メソッドへの間接的なアクセスの扱いの変更

$$foo['bar']$foo->$bar['baz'] といった、
変数やプロパティ、メソッドへの間接的なアクセスを厳密に左から右の順で評価するようになりました.

PHP5 PHP7
$$foo['bar']['baz'] ${$foo['bar']['baz']} ($$foo)['bar']['baz']
$foo->$bar['baz'] $foo->{$bar['baz']} ($foo->$bar)['baz']
$foo->$bar'baz' $foo->{$bar['baz']}() ($foo->$bar)'baz'
Foo::$bar'baz' Foo::{$bar['baz']}() (Foo::$bar)'baz'

このため可変変数や可変メソッドを使用するときには5.x系とは異なる挙動をすることがあります。

<?php
class Foo {
    public function bar(){
        echo "bar";
    }
    public function baz(){
        echo "baz";
    }
}
$arr = ['bar' => 'bar', 'baz' => 'baz'];

// PHP5.x系では $arr['bar'] が先に評価されていたが、
// PHP7.x系では (new Hoge)->$arr が先に評価されるためとエラーになる。
(new Foo)->$arr['bar']();
//=> Notice:  Array to string conversion in ...
//=> Notice: Undefined property: Foo::$Array in ...

// 波括弧を使用して $arr['bar'] を先に評価するよう指定すればok
(new Foo)->{$arr['bar']}();
//=> "bar"

無効な文字列による算術演算の通知

PHP7.1以降、数値形式ではない文字列を使って算術演算を行おうとしたときに E_WARNING あるいは E_NOTICE エラーが発生するようになりました。キャストしてくれることをいいことにうっかり空文字を0として扱ってしまうとバンバン Warning エラーが出ます。

  • E_NOTICE になるケース 数字で始まり、数値以外で終わる文字列のときに発生
    Notice: A non well formed numeric value encountered in ...

  • E_WARNING になるケース 数値が含まれない文字列のときに発生
    Warning: A non-numeric value encountered in ...

<?php
var_dump("0" + 1);
//=> int(1)

var_dump("01" + 1);
//=> int(2)

var_dump("" + 1);
//=> Warning:  A non-numeric value encountered in ...

var_dump("1m" + 1);
//=> Notice:  A non well formed numeric value encountered in ...

var_dump("m1" + 1);
//=> Warning:  A non-numeric value encountered in ...

var_dump("1m" + ""); 
//=> Notice:  A non well formed numeric value encountered in ... 
//=> Warning:  A non-numeric value encountered in ...

【AWS SAA合格への道】EC2

AWS ソリューションアーキテクト - アソシエイト合格に必要な知識をまとめていきたいと思います!
今回は「EC2」になります。

EC2(Elastic Compute Cloud)

AWS上で動く仮想サーバー

EC2の料金体系

オンデマンドインスタンス

EC2を起動している時間だけ料金が発生する(従量課金方式

利用用途

  • 前払いや長期間の契約なしで、EC2の低コストや柔軟性を利用したいと考えているユーザー
  • 短期間、スパイクを伴う、または予測不能な作業負荷があっても中断できないアプリケーション
  • EC2で開発またはテストするアプリケーション

リザーブドインスタンス

1年・3年の年単位でEC2を契約することで、オンデマンドより割引が受けられる(長期契約方式

利用用途

  • 定常的に使用するアプリケーション
  • キャパシティーを予約する必要があるアプリケーション
  • コンピューティングの合計コストを削減するために、EC2 の 1~3 年間の使用を確約できるお客様

スポットインスタンス

オークション形式で使用されていないEC2を入札し、自分が入札した価格より高く入札したユーザーが出たときなどに
使えなくなる代わりに安い値段で利用できる(入札方式

利用用途

  • 開始および終了時間が柔軟なアプリケーション
  • 非常に低額のコンピュート価格でのみ実行可能なアプリケーション
  • 大容量の追加キャパシティーのために、緊急のコンピューティングニーズを有するユーザー

Dedicated Hosts

物理ホストサーバーを専有し、その上でEC2を起動できる

  • Windows Server、SQL Server、SUSE Enterprise Server などのソフトウェアライセンスを使用できる(BYOL)
  • 物理ホスト単位で課金(物理ホストの上で動くEC2に対しては課金されない)
  • 管理機能が豊富でインスタンスの詳細のコントロール、リソースの可視化、CPUコアやソケットレベルの管理が可能

利用用途

  • 専有サーバーのソケット数または物理コア数に応じたライセンス数が必要な場合
  • コンプライアンスや規制要件

ハードウェア専用インスタンス

物理ホストサーバーを専有し、その上でEC2を起動できる

  • 一部のライセンスのみサポート
  • インスタンス単位で課金

利用用途

  • 専有サーバーのソケット数または物理コア数に応じたライセンスは必要ない場合
  • コンプライアンスや規制要件

プレイスメントグループ

インスタンス間における低レイテンシな通信実現するための機能

クラスタプレイスメントグループ

単一AZ内のインスタンスを論理的にグループ化したもの

  • EC2間で低ネットワークレイテンシー、高ネットワークスループットが発生する場合に最適
  • 複数のAZにまたがることはできない

パーティションプレイスメントグループ

論理的なパーティションに分散されているインスタンスのグループ

  • EC2で実行する HDFS、HBase、Cassandra といった分散され、レプリケートされた大規模なワークロードに最適
  • 複数のAZに分散させることが可能

スプレッドプレイスメントグループ

それぞれ異なるハードウェアに配置されるインスタンスのグループ

  • 少数のEC2が互いに分離して保持される必要があるアプリケーションに最適
  • 複数のAZにまたがることが可能

セキュリティ

責任共有モデル

f:id:aym413:20190227213618p:plain
AWSが提唱する責任共有モデル

【AWSの責任範囲】

AWS クラウドで提供される インフラストラクチャーの保護 に責任を負います。
インフラストラクチャーは AWSが持つハードウェア、ソフトウェア、ネットワーク、設備 を指します。

  • 物理的な施設や設備、セキュリティ
  • ハイパーバイザー
  • ネットワークインフラ
  • 仮想インフラ

【利用者の責任範囲】

  • OSやミドルウェアの管理 (更新やセキュリティパッチを含む)
  • インスタンスにインストールしたアプリケーションソフトウェアの管理
  • セキュリティグループの管理
  • 適切なネットワークの設定
  • データの暗号化
  • アカウントの管理

セキュリティグループ

EC2へのトラフィックを制限するファイアーウォールの機能

  • デフォルトでは全てのインバウンドトラフィックが遮断されている
  • 接続を許可するIPアドレス または セキュリティグループをインバウンドやアウトバウンドのルール適用する(ホワイトリスト型
  • アウトバウンド または インバウンドで許可した通信の戻りのトラフィックについては、許可をしなくても受信できる(ステートフル
  • 全てのルールが適用される
  • ルールの変更はすぐに反映される

ネットワークACLとの違いに注意!

ネットワーク

EIP

EC2に割り当てる固定のパブリックIP

  • 停止/起動しても割り当てはそのまま
  • EC2を停止していると課金が発生する

設定

メタデータ

インスタンスの起動時にコマンドを渡すことができる

ユーザーデータ

インスタンスのAMI、パブリックIP、ホスト名など、EC2の各種情報を取得することができる

ストレージ

インスタンスストア

ホストコンピュータにーに内臓されている 揮発性 ストレージ

f:id:aym413:20190227231210p:plain
インスタンスストア のイメージ

  • 揮発性のため、EC2を停止/起動すると インスタンスストア上のデータは削除 される
  • ただし、再起動では削除されない → 再起動では物理ホストが変わらないため
  • 内臓ディスクのため、高速なI/Oが可能
  • インスタンスストレージやエフェメラルストレージと呼ばれることもある
  • 利用用途: 一時的な計算処理やキャッシュデータ、高速なI/Oが必要な場合など

EBS(Elastic Block Store)

EC2とは独立して管理される 永続的な ストレージ

f:id:aym413:20190227231411p:plain
EBSのイメージ

  • EC2とは独立しているため、EC2を停止/起動してもデータは保持されたまま
  • 任意のAZ(アベイラビリティゾーン)に作成可能
  • スナップショット(バックアップ)を取得すれば、他のAZにEBSを作成可能
  • スナップショットは初回フルバックアップ、その後は差分のみ取得される

EBS最適化オプション

通常のネットワークとは別にEBS専用のネットワーク帯域を確保し、ディスクパフォーマンスを安定させるオプション

f:id:aym413:20190227233656j:plain
EBS最適化オプションのイメージ

EFS(Elastic File System)

複数AZの最大数千のEC2から同時接続可能なファイルストレージサービス(概要レベルでOK)

バックアップ

AMI(Amazon Machine Image)

EBSの中のデータ(スナップショット) とEC2を構成する管理情報を含む起動テンプレート

  • AMIを取得することで所有するEC2のバックアップが取れる
  • 既に公開されているAMIを使用してEC2を起動することも可能

【イベントレポート】JAWS DAYS 2019 に参加してきました!

f:id:aym413:20190228110320j:plain こんにちは!エンジニアのこぶかた@kobuuukata)です!

2019年2月23日(土) 五反田TOCメッセで行われた JAWS DAYS 2019 に参加してきました!
今年は来場者がなんと! 1900人!?
申し込み時点では上限の2300人を超えて、キャンセル待ちも出ていたようです・・すごい!

そんな中、サイダスも企業スポンサーとして参加!

f:id:aym413:20190228090150j:plain
サイダス見つけられましたか?

ボランティアスタッフとしての活動

私は当日ボランティアスタッフとして参加してきました!
ボランティアスタッフでも色々と役割があるのですが、
今回はセッショントラックと懇親会補佐を担当してました。

ボランティアスタッフは以前もやったことがあるのですが、
セッショントラックのお手伝いは初めてでした。

セッショントラックのスタッフとして主にやったこと

  • 参加者に詰めて座ってもらうように誘導
  • 各セッションの登壇者の名前とセッションタイトルの確認
  • 各セッション開始・終了時のアナウンス
  • タイムキーパー
  • 随時、必要なアナウンスを行う(忘れ物やランチの案内など)

これを2名で分担してやります。

セッショントラックのスタッフをやってみて

  • 役割上、一番前の席で聞けるというのは特権!

  • でも、本当に見たいセッションはスタッフのシフトを入れないようにする

スタッフのシフトを決める時、自分の聞きたいセッションを選んでいいよ
と言われて自分のシフトを組んでみたのですが、

セッション中は 時間を気にしなければいけなかったり・・
次のセッションを確認したり・・
何か連絡がきていないかSlackを確認したり・・
直前まで登壇者が現れず、ヒヤヒヤすることもしばしば・・

セッショントラックのスタッフをやっていると
なかなかセッションに集中することができませんでした。
(初めてだからというのもあるかもしれないですが)

なので、本当に聞きたい!というセッションがあれば
スタッフのシフトを入れない方がいいのかなと思いました。

  • すごい緊張する(笑)

ちょっと始めに挨拶するだけだからと思っていたのですが、
大勢のお客さんを前にすると緊張して
セッションタイトルをカミカミになってしまったことも・・登壇者のみなさんすみません!(泣)
次回の教訓としては、
自分が担当するセッションはわかっているので、
事前に練習しておくと本番落ち着いてできそうです!

  • 携帯のモバイルバッテリーは必須!

当日の連絡はSlackで行われ、随時情報が流れてきます。
かなり電池が消耗するので必須です。私は忘れて知り合いに借りました。。

懇親会補佐

懇親会補佐ってなに?って感じですが(笑)、
司会が困った時に助けたり(多分何もできてなかったと思う・・すみません)
いきなりAWS SUMIRAIの授賞式の盾を持ってきてと言われたり
まあそんな感じで、ほとんど私の役目はなかったのですが・・
これまた懇親会スタッフの特権で一番前で見させてもらいました!感謝!!

セッションのまとめ

当日のセッション資料をまとめてくれている方がいます!神!!
聞けなかったセッションはこちらで見てみようと思います。
qiita.com

JAWS DAYSを通じて

私個人としては今回を含め、6回目の参加となりました。
最初は一般参加者として、セッションを聞いて学びを持ち帰るだけでしたが、
これまでJAWS DAYSやJAWS-UGのコミュニティを通じて多くの方に巻き込まれ(たくさんの機会をいただき)、
ボランティアスタッフや登壇、海外コミュニティでのスピーカーなどなど、様々な経験をさせてもらいました。
それが今の私にとってとても大きな価値になっています。
コミュニティに参加することでこんなにも変化していくとは想像もしていませんでした。
そういった方が少しでも増えると嬉しいなと思います。
また来年も何かの形でJAWS DAYSに関わり、いろんな人を巻き込んでいきたいと思います!

そして今回はスタッフとして参加させてもらっていたので、朝から夜までバタバタしていて
久しぶりにお会いできた方もいたのですが、ゆっくりお話できず・・。
またどこかの勉強会でお会いしましょう〜!

f:id:aym413:20190228110033j:plain

【後編】Vue CLI + Laravel によるMSPA (Multi-Single Page Application)

f:id:kfukuyama:20190225131307p:plain

こんにちは、福山健@kenfdev)です!

この記事は 【前編】Vue CLI + Laravel によるMSPA (Multi-Single Page Application) の続きになります。 前編 を読んでいることを前提としているのでご注意ください。

長すぎて読みたくない人へ

コードを見たほうが早い!って人は以下にこの記事に関連するリポジトリを公開しているので遊んでみてください。

github.com

後編でやること

後編ではMSPA(Multi-Single Page Application)の部分に着目します。

  • MSPAについて
  • app2 を作ってみる
  • まとめ

という内容でお届けします。

MSPA(Multi-Single Page Application)とは?

「MSPA MSPA」って言ってますけど、たぶんMSPAという正式な略語は無いと思います。SPA(Single Page Application)と違って、1ページ毎にサーバーがレンダリングするようなアプリケーションをMPA(Multi Page Application)と呼ぶことはあります。でも、この記事で作るものはただのMPAでは無く、 複数のページでそれぞれのSPAがある のでMSPA(Multi-Single Page Application)と呼ばせていただきます。

以下の悩みがある人にはMSPAがフィットするかもしれません。

  • 一つの巨大なSPAを作りたくない。
  • ドメインが明確に分かれている機能間の画面遷移までニュルニュル遷移する必要がないからSPAを分けたい。
  • 複数チームで分かりやすく、SPAを分担して開発したい

でもMSPAを作ろうと思った場合に悩みもあります:

  • 複数のSPA作りたいけど、できればパイプラインは統一したい(configの冗長管理したくない)
  • みんな同じ外部ライブラリ使うのに冗長なpackage管理したくない
  • 安易な共通化は危険。。。と理解しつつも共通利用できるものは複数のSPAから同じオレオレライブラリっぽいところから使いたい

通常であれば↑を真面目に考えないといけないのですが、Vue CLI を使うとこれらを意識せずにいい感じに管理してくれる pages という機能があります。 前編 ではこの機能を既に使わせていただいているのですが、単一のSPAしか作っていないという意味のない状態になっています。後編ではここにもう一つSPAを加えてMSPAにしましょう!

【余談】 上の「悩み」のところにもあるように、安易な共通化は長い目で見たときに負債になる可能性が高くなります。外部ライブラリの共通化も、本気でマイクロサービス化などでアプリを分けた場合には「独立したデプロイ」ができなくなるので、注意が必要です。ただし、それらを理解した上で、いざ別のアプリ(コードベース)に切り出すときには pages 機能でVueのアプリを切り出すのは比較的容易に行えるはずです。

構成

後半の構成は次の通り。Vueのアプリが増えています。MSPAのコンセプトを見せたいだけなので、2つ目のVueアプリは特に処理を入れません。(最初のアプリ同様、API連携は同じように作っていくことができます)

f:id:kfukuyama:20190224205558p:plain
構成

フロントエンド app2 の作成

名前が安易すぎますが、2個めのSPAを app2 とでもしましょう。中身というよりMSPAのコンセプトだけ見せたいので、ベースは app1 から作っちゃいます。

# frontendディレクトリ内で作業します
cd frontend

# app1をapp2としてコピー
cp -r src/app1 src/app2

app2 からはrouter機能を消しちゃいましょう。

rm src/app2/router.js

# 合わせてコンポーネントもApp以外消してしまう
rm src/app2/views/*
rm src/app2/components/*
// src/app2/main.js

import Vue from 'vue'
import App from './App.vue'
// import router from './router'  ←消してしまう

Vue.config.productionTip = false

new Vue({
  // router,  ←消してしまう
  render: h => h(App)
}).$mount('#app')

Vuetifyを適用

このまま app2 を表示しても面白くないので、無駄に Vuetify を使って見た目をドラスティックに変えてみちゃいましょう。まずはVuetifyインストールしましょう。

npm install vuetify

VuetifyをVueで使うように指示します。

import Vue from 'vue'
import App from './App.vue'
import Vuetify from 'vuetify'  // ←ここ
import 'vuetify/dist/vuetify.min.css'  // ←ここ

Vue.use(Vuetify)  // ←ここ

Vue.config.productionTip = false

new Vue({
  render: h => h(App)
}).$mount('#app')

iconも使えるようにしておきたいので、 index.html<head> にiconへのリンクを追加したいのですが、せっかくなので templates/base.html ではなく、Vuetifyアプリ用のテンプレートを用意しましょう。仮に 前編 で作った app1 でも使っている templates/base.html に追加してしまうと、 app1 はMaterial Designのアイコンが必要ないのに取得しにいってしまうという無駄が生じてしまいます。

# もともとのbase.htmlからコピーして作る
cp templates/base.html templates/vuetify-base.html

<head> 内に次の1行を増やしておきましょう。

<link href='https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons' rel="stylesheet">

これでVuetifyが使える状態になっているはずです。でも見た目を何も変えていないので、 App.vue を下の内容にしちゃいましょう。

<template>
  <v-app id="inspire">
    <v-navigation-drawer v-model="drawer"
                         fixed
                         app>
      <v-list dense>
        <v-list-tile @click="onClick">
          <v-list-tile-action>
            <v-icon>home</v-icon>
          </v-list-tile-action>
          <v-list-tile-content>
            <v-list-tile-title>App1</v-list-tile-title>
          </v-list-tile-content>
        </v-list-tile>
      </v-list>
    </v-navigation-drawer>
    <v-toolbar color="indigo"
               dark
               fixed
               app>
      <v-toolbar-side-icon @click.stop="drawer = !drawer"></v-toolbar-side-icon>
      <v-toolbar-title>Application</v-toolbar-title>
    </v-toolbar>
    <v-content>
      <v-container fluid
                   fill-height>
        <v-layout justify-center
                  align-center>
          <v-flex text-xs-center>
            <v-tooltip left>
              <v-btn slot="activator"
                     :href="source"
                     icon
                     large
                     target="_blank">
                <v-icon large>code</v-icon>
              </v-btn>
              <span>Source</span>
            </v-tooltip>
          </v-flex>
        </v-layout>
      </v-container>
    </v-content>
    <v-footer color="indigo"
              app>
    </v-footer>
  </v-app>
</template>

<script>
export default {
  data: () => ({
    drawer: null
  }),
  props: {
    source: String
  },
  methods: {
    onClick() {
      window.location.href = '/app1';
    }
  }

};
</script>

vue.config.jsの設定

app2 を作ったところで、 vue.config.js でちゃんとビルドができるように設定しましょう。 ほとんど app1 と同じ内容になるだけです( templateが違うので注意! )。

...
    pages: {
        app1: {
            entry: 'src/app1/main.js',
            template: 'templates/base.html',
            filename: `../../resources/views/spa/app1.blade.php`,
        },
        app2: {
            entry: 'src/app2/main.js',
            template: 'templates/vuetify-base.html',
            filename: `../../resources/views/spa/app2.blade.php`,
        },
    },
...

フロントエンドのビルド

上までの手順でフロントエンド側は完了です。ビルドしてLaravel側から使えるようにしましょう!

# frontendディレクトリで作業
npm run build

Laravel連携

では、アセットの準備ができたので残りはLaravelが app2 を配信できるようにすることです。前編SpaController を作ってるので、これのメソッドを増やすことと、 Router をつなげればOKです。

SpaControllerは次のようにメソッドを増やします。

<?php
// app/Http/Controllers/SpaController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class SpaController extends Controller
{
    public function app1()
    {
        return view('spa/app1');
    }
    // ↓至ってシンプル
    public function app2()
    {
        return view('spa/app2');
    }
}

そして、ここに http://localhost/app2 のリクエストがフォワードされるように web.php を修正します。

<?php
// routes/web.php

Route::get('/app1{any}', 'SpaController@app1')->where('any', '(/?$|/.*)');
// app2用のルーティングを追加
Route::get('/app2{any}', 'SpaController@app2')->where('any', '(/?$|/.*)');

これで完成です!では ./vessel start していることを確認して http://localhost/app2 にアクセスしてみましょう。

f:id:kfukuyama:20190224212531g:plain

上図のような画面が見えたなら正常にMSPAが完成しています! app2 内ではVuetifyが動いていて、そこから app1 に遷移して、 app1前編 のときと同様に動作しているのがわかります。

まとめ

Laravel Mixをあえて使わずに、Vue CLIとLaravelを連携して、なるべくDX(Developer Experience)を損なわずに開発していく方法について前編・後編と見てきました。「でもLaravel Mixの方が簡単でしょ」って意見もたくさんあると思いますが、あえてフロントをLaravelに依存させない方法で攻めています。

  • バックエンドの言語が変わるかもしれない
  • フロントエンドのコードを別リポジトリで管理したくなるかもしれない
  • Vue CLIに超素晴らしい機能が追加されたけどLaravel Mixが対応するのが遅いかもしれない

など、理由はいくつか思い浮かびます。ただ、この手法をとった場合の「Hot Reload」が効かなくなる問題は結構デメリットとして大きいと思います。不可能なのかどうかという点も含めて引き続きDXを磨いていきたいと思います。