RareJob Tech Blog

レアジョブテクノロジーズのエンジニア・デザイナーによる技術ブログです

iOS 版レアジョブアプリが Sign in with Apple に対応した話

APP/UX チームの玉置です。
今回は iOS 版レアジョブ アプリの Sign in with Apple 対応が完了しましたので、それについてエモい話をします。
はい、 Sign in with Apple にやっと対応することができました。非常に大変でした。

【目次】

Sign in with Apple とは何か

まずモバイルアプリ界隈では非常に有名ですが、その他の業界ではSign in with Appleの認知度はそこまで普及していないので簡単に説明してみます。
Sign in with AppleApple が開発した Apple アカウント( AppleID )を使ったソーシャルログインの認証システムです。
例えば、 TwitterFacebook のソーシャルログインといった感じのものですね。

レアジョブ が Sign in with Apple に対応した背景と対応後のデザイン

去年、 Apple が突如 iOS13 と一緒に Sign in with Apple を公開して「皆の者、これに対応しなさい!」という号令が発表されました。
全てのアプリがこれに対応しなければならないわけではなく、 FacebookTwitter といった Apple 以外のソーシャルログインを実装しているアプリが対象にされました。

6月末までにソーシャルログインを使う場合は Sign in with Apple 対応が必要になりました。レアジョブは Facebook ログインをサポートしています。
レアジョブ における Facebook ログインユーザーの比率が無視できないぐらい高いので、この Sign in with Apple に対応しなければならなくなりました。

もともとのレアジョブの登録・ログイン画面はこちらになります。

登録画面 ログイン画面
f:id:qed805:20200820135650p:plain
過去の登録画面
f:id:qed805:20200820135712p:plain
ログイン画面

それが Sign in with Apple の対応で iOS 13 以上と iOS 12 以下で見え方が変わるように対応しました。

iOS 13 登録 iOS 13 ログイン画面
f:id:qed805:20200820135924p:plain
リリース後のiOS13での登録画面
f:id:qed805:20200820135956p:plain
リリース後のiOS13でのログイン画面
iOS 12 登録 iOS 12 ログイン画面
f:id:qed805:20200820140048p:plain
リリース後のiOS12以下での登録画面
f:id:qed805:20200820140119p:plain
リリース後のiOS12以下でのログイン画面

スケジュールと見積もりについて

プロダクトオーナーに見積もりを提示してスケジュールを引くために Sign in with Apple の仕組みと Apple サーバーから得られる情報を確認する必要がありました。
Sign in with Apple のサンプルプロジェクトを知ったのは実際に開発が始まってからでしたので、見積もり段階ではどんな情報がどんな風に得られるかわからなかったのですね。

そのため自分のマイマシンで Sign in with Apple を実装する必要がありました。実装すること自体はそんな手間ではありませんでした。

これから Sign in with Apple を実装するのでしたら、先に Apple が公開しているサンプルプロジェクトを確認しておいた方がいいと思います。

Appleのドキュメントとサンプルプロジェクトファイルのダウンロードページ

https://developer.apple.com/documentation/authenticationservices/implementing_user_authentication_with_sign_in_with_apple

見積もりは正確な情報が分かりませんでしたので、 Sign in with Apple 自体のシステムの調査として

  1. 実装方法
  2. 得られるデータの確認
  3. 仕様の調査

の3つの項目を2回に分けて見積もりしました。

アプリ側の実装自体は5人日ぐらいのボリューム感でしたが、 Web 版や API との連携も含めた見積もりで2週間に膨れ上がってしまいました。

開発前に不明であった内容について

  1. ログイン後の挙動 (email, fullnameの扱い)
  2. メールを非公開の内容
  3. ログアウトの方法

1. ログイン後の挙動 (email, fullnameの扱い)

Sign in with Apple で有名なのがログイン後に取得できるデータについてです。
Apple サーバーから取得できるユーザー情報の一部に email と fullname の情報がありますが、この情報は最初の1回目しか取得できず2回目以降は空が渡ってくることです。

再度 email と fullname を取得する手段は残されていて、端末の設定アプリから

Apple ID > パスワードとセキュリティ > Apple IDを使用中のApp > 特定のアプリ

で表示される「Apple IDの使用を停止する」をタップして停止させるとアプリでの Sign in with Apple の使用が止まって 再度 email と fullname を取得できるようになります。

2. メールを非公開の内容

次に Apple 認証の際に Apple ID に紐づいているメールアドレスを公開するかどうかの選択できる部分の仕様について紹介します。

Apple 認証を用いるとアプリで使用する際にメールアドレスを公開するかどうかを選択できます。

  • 「メールを共有」
  • 「メールを非公開」

