SEEDS Creator's Blog

開発者やデザイナーは要注意?!シーズ社員達がMac OSをCatalinaへアップデートした体験談。

f:id:seeds-std:20191011110224p:plain

はじめまして!WEB事業部の小川です。

新しいMac OS(Catalina)が登場し、シーズの新しい物好きな社員達(自分を含む)はアップデートしてみました。

更新した感想等を書いていこうと思いますので、アップデートを検討されている方の参考になればと思います。

とりあえず Sidecarが便利

iPadユーザーには朗報であったSidecarが便利で、今まで有料のアプリやハードを使わないと出来なかったiPadのサブディスプレイ化が手軽に出来るのはいいですね!

f:id:seeds-std:20191011110625j:plain
シーズの社員も早速使いこなしておりました
※トレーダーではありません

やっぱりあった Catalinaの問題点とは?

特に開発者に問題がありそうだったのが、Catalina + Google Chromeで発生しているであろう問題点を中心に書いていきたいと思います。

1. input type="number" のstepが実際のstepの2倍になる問題

stepの指定はしていませんが、Catalina + Google Chromeの方は増やしていくと2ずつ増えていきます。問題ない場合は1ずつ増えていきます。

※Chrome Canaryでは修正されて?発生しない様子。
Chromeの対応待ちですかね。

2. ヒラギノ角ゴPro/ProNが無くなった?

Catalina + Google Chromeではヒラギノ角ゴPro/ProN(Sans Serif)が明朝体で表示されてしまう問題が発生しています。
Apple公開しているmacOS 10.15 Catalinaの組み込みフォントリストには存在しているので「ヒラギノ角ゴPro/ProNが無くなった」と言うことでは無さそうです。
デザイナーやフロントエンドエンジニア の方は要注意ですね。

f:id:seeds-std:20191011112730p:plainf:id:seeds-std:20191011112737p:plain
左がChrome 右がSafari

これもChromeの対応待ちですかね。。

3.Gitが使えなくなった?問題

Catalinaにして、エディターを開いてみると。。

xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun

こんなエラーが出ていました。 エラー文言でググればすぐ解決法はありました。
こちらのコマンドで対応出来ました。

xcode-select --install

4. zipファイルが解凍出来なくなった問題

f:id:seeds-std:20191011115340p:plain

zipファイルが解凍出来ない問題があるようです。 解凍出来ないパターンとして最初に考えられたのは
・日本語ファイルがダメ?
・暗号化されているとダメ?
・日本語のファイル名が含まれてても文字コードがUTF-8ならOK?
 と言うことが考えられました。

圧縮時の文字コードはUTF-8にしている場合が多いと思いますが、Windowsを想定している場合はSJIS-WINに変更している可能性もありますね。

回避策として

「ファイル名をアルファベットにしておく」が1番安全

と言うことになりそうです。

最後に

セキュリティの面やSidecarが便利すぎる点では更新した方がいいのでしょうが、新しいが故の問題点もあるので、注意は必要ですね。

この手のアップデートは時間がかかります。

かったりーな・・・

最後まで読んでいただきありがとうございました。 小川

AWSソリューションアーキテクトプロフェッショナル(SAP-C01) に合格しました

f:id:seeds-std:20191010165532p:plain

クラウド事業部の原口です。

AWSソリューションアーキテクトプロフェッショナル試験を受けてきて一度不合格となった末に合格しました。
合格点は750/1000点と結構お高め…
難しかったです。 勉強を初めてから1ヶ月半ほどかかりました。

一度不合格となってますし偉そうな事は言えないのですが、こんな勉強をしました、という事を書いてみたいと思います。

試験の概要

AWS 認定ソリューションアーキテクト – プロフェッショナル

AWS プラットフォームでの分散アプリケーションおよびシステムを設計するための
高度な技術的スキルと専門知識を認定します。
AWS でのクラウドアーキテクチャの設計およびデプロイに関する 2 年以上の実務経験を持つ方が対象です。

この試験で評価する能力は次のとおりです。
• AWS における、動的なスケーラビリティ、高可用性、フォールトトレランス、
信頼性を備えたアプリケーションの設計およびデプロイ
• 与えられた要件に基づいてアプリケーションを設計およびデプロイするための、適切な AWSサービスの選択
• AWS における複雑な多層アプリケーションの移行
• AWS におけるエンタープライズ全体のスケーラブルな運用の設計およびデプロイ
• コストコントロール戦略の導入

かなりのボリュームの試験です。
AWS 認定ソリューションアーキテクト – アソシエイト の上位版に位置する問題となります。

試験結果は 100~1000 点の範囲のスコアでレポートされます。最低合格スコアは 750 点です。スコ
アによって、試験での全体的な成績と合否がわかります。スケールドスコアモデルは、難易度にわず
かな違いのある複数の試験形式のスコアを平均化するために使用されます。

100点から?という単純な疑問とスケールドスコアモデルというものがわからなかったので調べてみましたが、
あまりよくわからなかったのですが「難易度が異なる試験問題をいい感じに調整」するモデルと解釈しました。
つまり問題によって配点が違ってくる、という事ですかね?