の2つのオプションがあります。

「メールを共有」して Sign in with Apple するとアプリ側でユーザーのメールアドレスが公開されます。
「メールを非公開」して Sign in with Apple すると Apple 側で生成されたプライベート用のメールアドレスが発行されます。

abcde12345@privaterelay.appleid.com
alk32943dw@privaterelay.appleid.com
bl31dkr03e@privaterelay.appleid.com

このような形式です。
アットマーク前は10文字の固定長のランダム文字列というのが特徴的です。

3. ログアウトの方法

こちらは Sign in with Apple のログアウト方法です。
1でApple IDの使用を停止する」をタップして使用を停止させるとそれがログアウトになる仕様のようでした。

フロント側で必要な対応

  • iOS 12 以下と iOS 13 以降での画面の切り分け
  • ユーザー情報のキーチェーンへの保存
  • アプリ起動時に Apple 認証しているかどうかのチェック

iOS 12 以下と iOS 13 以降での画面の切り分け

Sign in with AppleiOS 13 以降でした使えないので端末バージョンで処理を分けるしかありません。
iOS に ContainerView というコンポーネントがありますので ViewControler を分離させました。

    override func viewDidLoad() {
        super.viewDidLoad()
        setupSocialLoginView()
    }

    private func setupSocialLoginView() {
        if #available(iOS 13, *) {
            /// iOS 13以降で走る処理
            let socialNewVC = UIStoryboard.load("SocialRegister", idetifier: "SocialNewRegisterViewController") as! SocialNewRegisterViewController
            addSocialContainer(vc: socialNewVC)
        } else {
            /// iOS 12以下で走る処理
            let socialOldVC = UIStoryboard.load("SocialRegister", idetifier: "SocialOldRegisterViewController") as! SocialOldRegisterViewController
            addSocialContainer(vc: socialOldVC)
        }
    }

このように分岐させました。

Container 側のデザインは storyboard でデザインしています。

f:id:qed805:20200721140543p:plain
storyboard

ユーザー情報のキーチェーンへの保存

Apple 認証後に得られるユーザー情報をAPIに送信するのですが、確実に送信させないと行けませんので保存する領域を決めないといけませんでした。

ちょうどレアジョブアプリは iPhone のキーチェーン( Keychain )を使っていますので、得られたユーザー情報の一部をキーチェーンの領域に保存することにしました。

アプリ起動時に Apple 認証しているかどうかのチェック

Sign in with Apple でログイン中のレアジョブユーザーが再度レアジョブ アプリに戻ってきたときにログイン中かどうかを確認する必要がありました。

そこで AppDelegate で user をキーにしてログイン中かどうかを判別することにしました。
ログイン中であればその状態を維持して、ログアウトされていればアプリをログアウトする必要があります。

ソースコード上では次のような対応を行いました。

    private func checkAppleLoginStatus() {
        /// Apple認証後のuserの情報をキーチェーンから取得する
        guard let userIdentifier = UserDataStore.currentUserIdentifier else { return }
        /// Apple ログイン中かどうかキーチェーンの情報を使って確認する
        if #available(iOS 13.0, *) {
            let appleIDProvider = ASAuthorizationAppleIDProvider()
            appleIDProvider.getCredentialState(forUserID: userIdentifier) { (credentialState, error) in
                switch credentialState {
                case .authorized:
                    /// 端末側のApple ログイン中
                    break
                case .revoked, .notFound:
                    /// 端末側のAppleログインセッション切れ
                    // ログアウトする
                default:
                    break
                }
            }
        }
    }

Web フロントへの対応で共有すべき情報

  • Web でのログインに必要な ServiceID と Key
  • Web からのメール送信ができるようにするための対応
  • AppId と ServiceId との関係

Web でのログインに必要な ServiceID と Key

Web 版 Sign in with AppleApple 認証で必要なものがありました。 ServiceID と Key です。どちらも Apple Developer サイトで設定・取得できるものです。
ServiceID とは iOS アプリの AppID と似たものです。

iOS アプリでは BundleID を発行してユニークなIDを発行しますが、Web に該当するものが ServiceID になります。 ServiceID は AppID と同じものは指定できずさらにユニークなものにしなければなりません。 ServiceID はApplD.xxxx みたいに使っている AppID の後にユニークな文字列を追加しました。

さらに Key も設定してファイルをダウンロードして Web エンジニアに共有しました。

Web からのメール送信ができるようにするための対応

レアジョブのサービスに Web サーバーからメール送信する機能があります。例えば、レッスン予約時やレッスン開始前に送信される確認メールだったりします。

実は上で話しました「メールを非公開」にして Apple 認証すると Apple がプライベートなメールアドレスを生成します。
このメールアドレスに何も設定せずにメールを送信しようとするとエラーが発生して「メールが送れません」みたいなエラーメールが返ってきて正常にメールを送信できません。

そこでサービス元から Apple のプライベートメールアドレスにメールを送信するために Apple Developer サイトで設定を行う必要があります。
普段のアプリ開発のリリースでは触らない Certificates ページの「 More 」の項目に進みます。

f:id:qed805:20200721163337p:plain
Moreページ

「 Configure 」ボタンをクリックすると「Configure Sign in with Apple for Email Communication」ページにアクセスできます。
このページで Apple のプライベートメールアドレスに送信する「送信元のメールアドレス」を登録する必要があります。

上限があるのかまでは確認していませんが考えられるメール送信元のメールアドレスを全て登録しておきます。
この設定で Apple のプライベートメールアドレスにメールを送信できるようになります。

詳細について知りたい場合は Apple 公式のドキュメントがありますのでこちらを確認すれば把握できると思います。

Configure Private Email Relay Service

https://help.apple.com/developer-account/?lang=en#/devf822fb8fc

AppId と ServiceId との関係

最後に AppID と ServiceID との関係についてです。

さらっと説明すると
AppID は iOS アプリ側で Apple 認証に必要な ID で、 Web で Apple 認証するのに必要なものが ServiceID です。

AppID と ServiceID は密接に紐づいています。
AppID でApple 認証した AppleID アカウントであれば、それに紐づいている ServiceID を使えば同じ AppleID アカウントで Web 版の Apple 認証に成功することができます。
AppID と ServiceID が紐づいていないもので Apple 認証をしようとすると失敗してしまいます。

そこだけ注意すれば大丈夫でした。

最後に

とても長くなりましたが無事にアプリと Web とで Sign in with Apple に対応できましたので、是非とも使ってみてください。
リリースしてからしばらく経っていることもあって利用者の方も徐々に増え始めています。とても嬉しいことですね!
実際にログインするときは指紋認証か顔認証でログインできるようになりますので文字入力をする必要がないのはとても快適です。

Cypressを試してe2eテストやってみた

お久しぶりです。プラットフォームチームの南です
TSUTAYAで昔のドラマをレンタルするのにハマってます
(最近はラスト・フレンズを観て哀しい気持ちになりました)

今回は自分が個人的に触ってみたe2eテストツールについて紹介したいと思います

なんでe2e

当然ですがリリースをする際には動作確認を各環境で行います
その時、毎度同じ操作を画面で行うのが煩わしく感じることがあると思います(自分はそんなに嫌いじゃないです)

  • リリースを行う際、前後の動作確認は重要
  • 手順書としてドキュメントを用意しているが、人がやることなので抜けやミスがある可能性は否定できない
  • 確認事項は毎回変わるわけではない
    • ある程度手順が決まっている
  • 開発環境・ステージング環境・本番環境とそれぞれでやるのが割とコストとして重い

ざっとこんな感じの問題点があったので、e2eを導入して効率よく自動化できないかと考えました

Cypressとは?

ツールは

  • 王道的なseleniumとかよりもちょっと新しくていい感じのやつ触りたい
  • ciや他サービスとの連携がとりやすい
  • 導入・学習コストは下げたい

このあたりをポイントとして選定し、Cypressにいきつきました

www.cypress.io

Cypressの特徴として

  • javascriptで動作する
  • 軽量さくさく
  • dashboardが使いやすい
  • 導入・実装が容易

このあたりがあるようです
とりあえず使ってみます

導入方法

yarnやnpmでインストールできるので簡単でした

$ yarn add -D cypress

インストールが完了すると

$ yarn run cypress open

上記コマンドで動作確認できます
Cypressにはチュートリアル的なテストファイルがデフォルトで用意されているので、動かしながら使い方を学べます

e2eテストを書いてみる

実際にe2eテストを書いてみます
以前、vueを自習してたときに作ったtodo listに対してテストコードを実装してみます f:id:nannannanan:20200806154750p:plain
我ながらとてもシンプルです

機能としてはこんな感じです

まずcypress.jsonにテスト対象アプリケーションのbaseURlを指定します

{
  "baseUrl": "http://localhost:8080/"
}

テストファイルはintegrationディレクトリの中に作ります
今回はtodo_spec.jsというファイルを作ります
そして完成したコードがこちらです