試験時間は170分。問題数は75問ですので1問あたり2~3分程度で解く必要があります。
試験合格の有効期限は3年みたいです。

その他詳細な試験ガイドは以下となります(公式) 試験ガイド

試験勉強

私はAWSを初めて触ってからもう5年くらいは経っていると思うのですが、
業務で触れるサービスが中心となってしまい、どうしてもよくわからないサービスというのは存在しました。
そのため、いったい自分は何が苦手なのか、を把握する事からはじめました

模擬試験

模擬試験を受けて再学習が必要な領域を特定と試験の感じの把握からはじめました
サンプル問題でもわかりますが、言い回しが独特でとにかく文章が長い、という印象です

公式ドキュメントの「よくある質問」

よくある質問を読み込むのを中心に行いました。
サービスの制限など実運用を検討する方へのドキュメントである為、
サービスのネックや落とし穴となりやす部分が集約されているものと感じました。

ホワイトペーパー

DR関連のものとストレージサービス関連のものはよく読みました。 AWS Well-Architectedなどはななめ読みしたくらいです。

blackbelt

よく知らないサービスはblackbeltを見ました。
スライドをぽちぽちーとしてたんですが、ちゃんと動画で見ると理解度が段違いだったのでできれば動画がいいと思います

AWS クラウドサービス活用資料集 | サービス別資料

心構え

膨大な文章量と170分

とにかく文章が長いので単純に疲弊してきます。
集中力を長持ちさせる自分なりの方法を見つける必要がありました
疲れてくると読んでるだけで頭に入ってない事が多くてそういう時に集中しなおせる方法を持ってるといいです
秘訣について「170分くじけない心が必要」と聞いて笑ってたんですが
本当に重要ではないかと思いました

正常性バイアスとの戦い

疲労してくると「正常正バイアス」がかかりやすくなる気がしました。
問題の選択肢はすべてその問題を解決できるものであったり、もっともらしい書き方をされていて
知識が不足していると簡単に迷います。
その状態で出てくるのが以下のような考え。

・実務ではこんなやり方はしないだろう
・こういう時は枯れた技術で望むべき
・問題文に定義されてないからこういう意味に違いない

これらが出てきた時は大体知識不足が原因で明確な説明ができない以上は知識不足であると自分に言い聞かせました。
わりとここで問題の深読みまでしだすと精神的にもハマるし時間も浪費します。

サービスや機能の特性を単語で関連付ける

ストリーミングデータの処理といえばKinesis。 といった形で、仮想的だけど実務的なRFPからAWSにおける提案例を連想できるようにしました。
実際実務として提案を心がけている事もありこの方法は自分に合った勉強法でした。

短期集中で行う

長いスパンで考えるよりも2週間くらいでがーーっとやった方がよいです

その他

試験にはPSIとピアソンVUEがありますが、可能な限りピアソンVUEでの受験をおすすめします!
PSIでは監督員と遠隔でチャットをしながら、カメラで常時監視されながらの試験となり、
「キーボードから手を離してはいけない」などのプレッシャーが半端なかったです笑

ピアソンVUEでも監督員はもちろん居ますが、口元に手をあてたり、頭を掻いたりしても問題はないので
この点は心理的負担がだいぶ減ります!

感想

正直合格の文字を見た時は喜びよりほっとしました。2,3回確認しました。(笑) 社内での人権ができてよかったです。

自信もつき、試験を通じた勉強を行った事で、より深く!AWSの理解が深まったという実感があります。
これからはプロフェッショナル原口としてよりお客様にピッタリのクラウド最適化した提案ができると思います!
お困りの事がありましたら、是非ご相談下さい!!

DevOpsとかの他の試験も受けていくぞー!って思ってたのですが
本当に疲れたので、とりあえずはアイスボーンをやります。
社内でもどんどんAWS認定の取得者を増やしたいですね。
これから試験を受ける方は頑張って下さい!

宣伝枠

AWSの無料相談会というものを行っています! www.seeds-std.co.jp

弊社ではAWSが大好きなエンジニアを募集しています!じょいなす! recruit.seeds-std.co.jp

Kotlin/JSのAWS Lambda関数を便利にしてみる

f:id:seeds-std:20191010160615p:plain

こんにちは, Web事業部の西村です
私の前回の記事 ではKotlin/JSを用いてLambda関数を書いてみました
しかし, dynamic を利用していたため使いにくい部分もあったと思います
今回はその点を改良しより使いやすくなるよう変更したいと思います

目次

過去の記事

開発環境

この記事では下記の環境で開発を行っています

  • AdoptOpenJDK 1.8.0_222-b10
  • IntelliJ IDEA Community 2019.2.2
  • Kotlin 1.3.50

プロジェクト

前回の記事 で作成したプロジェクトを利用します
プロジェクトの構成は下記のようになっています