describe('todo list e2e', function () {
    beforeEach(() => {  // functionの前に必ず実行される
        // baseUrlのrootへ
        cy.visit('/');

        // todoを1つ追加
        cy.get('.todo')
            .type('test todo').should('have.value', 'test todo')
        cy.get('.addBtn').click()
        cy.get('.todo').clear()

        // 2つ目のtodoを追加
        cy.get('.todo')
            .type('test todo2').should('have.value', 'test todo2')
        cy.get('.important').check()
        cy.get('.addBtn').click()
        cy.get('.todo').clear()
    });
    it('should create new todo list', function () {
        // .todoList -> 1行目の.rowTodo => Titleには"test todo"が入ってるよね?ImportantはFalseだよね?のテスト
        cy.get('.todoList')
            .find('.rowTodo:first').should('have.class', 'rowTodo')
            .find('.tIndex').should('have.text', '1')
            .siblings('.tTitle').should('have.text', 'test todo')
            .siblings('.tImportant').should('have.text', 'False')

        // 全部でtodoは2行あるよね?のテスト
        cy.get('.rowTodo').should('have.length', '2')

        // .todoList -> 2行目の.rowTodo => Titleには"test todo2"が入ってるよね?ImportantはTrueだよね?のテスト
        cy.get('.todoList')
            .find('.rowTodo:last').should('have.class', 'rowTodo')
            .find('.tIndex').should('have.text', '2')
            .siblings('.tTitle').should('have.text', 'test todo2')
            .siblings('.tImportant').should('have.text', 'True')
    });
    it('should delete complete todo', function () {
        // 2行目のtodoをCompleteするぞ!の動作
        cy.get('.todoList')
            .find('.rowTodo:last')
            .find('.compTask').check()

        // 全部でtodoは1行あるよね?のテスト
        cy.get('.rowTodo').should('have.length', '1')

        // 残ったtodoの中身は正しいよね?のテスト
        cy.get('.todoList')
            .should('have.length', '1')
            .find('.rowTodo:first').should('have.class', 'rowTodo')
            .find('.tIndex').should('have.text', '1')
            .siblings('.tTitle').should('have.text', 'test todo')
            .siblings('.tImportant').should('have.text', 'False')
    });
});

checkやclickのアクションを使ってDOMを操作し、shouldでassertを行います
siblingsとかのselectorをみるとjQueryを思い出しました
作ったテストを動かすには

$ yarn cypress run --browser chrome --spec=./cypress/integration/todo_spec.js

これで動きます
コマンドをみればわかるように、動作させるブラウザを選ぶことができます
コマンドラインで実行もできますが、 cypress openのコマンドなら動かすテストファイルをGUIで選択することもできます f:id:nannannanan:20200806164213p:plain

Cypress dashboardの紹介

最後にCypressの大きな魅力であるdashboardについて紹介します
CypressにログインしProject IDとRecord Keyを取得します
取得したProject IDをcypress.jsonに追記します

{
  "baseUrl": "http://localhost:8080/",
  "projectId": "xxxxxxxx"
}

設定はこれだけ
あとは実行コマンドにRecord Keyを追記して

yarn cypress run --browser chrome --spec=./cypress/integration/todo_spec.js --record --key 999999999

これでテストの結果がdashboardと連携できます
f:id:nannannanan:20200806170428p:plain
dashboardではテストの結果やciを含む外部サービスとの連携設定、テスト動画・スクショの確認などが行えます
正直、dashboardが使いたいからCypressを使ってる感じもあります

さいごに

今回、試験的にCypressを使ってみましたが

  • 動作はめちゃ速い
  • コードの書き方は覚えやすい
  • dashboardとの連携は楽
  • dashboardきれい

などの利点がありました
ただし

  • DOMを変更したときの対応
  • テストデータの管理
  • dashboardを4人以上で使うには課金が必要

などなど考慮しなければいけない課題もあるにはあると思いました
Cypressだけでなく、Postman -> Newman などツールは世の中に山程あるので、うまく組み合わせられると効率化できそうですね

以上!

マルチステージビルドで環境毎のLaravelイメージを作る

はじめに

こんにちは、サービス開発チームの加々美です。初投稿になります。

先日直属の上司の方に「最近2日に1回はカレー食べてます」と謎の共有をしたところ、「疲れてるんじゃないですか?」と言われました。 私はただカレーにハマってるだけだと思っていますが、もし週に何回もカレーを食べてしまうという方は注意が必要かもしれません。

早速ですが本題に入っていきたいと思います。

Dockerイメージを作る際に開発環境と本番環境でインストールしたいパッケージや設定が異なることがあると思います。

一方で全く異なるかというとそんなことはなく、基本的には同じで開発時のみデバッグツール(PHPだとXdebug)を利用できるようにしたい、といったケースが多いです。