KotlinLambda
├─.gradle/
├─.idea/
├─build/
├─gradle/
├─src/
│ └─main/
│   └─kotlin/
│     └─jp.co.seeds_std.lambda.kotlin/
│       └─handler.kt
├─build.gradle.kts
├─compress.zip
├─gradlew
├─gradlew.bat
└─settings.gradle.kts

Jsonを使って便利にしてみる

dynamicをなくす

Kotlin/JSには dynamic に似た Json があります
dynamic はJavaScriptのデータであったり, オブジェクトを格納することができますが, Json はKey-Value形式でのみ格納できるものとなっています
handler関数の引数, eventcontext はどちらもJavaScriptのJsonオブジェクトですのでKotlinの Json に変換できます
また, Callbackの第二引数もJsonオブジェクトを設定するためこちらも変更できます

handler.kt

fun handler(event: dynamic, context: dynamic, callback: (Error?, dynamic) -> Unit)

↑ が ↓ に変更できます

fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit)

このJsonオブジェクトへのアクセスはMapのように利用してアクセスすることができます

event["body"] // こちらか
event.get("body") // こちらでデータの取得ができます

Callbackに渡すのJsonを関数で作れるようにする

API Gatewayでは下記のJsonを処理するようになっています *1

{
    "isBase64Encoded": true|false,
    "statusCode": httpStatusCode,
    "headers": { "headerName": "headerValue", ... },
    "multiValueHeaders": { "headerName": ["headerValue", "headerValue2", ...], ... },
    "body": "..."
}

こちらをもとに引数が5つの関数を作成したいと思います
記述する場所はhandler関数の下になります

handler.kt

fun responseJson(statusCode: Int = 200, body: String = "{}", isBase64Encoded: Boolean = false, headers: Json = json(), multiValueHeaders: Json = json()) = json(
    "statusCode" to statusCode,
    "body" to body,
    "isBase64Encoded" to isBase64Encoded,
    "headers" to headers,
    "multiValueHeaders" to multiValueHeaders
)
  • json() Jsonを作成する関数です/引数は vararg Pair<String, Any?> となっており json() と書くと空のJsonを作ることができます

Callbackに渡すよう変更する

現在handler関数は下記のようになっていると思います

handler.kt

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit) {
    val response: dynamic = object {}
    response["statusCode"] = 200
    response.body = "Hello from Kotlin Lambda!"
    callback(null, response)
}

この関数を先ほど作成した関数を用いた形に変更します

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit) {
    val response = responseJson(body = "Hello from Kotlin Json Lambda!")
    callback(null, response)
}

関数をデプロイする

前回と同様の手順を用いて関数をデプロイします
画面右側の Gradle タブを開き, kotlin_lambda -> Tasks -> other の順に開き, その中の compress をダブルクリックして実行します
ビルドに成功するとプロジェクトのディレクトリに compress.zip というファイルが更新されます
この生成されたファイルをコンソールからアップロードし, 保存します

動作確認

あらかじめ作成しておいたAPI GatewayのURLにアクセスし Hello from Kotlin Json Lambda! と表示されれば成功です

レスポンスをクラスに変更する

レスポンスはクラスを用いても影響が少ないためクラスに置き換えます
リクエストにクラスを利用しない理由については蛇足をご覧ください

まずは responseJson関数 を変更します

handler.kt

fun responseJson(statusCode: Int = 200, body: String = "{}", isBase64Encoded: Boolean = false, headers: Json = json(), multiValueHeaders: Json = json()) = json(
    "statusCode" to statusCode,
    "body" to body,
    "isBase64Encoded" to isBase64Encoded,
    "headers" to headers,
    "multiValueHeaders" to multiValueHeaders
)

data class Response(
    val statusCode: Int = 200,
    val body: String = "{}",
    val isBase64Encoded: Boolean = false,
    val headers: Json = json(),
    val multiValueHeaders: Json = json()
)

続いて, Callbackの引数を変更します

fun handler(event: dynamic, context: dynamic, callback: (Error?, Json?) -> Unit)

fun handler(event: Json, context: Json, callback: (Error?, Response?) -> Unit)

最後にレスポンスを変更します

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json, callback: (Error?, Json?) -> Unit) {
    val response = responseJson(body = "Hello from Kotlin Json Lambda!")
    callback(null, response)
}

@JsExport
@JsName("handler")
fun handler(event: Json, context: Json, callback: (Error?, Response?) -> Unit) {
    val response = Response(body = "Hello from Kotlin Class Lambda!")
    callback(null, response)
}

この変更ができたらデプロイし動作を確認します
Hello from Kotlin Class Lambda! と表示されれば成功です

最後に

いかがでしたでしょうか Kotlinを用いていると dynamic では少し取り扱いにくく感じてしまいます
しかし Json にしてしまうことで,Mapのように利用でき, より使いやすいのではないかと思います
次回は非同期処理にする記事を書きたいと思います
ここまで読んでいただきありございました

Kotlin/JSのAWS Lambda関数でPromiseを使うようにする


蛇足

リクエストのほうもクラス使ったらだめなの?

注意事項はありますが、クラスを利用しても問題はないです

handler.kt

package jp.co.seeds_std.lambda.kotlin

import kotlin.js.Json
import kotlin.js.json

@JsExport
@JsName("handler")
fun handler(event: Event, context: Json, callback: (Error?, Response?) -> Unit) {
    println("body: ${event.body} isBase64Encoded: ${event.isBase64Encoded}")
    // event.copy() // Error!
    val response = Response(body = "Hello from Kotlin Json Lambda!")
    callback(null, response)
}

data class Event(val body: String?, val isBase64Encoded: Boolean)
data class Response(
    val statusCode: Int = 200,
    val body: String = "{}",
    val isBase64Encoded: Boolean = false,
    val headers: Json = json(),
    val multiValueHeaders: Json = json()
)

この時ログに bodyisBase64Encoded の出力が行われます
その後 copy() を実行しようとした場合, 変数 eventEventクラスではなく Json となっていますので, 関数が見つからず実行時にエラーが出てしまいます
また, 注意点として private な変数はトランスパイルした際に変数名が変更されてしまうためうまく利用できません
そのため, リクエストの eventcontext はJsonで, レスポンスはKotlinのクラスを利用していくことがいいのかなと私は思います
蛇足も読んでいただきありございました

Bitbucket PipelinesでDocker Composeプロジェクトをインテグレーションテストする

はじめまして。小國です。

今回は、Docker Compose で構築されたプロジェクトを、Bitbucket Pipelines を使ってインテグレーションテストする方法をご紹介したいと思います。

はじめに

シーズのプロジェクトの開発環境・手法は、主に以下のような構成となっています。

  • VCS に Git を使い、Bitbucket にホスティングしている
  • 各プロジェクトは Docker Compose を使い、ローカル環境を構築し、開発を行っている
  • PHPUnit などを使って、テストケースを書いている

Bitbucket Pipelines で Docker Compose を使うには

Bitbucket には Pipelines という CI/CD サービスがあり、Docker を使ったテストが行なえます。

ですが、Pipelines で Docker Compose を使うためには、自身で Docker Compose バイナリを作成しなければなりません。

参考: https://ja.confluence.atlassian.com/bitbucket/run-docker-commands-in-bitbucket-pipelines-879254331.html

今回は、Docker Compose バイナリの作成・公開し、サンプルプロジェクトを使って、Pipelines で PHPUnit のテストを実行するまでを行います。

やってみましょう

主な流れは以下のようになります。

  1. Docker Compose バイナリの作成と、作成したバイナリを Docker Hub に公開
  2. サンプルプロジェクトの作成
  3. bitbucket-pipelines.yml の作成
  4. Bitbucket で Pipelines の有効化

Docker Compose バイナリの作成と、作成したバイナリを Docker Hub に公開

なお、https://hub.docker.com/r/seedsstd/seeds_bitbucket_pipelines にほぼ同様のイメージを公開していますので、こちらを使用する方は、このステップは不要です。

  • Docker Compose バイナリの作成
$ mkdir seeds_bitbucket_pipelines && cd seeds_bitbucket_pipelines
$ cat <<EOF > Dockerfile
FROM docker:stable

# Add python pip and bash
RUN apk add --no-cache py-pip
RUN apk add --no-cache python-dev libffi-dev openssl-dev gcc libc-dev make
RUN apk add --no-cache bash

# Install docker-compose via pip
RUN pip install --no-cache-dir docker-compose
EOF
  • 作成したバイナリを Docker Hub に公開
docker build -t <YOUR_ACCOUNT>/seeds_bitbucket_pipelines:stable .
docker push <YOUR_ACCOUNT>/seeds_bitbucket_pipelines:stable

<YOUR_ACCOUNT> には、自身の Docker Hub アカウント、またはオーガニゼーションを指定してください。

docker build -t seedsstd/seeds_bitbucket_pipelines:stable .
docker push seedsstd/seeds_bitbucket_pipelines:stable

サンプルプロジェクトの作成

サンプルプロジェクトを作成ます。ファイル構成、内容は以下のとおりです。

$ tree
.
├── composer.json
├── composer.lock
├── composer.phar
├── docker-compose.yml
├── php-apache
│   └── Dockerfile
└── tests
    └── SampleTest.php

2 directories, 6 files
  • composer.json
{
    "name": "seeds-std/blog_bitbucket_pipelines",
    "authors": [
        {
            "name": "SEEDS Co.,Ltd",
            "email": "info@seeds-std.co.jp"
        }
    ],
    "require": {},
    "require-dev": {
        "phpunit/phpunit": "^8.3"
    }
}
  • docker-compose.yml
version: "3"

services:
  web:
    build: php-apache # php7.2-apache に git が入っていなかったため作成
    volumes:
      - ./:/var/www/html
  • php-apache/Dockerfile
FROM php:7.2-apache

RUN apt-get update -y && apt-get install -y git
  • tests/SampleTest.php
<?php