今回はLaravelの環境毎に利用できるDockerイメージ作成時の知見についてまとめた内容になります。

マルチステージビルドに関する記事は多いですが、意外とLaravelでの記事がなかったためこの機会にまとめました。

今回のフォルダ構成

今回のサンプルは以下のフォルダ構成で作成しています。 srcにはLaravelアプリケーションのファイルを置いてます。

├── docker
│   ├── app
│   │   ├── Dockerfile
│   │   └── docker-php-ext-xdebug.ini
│   └── web
│       ├── Dockerfile
│       └── default.conf
├── docker-compose.yml
└── src

マルチステージビルドとは

Docker 17.05から利用できるようになった機能で、マルチステージビルドが登場する前は次に説明するDockerfileを複数作成するなど共通の定義を利用できませんでした。 マルチステージビルドを利用することでベースとなるイメージを作成し、それを再利用することによりイメージ毎に不要なファイルが含まれることを防ぐことができイメージサイズの削減が可能になります。

matsuand.github.io

マルチステージビルドを使用しない場合

マルチビルドステージを利用しない場合はDockerfileを複数作成する方法がありますが、Dockerfile毎にそれぞれで定義する必要があるため共通で使用する箇所が多い場合でも全て記載しないといけません。

Dockerfileを別々で定義する場合、変更する度にそれぞれに対して変更を加える必要があり管理が煩雑になります。

開発用のイメージのビルド

何も考えずにdocker/app/Dockerfileを作成すると以下になります。

FROM composer:latest AS composer

FROM php:7.4-fpm-alpine

COPY --from=composer /usr/bin/composer /usr/bin/composer

RUN set -eux \
  && apk update \
  && apk --no-cache add \
    git \
    oniguruma-dev \
    libzip-dev \
    zip \
  && docker-php-ext-configure zip \
  && docker-php-ext-install pdo_mysql mbstring zip \
  && composer config -g repos.packagist composer https://packagist.jp \
  && composer global require hirak/prestissimo \
  && apk --no-cache add \
    autoconf \
    gcc \
    g++ \
    make \
    openssh-client \
  && pecl install xdebug \
  && docker-php-ext-enable xdebug

COPY ./src /var/www/

上記の中で開発環境用に追加するパッケージはこちらです。

  && apk --no-cache add \
    autoconf \
    gcc \
    g++ \
    make \
    openssh-client \
  && pecl install xdebug \
  && docker-php-ext-enable xdebug

RUN内で2度apk --no-cache addを行なっていますが、本番用のイメージと比較しやすいようにあえてこのように記述しています。

このDockerfileをビルドしてみます。

docker build . -f docker/app/Dockerfile -t sample1

docker imagesで作成されたイメージを確認してみます。

REPOSITORY     TAG       IMAGE ID          CREATED            SIZE
sample1        latest    536e1516fffb      2 hours ago        378MB

本番用のイメージのビルド

ビルドするファイルの内容は以下になります。

FROM composer:latest AS composer

FROM php:7.4-fpm-alpine AS builder

COPY --from=composer /usr/bin/composer /usr/bin/composer

RUN set -eux \
  && apk update \
  && apk --no-cache add \
    git \
    oniguruma-dev \
    libzip-dev \
    zip \
  && docker-php-ext-configure zip \
  && docker-php-ext-install pdo_mysql mbstring zip \
  && composer config -g repos.packagist composer https://packagist.jp \
  && composer global require hirak/prestissimo

COPY ./src /var/www/

こちらもdocker imagesで作成されたイメージを確認してみます。

REPOSITORY  TAG       IMAGE ID          CREATED           SIZE
sample2     latest    dbb97bb45402      9 seconds ago     136MB
sample1     latest    536e1516fffb      2 hours ago       378MB

Xdebug関連のインストールがないだけでイメージサイズが半分以下になっているのがわかりますね。

マルチステージビルドを使う

docker/app/Dockerfileは以下になります。

FROM composer:latest AS composer

FROM php:7.4-fpm-alpine AS builder

COPY --from=composer /usr/bin/composer /usr/bin/composer

RUN set -eux \
  && apk update \
  && apk --no-cache add \
    git \
    oniguruma-dev \
    libzip-dev \
    zip \
  && docker-php-ext-configure zip \
  && docker-php-ext-install pdo_mysql mbstring zip \
  && composer config -g repos.packagist composer https://packagist.jp \
  && composer global require hirak/prestissimo


FROM builder AS dev

RUN set -eux \
  && apk --no-cache add \
    autoconf \
    gcc \
    g++ \
    make \
    openssh-client \
  && pecl install xdebug \
  && docker-php-ext-enable xdebug