class SampleTest extends \PHPUnit\Framework\TestCase
{
    /**
     * @return void
     */
    public function testTrueIsTrue()
    {
        $this->assertTrue(true);
    }
}

ここでのポイントは docker-compose run --rm web bash -c "php composer.phar install && vendor/bin/phpunit tests/SampleTest.php" というような形で、ホスト側から PHPUnit のテストが実行できることです。

ためしに、実行してテストが通ることを確認しましょう。

$ docker-compose run web /bin/bash -c "php composer.phar install && vendor/bin/phpunit tests/SampleTest.php"
Do not run Composer as root/super user! See https://getcomposer.org/root for details
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Nothing to install or update
Generating autoload files
PHPUnit 8.3.5 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 545 ms, Memory: 4.00 MB

OK (1 test, 1 assertion)

良さそうですね。

bitbucket-pipelines.yml の作成

以下のような bitbucket-pipelines.yml を作成し、リポジトリの直下に含めます。

内容は、上記で作成した Docker Compose バイナリを使い、そのホストからコンテナを起動しテストする内容になっています。

image: seedsstd/seeds_bitbucket_pipelines:stable

options:
  docker: true

default_script: &default_script
  - docker-compose run --rm web /bin/bash -c "php composer.phar install && vendor/bin/phpunit tests/SampleTest.php"
  - docker-compose down

pipelines:
  default:
    - step:
        script: *default_script

Pipelines の有効化

最後に、Bitbucket の画面から Pipelines を有効化します。

f:id:seeds-std:20190927100459p:plain

Pipelines を有効化すると、以下のように bitbucket-pipelines.yml で指定したテストが実行されます。

f:id:seeds-std:20190927100819p:plain

テストが全て正常に終わり、以下のようになればおkです。

f:id:seeds-std:20190927101038p:plain

最後に

Bitbucket Pipelines で Docker Compose を使用したプロジェクトのテストを実行することができました。

LaravelのPivotのincrementingがfalseになっていた件

f:id:seeds-std:20191011113251p:plain

みなさま、初めまして。

WEB事業部の李です。 どうぞよろしくお願いいたします。
最近、カップヌードルの味噌味にハマっています。
昼は基本おむすびです。

www.nissin.com

本題

さて、本日は、 Laravelを5.6から5.8にアップグレードした際に、 Pivotクラスのidをデフォルトでは取得できなかった話を書きたいと思います。

結論

先に結論ですが、LaravelのPivotのincrementingが5.8からdefaultでfalseに設定されていたためでした。
trueにオーバーライドすると取れました。
オーバーライドもどうだろうというのはありますが。。

経緯など詳細

Laravelのバージョンが5.6の時に、中間テーブルを複数またぐような構造のデータを作成する必要がありました。
実装としては、pivotクラスのormを利用して登録後のidを取得し、さらに中間テーブルを作成するというプログラムが既に作られていました。

モデル->Hoge(※中間テーブル)->Hoge2(※中間テーブル)->….

上の例で、Hoge2に、Hogeのidを登録するという流れですが、
5.8へアップグレードするとhogeのレコードを作成後idが取れなくなっていました。。。
※Hogeのidは、auto_incrementで定義されています。

$hoge = Hoge::create($data); // $hoge->id 取れない。。

$hoge = new Hoge;
・
・
・
$hoge->save();  // $hoge->id 取れない。。

まじかよ。。と、もう一度アップグレードガイドを見たりしたのですが、
特に記述はなく、あの手この手と試してみたりしてたのですが、やはり取れず。。
まさかのlastInsertIdメソッド記述しないといけないのかと若干気持ちが沈んでいた頃、
あるエンジニアの方から、もしかしてこれじゃないですか?と指摘を受け、

venderの方のpivotクラスをのぞいてみると、

public $incrementing = false;

の記述がありました。 そこで、Modelクラスのsaveメソッドを少し追いかけてみると、ありました。

performInsertメソッド!これやん!!と。。

この中で、下記のような記述がありました。

//・・・省略

// If the model has an incrementing key, we can use the "insertGetId" method on
// the query builder, which will give us back the final inserted ID for this
// table from the database. Not all tables have to be incrementing though.
$attributes = $this->getAttributes();

if ($this->getIncrementing()) {
    $this->insertAndSetId($query, $attributes);
}

// If the table isn't incrementing we'll simply insert these attributes as they
// are. These attribute arrays must contain an "id" column previously placed
// there by the developer as the manually determined key for these models.
else {
    if (empty($attributes)) {
        return true;
    }

    $query->insert($attributes);
}



//・・・省略

“which will give us back the final inserted ID”<-まさにですね。

getIncrementingメソッドは下記のような記述でした。

public function getIncrementing()
{
    return $this->incrementing;
}

念の為、Laravelframeworkリポジトリで、バージョン5.6のpivotクラスを見てみると、
incrementingプロパティの記述がありません。
PivotクラスはModelクラスを継承しており、Modelクラスのプロパティには、$incrementing=trueとあったので、
バージョン5.6では、idを取得出来ていたのですね。

github.com

ちなみに、create()メソッドは、Illuminate\Database\Eloquent\Builderクラスのcreateメソッドで、結局saveを使っているみたいです。

public function create(array $attributes = [])
{
    return tap($this->newModelInstance($attributes), function ($instance) {
        $instance->save();
    });
}

というわけで、問題のHogeクラスに、

public $incrementing = true;

と、オーバーライドするとidが取れるようになりました。

frameworkでの詳しい経緯は下記のPRご参考ください。

github.com

以上です。
すでに6.xがLTSとなってますが、Pivotクラスのincrementingプロパティのデフォルトはfalseになっていますので、
この記事が皆様のご参考になればと思います。

slackに学ぶ、デザインシステムを作る重要性

こんにちは。デザイナーの河野です。

突然ですが、「デザインシステム」ってご存知ですか?

先日、Slackのデザイナーによって下記のような記事が公開されていました。
slack.engineering
slackは長らくユーザーの満足度を最優先でサービスを展開していましたが、
デザインはその時々の問題解決を優先で対応していたため、
結果、デザインに一貫性がなくなっていたり矛盾点が生じていたようです。
(スケールは全然違いますが、似たような体験をしたことが。あるあるですね。)

それを今回、時間をかけてコンポーネントからUIの見直しなど一から再構築し
デザイナー、エンジニアみんなが使える「Slack Kit」という
デザインシステムが出来上がったよ、という内容の記事になります。

f:id:seeds-std:20190924122735p:plain
引用:The Gradual Design System: How We Built Slack Kit


シーズの今までのデザイン体制

私はここ1年半ほど、ほぼ1人でデザイン業務を担当していたので
全く意識していなかったのですが、
新しく入ったデザイナーに既存のサイトの一部業務の引き継ぎをする時に
どうもイメージや認識の違いを覚えるようになり、
今までルールが自己で完結していたなぁ...と、その重要性を痛感しました。

デザインは複数人が行うと、バラつきやぶれが発生してしまいがちです。
同じサービス内なのにボタンやバナーのディティールや
使っている色、余白にバラつきがあったら
一貫性がなくなり、おのずとサービスとしての質も下がって見えてしまいますね。
またエンジニアへのコミュニケーションコストなども考えると大きな損失になります。
結果、会社全体の生産性の話にも繋がってきます。

デザインシステムを作るメリット

こんな時、デザインシステムを予めしっかり作っておくと、
今後また新しいチームメンバーが参加しても
それまでのルールに乗っ取ったデザインができるようになります。

また、どのようにデザインすべきかが明示されているので、
極論、デザイナーでない人もデザインできるようなります(!)
例えばちょっとしたアラートを出すとか、バナーを追加するとなった時は
コーダーやフロントエンジニアが直接作ってしまうというのも可能なわけです。

実はシーズのフロントエンドエンジニアチームでは、すでに似たようなものが導入されていて、
リーダーのNさん(あだ名:師匠)によって
コーディングガイドサイトのようなものが社内向けで共有されています。

f:id:seeds-std:20190924122752p:plain
中身はナイショでございます。



デザインシステムに入れると良い要素とは?

ということで、うちもデザインシステム作ろうじゃないの!
と、早速PCに向かったはいいものの、
具体的に何を作ればいいんだっけ・・
と、路頭に迷ったので、まずは色々と調べてみました。

①カラー

f:id:seeds-std:20190924131222p:plain

コーポレートカラーやブランドカラーといった意匠的なところから、
アプリやサービスなどの場合はBootstrapのカラーユーティリティーのような
状況に応じたルールも必要になります。
色は配色バランスによって印象が大きく変わりますので、
「この色は画面全体の●●%まで」と割合も決めた方が良いかもしれません。

②各種コンポーネントなど

f:id:seeds-std:20190924145220p:plain
色に続いて、これはエンジニアの作業と紐付けやすいと思います。
各種ボタンなどデザインパーツ、統一されてないと非常にモヤモヤしますよね。。(職業病)
Sketchの場合symbolを使用すれば解決する話でもあります。

③余白

f:id:seeds-std:20190924145230p:plain
これ、盲点ですね。
例えば既存のサイトで新しいセクションやページを追加するとなった時、
別のデザイナーに依頼したらmarginが違う...とかありそうですよね。

④レイヤーの命名規則

f:id:seeds-std:20190924161419g:plain
作業中のレイヤーって気づくと混沌とした世界になっている事がありますね。。
これも共通ルールなど徹底すると、エンジニアの作業も楽になるのではないでしょうか?

⑤テキスト

f:id:seeds-std:20190924145251p:plain
これもルール化しておけば良かった...と後悔したものの上位になります。
何も指定していないと100%デザイナーによって変わります。
フォント、行間、サイズ、行揃えなどなど、ただのテキストでも留意点がたくさんありますね。

⑥ページレイアウト