COPY ./src /var/www/

FROM builder AS prod

COPY ./src /var/www/

簡単に解説していきたいと思います。

FROM php:7.4-fpm-alpine AS builderの箇所で共通で利用できるステージを定義しています。

devprodのステージを定義する際にbuilderステージを利用します。

builderの利用方法はFROM builder AS devのように利用します。

devではデバッグ用のパッケージをインストールしていますが、prodではbuilderをそのまま利用しています。

ビルドする際は--target devのようにビルドしたいステージをオプションで指定します。

それぞれビルドします。

docker build . docker/php/Dockerfile -t sample3 --target dev

docker build . docker/php/Dockerfile -t sample4 --target prod

実行結果を確認すると以下のようになりました。

REPOSITORY    TAG       IMAGE ID          CREATED              SIZE
sample4      latest     39bd6a456c9d      29 seconds ago       136MB
sample3      latest     e1bee9d50280      About a minute ago   378MB

マルチステージビルドを利用しないパターンと同じイメージがビルドできました。

弊社では主にAWSを使用しており、ECSへの移行も考慮しているので各環境毎にビルドしたイメージをECRにpushする場合などは--targetでステージを指定することで対象のDockerイメージをビルドすれば良さそうです。

また、ステージを指定してビルドした場合は、対象ステージのみビルドされます。

つまり、prodを指定してビルドする際はdevはビルドされません。

docker-composeでステージを指定して利用する

最後にdocker-composeで指定する方法について解説したいと思います。

今回使用したdocker-compose.ymlはこちらでです。

version: '3.7'
services:
  app:
    build:
      context: .
      dockerfile: ./docker/app/Dockerfile
      target: dev
    volumes:
      - ./src:/var/www
      - ./docker/app/docker-php-ext-xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
  web:
    build:
      context: .
      dockerfile: ./docker/web/Dockerfile
    ports:
      - "8000:80"
    volumes:
      - ./src:/var/www
      - ./docker/web/default.conf:/etc/nginx/conf.d/default.conf

ステージを指定している箇所はtarget: devの箇所です。

意外と今までやったことがなかったですが、簡単に指定できました。

あとはdocker-compose upを実行し、立ち上がったらdocker-compose exec app shでコンテナ内に入り、composer create-project laravel/laravel .を実行すればブラウザで確認できるようになります。

おまけ

サンプルのDockerfile内でcomposerに関して以下のように利用していました。

FROM composer:latest AS composer

COPY --from=composer /usr/bin/composer /usr/bin/composer

これは外部イメージをステージとして利用する方法です。

https://matsuand.github.io/docs.docker.jp.onthefly/develop/develop-images/multistage-build/#use-an-external-image-as-a-stage

検証できていないですが、この方法を使用すればbuilderで定義したイメージをCIでECRなどにあげておき、開発者はそこからステージとして開発環境に利用できるのかも?と思ったりしました。

まとめ

今回はマルチステージビルドを利用したLaravelのDockerイメージ作成について説明しました。

もっと良い使い方あるという方はコメント頂けると嬉しいです。

思ったより記事を書くのに時間がかかったので今夜はカレーを食べたいと思います。(実は5日連続。。)

UMLをコードベースで管理する

さて、ジャンボです。本当はGOTO( Ghost of Tsushima Omoshiroi-zo )という話を書きたいところですが大人しくテックブログを始めようと思います。今日はPlantUMLの話をします。

PlantUMLの導入

みんな好きですよね、設定しましょう

1. 必要なツールを落とす

brew install graphviz
brew install plantuml
brew install maven

2. vscode拡張機能を入れる

  • PlantUML

これだけも利用を始めることができます。xx.pu というファイルを作り、Preview Current Diagram を実行すればUMLを表示することが可能です。ただxx.pu のファイルを画像に変換するときにこの拡張機能はリモートのサーバを見ているのでレンダリングがかなり遅いです。そのためローカルのマシン上にPlantUMLのサーバを立てます。

3. ローカルでPlantUMLサーバを動かす

git clone https://github.com/plantuml/plantuml-server.git
cd plantuml-server
mvn jetty:run -Djetty.port=10001

jettyのデフォルトのportが他と被りやすいので明示的にport指定します。 このプロセスが動いていないとレンダリングには失敗します、起動は少し時間がかかります

4. 拡張機能の設定変更

ここから f:id:jumbos5:20200721093242p:plain

ここを変更 f:id:jumbos5:20200721093222p:plain

この辺を設定する。 RenderとServerの値を変更して下さい。

5. markdownでプレビューする