f:id:seeds-std:20190924145325p:plain
例えば、一口にレスポンシブデザインと言っても、
リキッドなのかフレキシブルなのかで作業ボリュームが全然違います。 対応端末は何パターンか、ブレイクポイントはいくつか、など 段階別に何パターンかルールを分けておくと良いかもしれないです。


<おまけ>著名な企業のデザインシステムの実例

最後に、著名な企業のデザインシステムをご紹介します。
近頃では、デザインシステムをただの社内用のルールとしてではなく、
ブランディングの一環として公表している会社が結構あることが分かりました。

Atlassian

f:id:seeds-std:20190924123201p:plain エンジニアの皆さんご存知のAtlassianです。
なんとデザインシステムを作る前は同じドロップダウンのパーツなのに45種類の異なるデザインがあったとか。。

Airbnb

f:id:seeds-std:20190924123203p:plain 創業者がデザイナーということもあってか、ブランディングの徹底ぶりが半端ない Airbnb。
2018年にオリジナルフォントを出したことも記憶に新しいです。


Uber

f:id:seeds-std:20190924123208p:plain Uberは2018年にリブランディングを行いました。
それについて詳しく書かれています。
一見自由な線のイラストもGridに沿って作成されていて、その仕事の精密さに感動すら覚えます。

Salesforce

f:id:seeds-std:20190924123211p:plain 最後にBtoB企業も紹介します。Salesforceは「Lightning」と言うデザインシステムを公開しています。
「分かりやすさ」「効率性」「一貫性」「美しさ」をデザイン原則として作られています。

まとめ

調べるほど奥が深いデザインシステム。
まさかこうやってネット上に公表している組織があることが少し驚きでした。

また、デザイナーはセンスや感性が命!と思われている人も多いかもしれないですが、
こういった理念に基づいた細やかなルールを守り、
効率的にプロダクトの品質を守っていくというのも大事な仕事なんですね。

シーズは現在デザイナーは2名しかいないですが、
いつかメンバーが入れ替わったり大きな組織になった時、
未来のデザイナーが困らないよう、ちょっとずつ頑張っていきたいものです。

AWS Lambda関数をKotlin/JSで書いてみる

f:id:seeds-std:20190925123639p:plain

WEB事業部の西村です。

唐突ですが皆さんはLambda関数を作成する際にどのような言語を用いていますでしょうか?
用途に合った言語や自分で書きやすいと思う言語などの要因で決めていると思われます
私はKotlinという言語が好きでKotlinで書こうと思ったのですが、Kotlinで書かれている記事はそのほとんどがJavaで実行することを前提とした元なっています
しかし、Javaではコールドスタートした際に実行時間がかかってしまうというデメリットがあります
KotlinはJavaだけでなくJavaScriptでも動作させることができるので今回はNode.jsで実行させる関数の作成を行いたいと思います

目次

この記事の目標

Kotlin/JSを用いたLambda関数の作成とそのレスポンス確認

この記事で取り扱わないこと

この記事では AWS LambdaAmazon API Gateway については詳しく解説いたしません
あらかじめLambda関数とその関数を呼び出すためのAPIの作成を行っておいてください

開発環境

この記事では下記の環境で開発を行っています

  • AdoptOpenJDK 1.8.0_222-b10
  • IntelliJ IDEA Community 2019.2.2
  • Kotlin 1.3.50

プロジェクトの作成

プロジェクトの作成を行います
任意のディレクトリにプロジェクト用のフォルダを作成します(この記事では KotlinLambda としています)
その後そのディレクトリ内に build.gradle.ktssettings.gradle.kts の2つのファイルを作成します

KotlinLambda
├─build.gradle.kts
└─settings.gradle.kts

作成したらIntelliJ IDEAでプロジェクトのフォルダを開きます
続いて、 build.gradle.ktssettings.gradle.kts を開き下記の内容を入力します

build.gradle.kts

plugins {
    kotlin("js") version "1.3.50"
}

repositories {
    mavenCentral()
}

kotlin {
    target {
        nodejs()
        useCommonJs()
    }

    sourceSets["main"].dependencies {
        implementation(kotlin("stdlib-js"))
    }
}

settings.gradle.kts

rootProject.name = "kotlin_lambda"

その後画面右下に出ている Gradle projects need to be importedImport Changes をクリックします
CONFIGURE SUCCESSFUL と出てきたら準備完了です

ハンドラソースコードの追加

次にソースディレクトリを作成します
プロジェクトのディレクトリに src/main/kotlin でディレクトリを作成します
またパッケージ名を追加する場合、パッケージを作成します(この記事では jp.co.seeds_std.lambda.kotlin とします)
作成したディレクトリに handler.kt というファイルを作成し下記の内容を記述します

handler.kt

package jp.co.seeds_std.lambda.kotlin