.md内でプレビューしたい場合は上記の設定を終了後に#4の設定で対象とする拡張子指定で md を指定できるので、ここから設定しておくと設定されたローカルサーバを参照してマークダウン内でもプレビューされます。

f:id:jumbos5:20200721093624p:plain

tips

お手軽に試したいようであればこちらのプラグインでもプレビュー可能です。ただ外部サーバにリクエストしているようなのでレンダリングが遅かったり、またセキュリティ上も微妙かなと思います。 shd101wyy.github.io

また前回弊社のいとうさんが記事を書いてくださり、GitLabでPlantUMLをプレビューできると書いてくれています。これは便利なんですが、!include 機能がGitLabでは現在まだ利用できないように見えるので、これはただ期待するばかり。

用法用量を守って楽しくUMLしましょう。

Figmaでレアジョブコーポレートサイトのデザインをリニューアルした話

こんにちは、デザイナーのキョウです!
2回目の投稿ですが、ついこの前リニューアルしたばかりのレアジョブコーポレートサイトの話をちょっとしようかなと思います!

リニューアルの背景


まずは、今回のリニューアルの背景としては、

  • デザイン、コード共に古い
  • デザインが端末ごとに最適化されていない(レスポンシブ対応がされていない)
  • 運営更新に限界を感じている

会社のイメージアップと今後のビジョンのために、そろそろリニューアルしたいねという話になりまして、今回のリニューアルプロジェクトが開始したわけです。

作成のボリュームは、PCデザイン画面38ページ、スマホデザイン画面38ページといった感じで、構築はWordPress上で、制作期間は2ヶ月ちょっとという短い期間でした。

制作の過程について


今回のリニューアルプロセスとして、こういった感じで進めていました。

f:id:kyou_setsu:20200702152032p:plain

デザイン作成からサイト公開するまでの納期時間は短かったため、今回のデザイン作成は自社で、コーディングとサイト構築はパートナー会社に頼む形になりました。

そこで、今回もFigmaというデザインツールが大活躍してくれました!

まずは何がよかったというと、フィードバックのしやすさ、パートナー会社とのコミュニケーションのとりやすさ、自社の確認者(広報の担当者)とデザイナー(私)とサイト構築作業者(パートナー会社)の三者の間の情報共有はすべてFigma上で実現することができ、確認作業はとてもスムーズで、時間短縮にはかなり役に立っていたと感じましたね。

今までなら、パートナー会社とやり取りする際は、デザインデータを渡したり、デザイン修正する度に新しいデザインデータを書き出して渡したり、また修正指示を出す時は、画面のキャプチャをいっぱい撮って修正箇所のコメントを書いたり、指示書を作ったり、メールで送ったり、かなりめんどくさかったんですが、今回はこのようなやり取りをすべてFigma画面上で実現することに当たって、デザイン画面をリアルタイムで共有し、デザインの修正が発生してもURLが変わらないので、広報の担当者とパートナー会社は常に最新のデザインが確認できます。

また、Figmaのコメント機能を利用すれば、パートナー会社に修正してほしいところを画面上に直接指示を出すことができ、パートナー会社も修正指示にすぐ気付いてくれたり、修正のレスポンスはとにかく早いです! お互いに質問があった時も、すぐコメントで書いたり返信したり、効率はとてもいいですね!

実際にFigmaのコメント機能を利用して修正依頼のやりとり例として:

f:id:kyou_setsu:20200703154442p:plain

新しいデザインのあれこれ


そして完成した新しいコーポレートサイトはこちらです!
トップページはこんな感じに変わりました↓

f:id:kyou_setsu:20200701160510p:plain


■今回のリニューアルで特に意識して力を入れたポイント

今回のリニューアルはなんと言って、今までと全然違うイメージを与えたくて、
初めてこのサイトを訪れる人に印象が残りやすいように、初めてではない方にイメージアップとインパクト力を感じるように、おしゃれ感とスタイリッシュ感の演出に意識してデザインを作成しました。
例えば、今流行りのグラデーションカラーを取り入れたり、文字サイズを大きめにし、メリハリを付けたり、コンテンツ間の余白を広めにとったり、シンプル且つ見やすいデザインに仕上げました。

また色合いの調整について、レアジョブの代表色は元々薄い黄緑で、そのまま使うと、結構見づらかったり、ユーザービリティの悪いページになってしまうため、いかにブランドカラーのイメージを活かしながらバランスの良い色合いに仕上げるために、いろいろ試行錯誤を重ねてきました。

f:id:kyou_setsu:20200701114400p:plain



■デザインで苦労した点について