@JsExport
@JsName("handler")
fun handler(event: dynamic, context: dynamic, callback: (Error?, dynamic) -> Unit) {
    val response: dynamic = object {}
    response["statusCode"] = 200
    response.body = "Hello from Kotlin Lambda!"
    callback(null, response)
}
  • @JsExport JavaScriptのexportを行うアノテーションです
  • @JsName(name: String) 記述したクラスや関数をトランスパイルした際 name に指定した名前となるよう変換してくれるようになります
  • dynamic JavaScriptのオブジェクトをそのまま取り扱います(. [] でその中の関数や変数にアクセスできます)

デプロイ

デプロイパッケージの作成を行います
圧縮を自動化するためのタスクを追加します
build.gradle.kts を再度開き下記の内容を追加します

build.gradle.kts

import com.google.gson.JsonParser
import java.io.OutputStream
import java.util.zip.ZipOutputStream
import java.util.zip.ZipEntry

plugins { ... }
repositories { ... }
kotlin { ... }

open class CompressTask : DefaultTask() {

    lateinit var buildDirectory: File
    lateinit var projectName: String
    var rootProjectName: String? = null

    private val blackLists = setOf("kotlin-test-nodejs-runner", "kotlin-test")

    @TaskAction
    fun compress() {
        val projectPrefix = if(rootProjectName != null && rootProjectName != projectName) "$rootProjectName-" else ""
        val zipFile = File(buildDirectory, "../compress.zip")
        val outputStream = ZipOutputStream(zipFile.outputStream(), Charsets.UTF_8).apply {
            setLevel(9)
        }

        val jsDir = File(buildDirectory, "js")
        val projectDir = File(jsDir, "packages/$projectPrefix$projectName")
        val nodeModuleDir = File(jsDir, "node_modules")
        addDependencies(outputStream, File(projectDir, "package.json"), nodeModuleDir, mutableSetOf(*blackLists.toTypedArray()))
        addZipEntry(outputStream, File(projectDir, "kotlin/$projectPrefix$projectName.js"))

        outputStream.close()
    }

    private fun addZipEntry(zipOutputStream: ZipOutputStream, file: File, addDirectory: String = "") {
        val name = if(addDirectory.isEmpty()) file.name else "$addDirectory/${file.name}"
        if(file.isDirectory) {
            file.listFiles()?.forEach {
                addZipEntry(zipOutputStream, it, name)
            }
        } else {
            val zipEntry = ZipEntry(name)
            zipOutputStream.addZipEntry(zipEntry) {
                it.writeFile(file)
            }
        }
    }

    private fun addDependencies(zipOutputStream: ZipOutputStream, packageJsonFile: File, nodeModuleDir: File, addedDependencies: MutableSet<String> = mutableSetOf()) {
        val packageJson = JsonParser().parse(packageJsonFile.reader()).asJsonObject
        if(!packageJson.has("dependencies")) return
        val dependencies = packageJson.getAsJsonObject("dependencies")
        dependencies.keySet().forEach {
            if(!addedDependencies.contains(it)) {
                addedDependencies.add(it)
                val dependenciesDir = File(nodeModuleDir, it)
                addZipEntry(zipOutputStream, dependenciesDir, "node_modules")
                addDependencies(zipOutputStream, File(dependenciesDir, "package.json"), nodeModuleDir, addedDependencies)
            }
        }
    }

    private inline fun ZipOutputStream.addZipEntry(entry: ZipEntry, crossinline block: (OutputStream) -> Unit) {
        try {
            putNextEntry(entry)
            block(this)
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            closeEntry()
        }
    }

    @Suppress("NOTHING_TO_INLINE")
    private inline fun OutputStream.writeFile(file: File) {
        file.inputStream().use {
            it.copyTo(this)
        }
    }
}

tasks.register("noMainCall") {
    doFirst {
        kotlin.target.compilations.all { compileKotlinTask.kotlinOptions.main = "noCall" }
    }
}

tasks.register<CompressTask>("compress") {
    dependsOn("compileKotlinJs", "noMainCall")
    this.buildDirectory = rootProject.buildDir
    this.projectName = project.name
    this.rootProjectName = rootProject.name
}

tasks["compileKotlinJs"].mustRunAfter("noMainCall")

追加後、画面右下に出ている Gradle projects need to be importedImport Changes をクリックします
CONFIGURE SUCCESSFUL と出てきたら画面右側の Gradle タブを開きます
kotlin_lambda -> Tasks -> other の順に開き、その中の compress をダブルクリックして実行します
するとプロジェクトのディレクトリに compress.zip というファイルが生成されます
この生成されたファイルをコンソールからアップロードします
その後の ハンドラkotlin_lambda.{パッケージ名}.handler に変更します (この記事では kotlin_lambda.jp.co.seeds_std.lambda.kotlin.handler となります) アップロードと変更が完了したら保存を行います

動作確認

あらかじめ作成しておいたAPI GatewayのURLにアクセスし Hello from Kotlin Lambda! と表示されれば成功です

最後に

いかがでしたでしょうか
Javaの時より動作も軽くなったと感じるのではないかと思います
次回はもう少し使いやすくなるような記事も書きたいと思います
ここまで読んでいただきありございました

Kotlin/JSのAWS Lambda関数を便利にしてみる