サイト全体のページ数はそれなりに多く、また各ページのコンテンツ内容もそれぞれ違うので、分かりやすいレイアウトにするために、かなり悩みましたね。

プレスリリースなどのニュース系記事ページや、投資家情報を発信するためのIRページなど、情報量は多いため、細かくカテゴリー分けにしたり、各カテゴリーに移動しやすいようにページ内リンクを作ったり、いろいろ工夫をしました。
あとは、役員紹介ページの写真の統一感を出すため、役員陣の皆さまがかっこよくみえるように、写真のレタッチやレイアウトの調整を何度も行いました。

f:id:kyou_setsu:20200701120433p:plain

最後にちょっとした余談 (・ω・)


今回のリニューアルの話がきた時に、私の中ではかなりテンションが上がってました。何故なら、とてもやりがいを感じていたのと、せっかくなので攻めたデザインで行こうというワクワク感が、私の心の中でひっそり湧いてきたからです(笑)

初めてこのサイトを訪れた人には、この会社は明るくて、元気で、そしてチャレンジ精神旺盛だと思っていただけるよう、自分なりに精一杯レアジョブらしさを伝えようという想いで、今回のデザインリニューアルを行いました。

任意のサイトにあるaタグを別タブで開く

こんにちは。サービス開発チームのすずきです。

早速ですが、皆さんは外部リンクを別タブで開きたい派ですか?私は開きたい派です。 リンクを別タブで開くためにcommandを押しながらリンクをクリックするのですが、 毎回commandキーを押しながらクリックするのは少し手間です。 commandキーを押し忘れたら画面遷移してしまい、わざわざブラウザバックしなければいけません。

そこでChrome拡張機能であるScriptAutoRunnerを利用して、aタグのtarget属性に_blankを付与するようにしました。

github.com

利用方法は開発者のGithubに譲りますが、 以下のようにjsを記述することで、外部リンクを別タブで開くようにしました。

var anchors = document.getElementsByTagName('a');
for (var i = 0; i < anchors.length; i++) {
    var anchor = document.createElement('a');
    anchor.href = anchors[i].href;
    if (location.hostname !== anchor.hostname
        && anchor.href !== 'javascript:void(0);') {
        anchors[i].target = "_blank";
    }
}

では!

Airを使ったGo環境でのホットリロード

こんにちは。

つい先日誕生日だったのですが、昨今のアレコレのせいで独り寂しくお祝いしました。
アレコレが無ければきっと盛大にお祝いして貰えたはずだったのですが……
去年も一昨年もその前も独りでお祝いしたかもしれませんが、可能性は0では無いです。
サプライズパーティの準備をしていた方、大変申し訳ありません。
尚、私のカバンにはまだ若干の余裕があることだけはお伝えしておきます。

ホットリロード is 何?(実はライブリロード)

簡単にいうと「変更をすぐに反映してテストを行いやすくする機構」です。
修正がリアルタイムに反映されるためデバッグなどの作業が効率的に行えます。

実はホットリロードと言いつつ今回の話はホットリロードでは無いです。
何言うてるねんと言われそうですがGoは変更箇所の適用のため全体を読み直す為
「ホットリロード」ではなく「ライブリロード」になります。

  • 変更箇所だけ読み込んで反映するのがホットリロード
  • 変更箇所含めて全て読み込んで反映するのがライブリロード

と言うことだけ覚えておけば大丈夫です。
でも皆さんホットリロードと言ってる気がするのでどっちでもいい気がします。

伝わればいいのだ伝われば。

Go環境のホットリロードはRealizeが一番有名ですが、
現在ほぼ開発が止まっており、最新のmodulesを使う環境下ではエラーが出るなどから
代替を探して見つけたのがAirです。

とりあえずデフォルトで試す

何はともあれインストール

$ curl -fLo air https://git.io/darwin_air
$ chmod +x air
$ mv air /usr/local/bin 

Downloadしたairに実行権限をつけてパスが通っているところに移動させています。

次にテスト実行用の雑なsampleを用意

package main

import "fmt"

func main() {
    fmt.Println("Hello world!")
}

対象と同じディレクトリでAirを起動 f:id:dedekopon:20200626120731g:plain

Airの監視対象のファイルを修正する

package main

import "fmt"

func main() {
    fmt.Println("Hello world!")
    fmt.Println("Live reload!")
}

変更が反映され自動でリビルドされる f:id:dedekopon:20200626120739g:plain

まとめ

とりあえず動作確認しながらの開発には非常に便利そう。
実際はコンフィグを設定したりdocker上で動かしたり等もう一手間必要になりそうです。
コンフィグはexampleがあるのでそちらを参考に設定してみてください。