Metabaseをコンテナで立ち上げてみた

こんにちは。 エキサイト株式会社の三浦です。

今回は、Metabaseをコンテナで立ち上げてみた話をしていきます。

Metabaseとは

MetabaseはBIツールの一つで、社内のデータを可視化するのに役立ちます。

www.metabase.com

  • Dockerを使って簡単に起動できる
  • 様々なデータソースと接続できる

など、使いやすいツールです。

今回はこのMetabaseを、実際にコンテナで立ち上げてみます。

コンテナで立ち上げる

それでは、実際に立ち上げてみます。

とは言っても、 docker compose を使えば以下の設定だけで完了です。

version: '3.8'

services:
  metabase:
    image: metabase/metabase:v0.49.0
    environment:
      MB_DB_TYPE: postgres
      MB_DB_HOST: postgres
      MB_DB_DBNAME: bi_tool
      MB_DB_PORT: 5432
      MB_DB_USER: bi_tool
      MB_DB_PASS: bi_tool_pw
    ports:
      - "3000:3000"

  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: bi_tool
      POSTGRES_USER: bi_tool
      POSTGRES_PASSWORD: bi_tool_pw
    ports:
      - "5432:5432"

細かく見ていきます。

本質的には、Metabaseの設定は以下だけです。

version: '3.8'

services:
  metabase:
    image: metabase/metabase:v0.49.0
    ports:
      - "3000:3000"

実はこれだけで、Metabaseを立ち上げることが出来ます。

ただしこの場合、Metabaseの設定はローカルに保存されることとなります。 コンテナとして立ち上げる場合、コンテナを立ち上げ直すたびに設定が消えてしまうのは使い勝手が悪いので、設定をローカル以外の場所に保存する必要があります。

今回はPostgreSQLに保存するようにしており、そのために残りの設定をしています。

version: '3.8'

services:
  metabase:
    image: metabase/metabase:v0.49.0
    environment:
      MB_DB_TYPE: postgres
      MB_DB_HOST: postgres
      MB_DB_DBNAME: bi_tool
      MB_DB_PORT: 5432
      MB_DB_USER: bi_tool
      MB_DB_PASS: bi_tool_pw
    ports:
      - "3000:3000"

  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: bi_tool
      POSTGRES_USER: bi_tool
      POSTGRES_PASSWORD: bi_tool_pw
    ports:
      - "5432:5432"

postgres サービスにて、PostgreSQLのコンテナを立ち上げています。 DB名やユーザ名・パスワードは任意のもので構いません。

そして、 metabase サービスの environment にて、立ち上げたPostgreSQLに設定を保存するようにしています。

各種データは、先に立ち上げたPostgreSQLの設定に合わせるようにしましょう。

これで設定は完了です! 実際に立ち上げてみます。

http://localhost:3000/

無事、立ち上げることができました!

もちろん、Metabaseのコンテナのみ削除・再立ち上げしても、設定は失われません。

最後に

BIツールによるデータ可視化は、サービスを運営していく上で非常に有用です。

Metabaseは上記のように非常に簡単に立ち上げることができるので、ぜひ使ってみると良いのではないでしょうか。

参考

[ データ可視化ツール]MetabaseをDocker上で構築してRedshiftへ接続する | DevelopersIO

docker composeでmetabaseを構築する | mebee

Metabaseの設定情報をPostgreSQLに保存 - suzuki-navi’s blog

SpringBoot3 x Thymeleaf で標準のレイアウトを使用する

エキサイト株式会社メディア事業部エンジニアの佐々木です。SpringBoot3でMPAアプリケーションを開発する場合に、Thymeleafテンプレートを使用することは、ほぼデファクトになるかと思います。今回はThymeleafのフラグメントを使用した簡単なレイアウトファイルの作成をご紹介します。

前提

$ java --version
openjdk 21.0.2 2024-01-16 LTS
OpenJDK Runtime Environment Corretto-21.0.2.13.1 (build 21.0.2+13-LTS)
OpenJDK 64-Bit Server VM Corretto-21.0.2.13.1 (build 21.0.2+13-LTS, mixed mode, sharing)

$ ./gradlew --version

Welcome to Gradle 8.6!
------------------------------------------------------------
Gradle 8.6
------------------------------------------------------------

Build time:   2024-02-02 16:47:16 UTC
Revision:     d55c486870a0dc6f6278f53d21381396d0741c6e

Kotlin:       1.9.20
Groovy:       3.0.17
Ant:          Apache Ant(TM) version 1.10.13 compiled on January 4 2023
JVM:          21.0.2 (Amazon.com Inc. 21.0.2+13-LTS)
OS:           Mac OS X 12.5 aarch64

./gradlew bootRun

> Task :bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.3)

設定ファイル

build.gradleは下記にように設定されています。

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.3'
    id 'io.spring.dependency-management' version '1.1.4'
}

group = 'jp.co.excite'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '21'
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

bootRun {
    sourceResources sourceSets.main
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // Thymeleafを使用するモジュール
    implementation 'org.springframework.boot:spring-boot-starter-web'  // Webが表示できるようにするモジュール
}

tasks.named('test') {
    useJUnitPlatform()
}

今回のファイル構成

下記は、Thymeleafに必要な部分のみになります。

project_root
└ src
   └ main        
     └ resoureces
       └ templates
         └ index.html
         └ layoutFile.html  

エンドポイントの設定

下記のようなコントローラーを設定します。リクエストがきたら、index.htmlテンプレート返却するような処理になります。

@Controller
@RequestMapping("")
public class RootController {

    @GetMapping("")
    public String index(){
        return "index";
    }
}

レイアウトHTMLを作成する

レイアウト用のHTMLを作成します。ファイル名は、layoutFile.htmlとします。レイアウトファイルでは、 th:fragment を使用して構築していきます。

layoutFile.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"  th:fragment="layout(title, content, header, footer)"> <!--  th:fragment を宣言します -->
<head>
    <meta charset="UTF-8">
    <title th:replace="${title}">Title</title> <!-- th:fragmentで宣言したフラグメント名layoutの title を利用する -->
</head>
<body>
<header>
    <h1 th:replace="${header}">ヘッダーです</h1> <!-- th:fragmentで宣言したフラグメント名layoutの header を利用する -->
</header>
<hr/>
<main>
    <article>
        <h2>メインのコンテンツです。</h2>
        <div th:replace="${content}"> <!-- th:fragmentで宣言したフラグメント名layoutの content を利用する -->
            demo用のコンテンツです
        </div>
    </article>
</main>
<hr/>
<footer>
    <b>フッターです</b>
    <div>
       <p th:insert="${footer}">Copyright &copy; excite.co.jp demo</p>  <!-- th:fragmentで宣言したフラグメント名layoutのfooterを利用する -->
    </div>
</footer>
</body>
</html>

th:fragment="layout(title, content, header, footer)として4つの引数を取ることができますが、この値を置き換えたいHTMLタグにth:replaceth:insertを使用し設定します。

テンプレートを利用する側のHTML

続いては利用する側のHTMLを書いていきます。ファイル名は index.html とします。 利用する側は、 th:replace を使用します。このときに 前項で作成したlayoutFile.htmlを読み込みながら、layoutフラグメントをそれぞれの値で書き換えいくように設定します。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layoutFile::layout(~{::title}, ~{::#content}, ~{::index_h1}, _)}">
<head>
    <meta charset="UTF-8">
    <title>sample sample</title>
</head>
<body>
<header>
  <h1 th:fragment="index_h1"> index.htmlのh1です </h1>
</header>
<div id="content">
    <p>コンテンツの中身です。</p>
</div>

</body>
</html>

th:replace="~{layoutFile::layout(~{::title}, ~{::#content}, ~{::index_h1}, _)}" これの説明をします。

  1. th:replaceで置き換える指示をします。
  2. ~{layoutFile::layout(〜〜〜) layoutFile.htmlのlayoutフラグメントを呼び出します。下記は、layoutの引数の中身を解説します。
    1. ~{::title} titleタグを引数に入れます
    2. ~{::#content} HTML内の id="content" を引数に入れます
    3. ~{::index_h1} HTML内の th:fragment="index_h1"を引数に入れます
    4. _ トークンなし引数を渡します。Thymeleafとして処理せずに記述してあるlayoutFile.htmlに書いてあるHTMLをそのまま出力してくれます。

出力は下記になります。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>sample sample</title>
</head>
<body>
<header>
    <h1>index.htmlのh1です</h1>
</header>
<hr/>
<main>
    <article>
        <h2>メインのコンテンツです。</h2>
        <div id="content">
    <p>index.htmlのコンテンツの中身です。</p>
</div>
    </article>
</main>
<hr/>
<footer>
    <b>フッターです</b>
    <p>Copyright &copy; excite.co.jp demo</p>
</footer>
</body>
</html>

layoutFile.htmlの指定した場所のみの書き換えができました。

まとめ

Thymeleafは標準レイアウトのみで、上記のようなレイアウトファイルを使用した作りが可能です。参照もHTMLタグでもidでも指定可能なので割と柔軟性はあるかと思います。MPAで開発する際には活用したいところです。

最後に

エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しております。長期インターンも歓迎していますので、興味があれば連絡いただければと思います。

募集職種一覧はこちらになります!(カジュアルからもOK) www.wantedly.com

OpenAPI Generatorを利用して自動生成したJavaのAPIクライアントをローカルリポジトリに保存する

こんにちは、エキサイト株式会社の平石です。

今回は、OpenAPI Generatorを利用して自動生成したJavaAPIクライアントを独自のローカルリポジトリに保存する方法をご紹介します。

なお、今回の方法は例として「OpenAPI Generatorを利用して自動生成したJavaAPIクライアント」を挙げているだけで、それ以外の自作のライブラリ等でも利用できます。

はじめに

OpenAPI Generatorを利用してJavaAPIクライアントを自動生成する基礎については、以下のブログ記事でまとめていますので、こちらをご覧ください。

tech.excite.co.jp

実際のサーバー環境上で運用する際の問題点

上で紹介したブログ記事では、生成したクライアントをローカルのMaven Repositoryにインストールし、利用したいプロジェクトで依存関係を追加して利用しました。

ローカルのMaven Repositoryはデフォルトでは、$USER/.m2/repositoryに存在しています。
ローカルで開発している間は良いのですが、サーバー環境上にデプロイした際にはこのRepositoryをそのまま使うことはできませんので、何か策を講じなければなりません。

ここからは、上記の問題を解決していきます。

独自のMaven Repositoryを利用する

この問題の解決方法としては、独自のMaven Repositoryを利用するように設定することが挙げられます。

そもそも、Maven Repositoryは以下のような構成になっていれば、どこに配置していても利用することが可能です。

/{repository_root}/{group ID}/{artifact ID}/{version}/

group ID, artifact ID, versionは、pom.xmlを利用して設定や依存関係を記述している人なら馴染み深いでしょう。
例えば、OpenAPIをSpring Bootで利用するための、Springdocというライブラリを追加する際には、pom.xmlに以下のように記述します。

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.3.0</version>
</dependency>

つまり、この設定はMaven Repositoryの

/{repository_root}/org/springdoc/springdoc-openapi-starter-webmvc-ui/2.3.0

配下にあるライブラリを依存関係として追加するということを表しているのです。

そして、この配下に主に以下の4つのファイルが配置されています。

  • {artifact ID}-{version}.pom
  • {artifact ID}-{version}.jar
  • {artifact ID}-{version}-sources.jar
  • {artifact ID}-{version}-javadoc.jar

逆に言えば、この構成を好きな場所に再現して利用すれば良いのです。

この「好きな場所」は、別のサーバー上やAWSのS3のようなオブジェクトストレージでも可能です。

build.gradleから参照する際には、以下のように記述しておけば、ライブラリを探すときにこの中を見てくれるようになります。

repositories {
        mavenCentral()

        maven {
            url {利用したいMaven RepositoryのURL}
        }
}

なお、ライブラリを探す際には、repositoriesで指定した順番で探しに行くようです。
したがって、mavenCentral()から取得するライブラリが多い場合には、こちらを上に記述することで、ビルド時間を短縮することが可能です。

独自のライブラリを多く利用する必要がある場合には、別のサーバー上やS3でMaven Repositoryを管理するメリットがあります。
しかし、当分今回作成したAPIクライアントしか利用する予定がないような場合には、サーバーやS3に保存しておく方法は作成や管理の手間がかかってしまいます。
そのような場合には、Maven Repositoryをプロジェクト内に含めて、まとめてデプロイしてしまう方法も選択肢に入ります。
JARファイルは圧縮されているため、ファイルサイズ的には問題にはならないでしょう。

今回は、こちらの方法をご紹介します。
といっても、やることは簡単です。

基礎編では、以下のコマンドでLocal RepositoryにAPIクライアントをインストールしていました。

mvn clean install

先ほど述べたように、このままではデフォルトのローカルリポジトリである$USER/.m2/repository配下にインストールされてしまいます。

そこで、-Dmaven.repo.localオプションを付与することで、一時的にLocal Repositoryの場所を変更することができます。

mvn clean install -Dmaven.repo.local=./{path_to_local_repository}

{path_to_local_repository}の部分にご自身が設定したいlocal_repositoryの場所を指定してあげることで、その場所にAPIクライアントがインストールされます。

ただし、このままでは生成したAPIクライアント以外にも不要なライブラリもインストールされてしまいます。 (このAPIクライアントのpom.xmlで設定されているライブラリか?)

これらは、実際にAPIクライアントを利用する上では不要なので、APIクライアントを含んだディレクトリだけをコピーして適当なディレクトリにペーストしておきます。

例えば、以下のようなディレクトリ構成となっていおり、clientgenディレクトリにAPIクライアントが自動生成されているとします。

この場合には、以下のようなコマンドを実行することで、local_repositoryにプロジェクトで利用するAPIクライアントのみを含んだMaven Repositoryを構成することができます。
あらかじめ、local_repository配下にcom/exampleという空ディレクトリを作成しておいてください。 (現在、プロジェクトルートにいるとします。)

cd clientgen
mvn clean install -Dmaven.repo.local=./local_repository_temp
cp -rf ./local_repository_temp/com/example/openapi-java-client ./../local_repository/com/example

次に、ライブラリを取得する先のMaven Repositoryを設定します。

repositories {
    mavenCentral()
    maven {
        url "$rootDir/local_repository"
    }
}

依存関係を追加します。

dependencies {
    〜〜 略 〜〜

    implementation "com.example:openapi-java-client:0.0.1-SNAPSHOT"
}

これで、プロジェクト内でAPIクライアントを利用できるようになりました。
自動生成したAPIクライアントそのもの(clientgen配下)やlocal_repository_tempディレクトリは.gitignoreに記述する(Gitを利用している場合)か、削除しておくと良いでしょう。

おわりに

今回は、自動生成したAPIクライアントをプロジェクト内の自作のMaven Repositoryに保存してサーバー環境上でも利用できるようにするための設定方法をご紹介しました。

では、また次回。

LEFT JOINでハマった話

こんにちは、エキサイト株式会社の平石です。

今回は、初歩的な内容でありながら、SQLLEFT JOINを利用した際にハマったことを記事にしたいと思います。

なお、本ブログのSQLMySQL 8.2で動作確認をしています。

以下のような3つのテーブルがあるとします。

  • sample_userテーブル・・・ユーザーIDとユーザ名を管理
  • prefectureテーブル・・・都道府県IDと都道府県名を管理
  • user_prefectureテーブル・・・ユーザーの居住する都道府県を管理

そして、これらのテーブルにはそれぞれ以下のようなレコードが入っているものとします。

sample_userテーブル

user_id name
1 佐藤
2 鈴木
3 高橋
4 太田
5 山本
6 中川

prefectureテーブル

prefecture_id prefecture_name
1 東京
2 神奈川
3 岡山

user_prefectureテーブル

user_id prefecture_id
1 1
4 1
5 2
6 3

INNER JOINの場合

まずは、関東圏に住んでいるユーザーのみを、都道府県名とともにSELECTすることを考えます。

そのためのSQLは、例えば以下の通りです。

SELECT 
    sample_user.user_id, 
    sample_user.name, 
    prefecture.prefecture_name

FROM sample_user

INNER JOIN user_prefecture
    ON user_prefecture.user_id = sample_user.user_id

INNER JOIN prefecture
    ON prefecture.prefecture_id = user_prefecture.prefecture_id

WHERE prefecture.prefecture_id IN (1, 2);

都道府県を登録していないユーザーは無視して良いのでINNER JOINを利用してsample_userテーブル、user_prefectureテーブル、prefectureテーブルを結合しています。
そして、WHERE句で関東圏の都道府県(ここでは、東京と神奈川)を指定しています。

結果は以下の通りです。

user_id name prefecture_name
1 佐藤 東京
4 太田 東京
5 山本 神奈川

先ほどは、都道府県の条件をWHERE句で指定しましたが、prefectureとの結合条件に含めることで見通しが良くなります。

SELECT 
    sample_user.user_id, 
    sample_user.name, 
    prefecture.prefecture_name

FROM sample_user

INNER JOIN user_prefecture
    ON user_prefecture.user_id = sample_user.user_id

INNER JOIN prefecture
    ON prefecture.prefecture_id = user_prefecture.prefecture_id
    AND prefecture.prefecture_id IN (1, 2);
user_id name prefecture_name
1 佐藤 東京
4 太田 東京
5 山本 神奈川

LEFT JOINの場合

ここからが本題です。
次は、関東圏に住んでいるユーザーと都道府県を登録していないユーザーをSELECTします。
ただし、都道府県を登録しているユーザーはその都道府県名とともに取得するとします。

そのためのSQLは、INNER JOINLEFT JOINに変更すれば良さそうです。

SELECT 
    sample_user.user_id, 
    sample_user.name, 
    prefecture.prefecture_name

FROM sample_user

LEFT JOIN user_prefecture
    ON user_prefecture.user_id =sample_ user.user_id

LEFT JOIN prefecture
    ON prefecture.prefecture_id = user_prefecture.prefecture_id
    AND prefecture.prefecture_id IN (1, 2);

しかし、このSQLではうまくいきません。

user_id name prefecture_name
1 佐藤 東京
2 鈴木
3 高橋
4 太田 東京
5 山本 神奈川
6 中川

user_id = 6の中川は岡山県在住ですが、都道府県を登録していないユーザーであるかのように取得してしまっています。
INNER JOINの場合は結合条件に記述することでうまくいきましたが、これは一体どういうことでしょうか。

とはいえ、この理由はLEFT JOINの定義から明らかです。
LEFT JOINは結合条件(ON以下の文)に合致したレコード同士を結合した上で、合致しなかった場合も結合の「左側」のテーブルのレコードは全て残します。
この時、結合の「右側」のテーブルのカラムは全てnullとして扱います。

この例の場合は、「鈴木」、「高橋」は

user_prefecture.user_id = sample_user.user_id

となるレコードがuser_prefectureに存在しません。
また、「中川」も2つ目の結合で結合条件prefecture.prefecture_id IN (1, 2)に合致しないため、prefectureテーブルのカラムがnullになった上で選択されます。

prefecture.prefecture_id IN (1, 2)が先に実行され、prefectureテーブル内のprefecture_idが1, 2のレコードが残った状態でLEFT JOINが実行されると、勝手に勘違いしてしまっていました.....。

では、「中川」が選択されないようにするためにはどうすれば良いのでしょうか。
今回の場合は、WHERE句に条件を記述する必要があります。
当然ながらINNER JOINの時と同様に

WHERE prefecture.prefecture_id IN (1, 2)

と記述すると、都道府県を登録していないユーザーが取得されません。

都道府県を登録していない場合と、都道府県を登録している場合で場合分けをして条件を記述する必要があります。

WHERE prefecture.prefecture_id IS NULL
    OR prefecture.prefecture_id IN (1, 2);
user_id name prefecture_name
1 佐藤 東京
2 鈴木
3 高橋
4 太田 東京
5 山本 神奈川

CASE式を利用することもできます。

WHERE CASE
          WHEN prefecture.prefecture_id IS NOT NULL
          THEN prefecture.prefecture_id IN (1, 2)
          ELSE TRUE
      END;

終わりに

今回は、LEFT JOINでハマった内容をブログとして残しました。
定義に立ち返ると何ということはない話なのですが、INNER JOINと同じ感覚で使ってしまうと思わぬバグを生むことになりそうです。

では、また次回。

PHP アプリケーションの FCM HTTP v1 API 移行手順

エキサイト株式会社の@mthiroshiです。

アプリのプッシュ通知は、Firebase Cloud Messaging を用いて実装できます。サーバー環境から FCM 実装でプッシュ通知を送る方法の一つとして、FCM HTTP API があります。現在、FCM HTTP API は、新しいAPIへの移行のアナウンスがされています。2024年6月にはレガシーAPIが利用できなくなります。

firebase.google.com

今回は、PHP の実装における FCM HTTP API v1 への移行について紹介します。

公式の移行ガイドによると、主な変更点は下記の3点です。

作業手順に沿ってこの3点を変更していきます。

サービスアカウントの準備

まず、送信リクエストの認可の方法が変更されています。 Firebase と連携しているGCPコンソールからからサービスアカウントを作成し、秘密鍵を生成します。

GCPコンソールでサービスアカウントの秘密鍵を生成する

秘密鍵を生成すると、JSONファイルがローカルにダウンロードされます。このJSONファイルには、秘密鍵が含まれているため、安全に管理してください。

そして、このサービスアカウントに対して、Firebase Admin SDKのロールを割り当てます。

サービスアカウントに Firebase Admin SDK のロールを割り当てる

JSON ファイルを PHP アプリケーションが動作しているサーバーに設置し、環境変数 GOOGLE_APPLICATION_CREDENTIALS にそのパスを設定します。

export GOOGLE_APPLICATION_CREDENTIALS="/home/demo_user/service-account-file.json"

環境変数を設定することで、後述する Google API クライアントが認可する際に、環境変数から JSON ファイルを読み込んでくれます。

Google が提供するAPIやライブラリを利用する際には、アプリケーションのデフォルト認証情報(ADC)と呼ばれる仕組みが使われます。今回の環境変数の設定は、ADCに則った方法です。 cloud.google.com

PHP アプリケーションの対応

プッシュ通知実装の実体として、Google API を使って FCM サーバーにリクエストを行います。 Google API を利用するため、PHPGoogle API クライアントのライブラリを composer を使ってインストールします。 github.com

下記が Google API クライアントを使ったプッシュ通知実装のサンプルコードです。

<?php

require_once 'vendor/autoload.php';

$client = new Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope('https://www.googleapis.com/auth/firebase.messaging');
$httpClient = $client->authorize();

$params = [
    'message' => [
        'topic' => 'demo_topic',
        'notification' => [
            'title' => 'demo_title',
            'body' => 'demo_body',
        ],
        'data' => [
            'demo_contents_id' => '12345',
        ],
    ],
];

$project = 'demo_firebase_project_id';
$response = $httpClient->post("https://fcm.googleapis.com/v1/projects/$project/messages:send", 
    ['json' => $params]
);

GoogleClient クラスをインスタンス化します。

次に、useApplicationDefaultCredentials() によって先程の環境変数を読み込みます。

Cloud Messagingを利用するため、firebase.messaging のスコープを追加して、 FCM へのアクセスを認可します。

authorize() で認証を実行し、GuzzleHttp の ClientInterface を返却します。

$params は送信リクエストのペイロードです。こちらも移行の変更点でして、以前の API から JSON のフォーマットが変更されています。詳しくは、下記のドキュメントをご覧ください。

REST Resource: projects.messages  |  Firebase Cloud Messaging REST API

最後に、GuzzleHttp クライアントである $httpClient から POST リクエストをして、プッシュ通知のリクエストを行います。 このAPIエンドポイントも変更点となっていまして、パスに Firebase プロジェクトの ID が含まれています。

最後に

PHP実装におけるFCM HTTP v1 API の移行手順について紹介しました。サービスアカウントにロール割り当てができていない場合、プッシュ通知をリクエストした際に権限不足のエラーが出ることがありますので、注意が必要です。

2024年6月にレガシーAPIは利用できなくなりますので、計画的な移行をおすすめします。 参考になれば幸いです。

採用情報

エキサイトではエンジニアを随時募集しております。ご興味ございましたら、下記の募集一覧ページをご覧ください!

www.wantedly.com

参考記事

以前の HTTP から HTTP v1 に移行する  |  Firebase Cloud Messaging

Google API PHP Client - Firebase Cloud Messaging Service v1 example · GitHub

Amazon Aurora MySQLの証明書を更新した話

こんにちは。 エキサイト株式会社の三浦です。

AWSのAurora MySQLにはサーバー証明書が組み込まれていますが、以前デフォルトとして設定されていた rds-ca-2019 がもうすぐ有効期限を迎えます。

今回は、この証明書を更新した話をしていきます。

準備

実際に更新する前に、まず以下の2点を確認しました。

アプリケーションコードで検証のために証明書を使っていないか

アプリケーションコードからDBに接続する際、アプリケーションコード側で証明書を使うことにより、厳密にDBの証明書をチェックをすることができます。

この厳密な証明書チェックをしている場合は、DBの証明書の更新に合わせてアプリケーションコード側で使用している証明書も更新しておく必要があります。

なお、例えばJavaのDB接続ライブラリであるJDBCでは、デフォルトは以下の動作になっています。

  • DBの証明書の有効期限が切れていないかチェックする
  • ただし、わざわざアプリケーションコード側で証明書を使ってまで厳密にはチェックしない

そのため、デフォルトの使い方であればアプリケーションコード側の証明書の更新はする必要がありません。

docs.aws.amazon.com

dev.mysql.com

アプリケーションコードでどのような証明書チェックをしているのかを確認し、適切に対応しましょう。

証明書更新時、DBの再起動が必要かどうか

証明書を更新する際、DBの種類やバージョン等によって再起動の必要性が変わってくるようです。

docs.aws.amazon.com

「証明書の更新」ページから必要性がチェックできます。

再起動が必要な場合は、再起動時にサービスに問題が生じないよう適切に対処しましょう。

証明書更新実行

上記の2つのチェックが完了したので、続いて実際に証明書を更新しました。

準備段階をしっかりしていればそこまで気にすることはありませんが、1つ迷いそうな箇所がありました。

証明書の更新はインスタンス単位

証明書は、クラスタ単位ではなくインスタンス単位で更新する必要があります。

クラスタから変更しようとしても変更項目にないので注意しましょう。

以上で証明書更新は終了です!

最後に

証明書の更新、やってみると意外と簡単でした。

特に準備段階で、アプリケーションコードで証明書を使っておらず、かつ更新時に再起動が不要であれば、実質ポチポチと更新するだけで終わります。

いつかはやらなくてはならないものなので、思い切って早めに終わらせてしまいましょう!

Spring Bootで@CacheEvictを使ってキャッシュを削除する

こんにちは、エキサイト株式会社の平石です。

今回は、Spring Bootで一度作成したキャッシュをTTLが過ぎる前に明示的に削除する方法をご紹介します。

キャッシュを削除したいとき

キャッシュという仕組みでは、DBなどの情報源にアクセスした結果を高速にアクセスできる領域に保存しておきます。
そして、同じアクセスが来たときにもう一度情報源に問い合わせるのではなく、結果を保存した領域からデータを取得します。
このようにすることで、情報源の負荷を軽減したり、レスポンスを高速にしたりすることができます。

便利な仕組みですが、DB等内の情報が更新されてもキャッシュが削除されるまでは同じデータを返し続けてしまうという欠点もあります。
特に、普段は滅多に更新されないデータは、TTL(キャッシュを保持する期間)を長めにしておくことが多く、その場合情報源での更新がなかなか反映されません。

このような場合に対応するために、キャッシュを明示的に削除する方法をご紹介します。

なお、今回はデータへの変更を加える操作もJavaで行われるという前提で使える方法をご紹介します。

環境

今回のブログにおけるソースコードはSpring Boot v3.2.2, Java 21で動作確認をしています。

以下のような、ニュース記事のIDとその記事のタイトルを管理する場合を考えます。
なお、例をシンプルにするためDB等は使わずにデータはハードコーディングしています。

@Service
@RequiredArgsConstructor
public class NewsArticleServiceImpl implements NewsArticleService {

    private final Map<Integer, String> newsArticleTitleMap = new HashMap<>() {
        {
            put(1, "〇〇株式会社が上場");
            put(2, "〇〇地方で大雪");
            put(3, "〇〇が電撃結婚");
        }
    };

    @Override
    @Cacheable(cacheNames = "newsCache")
    public String putCache(final Integer newsId) {
        return newsArticleTitleMap.getOrDefault(newsId, "");
    }
}

putCacheメソッドをnewsId引数と共に呼ぶと、対応するnewsIdを持つ記事のタイトルが返され、設定した「データ領域」にキャッシュが行われます。(詳細は省きますが、今回は裏でRedisを使っています。)

テストのために、3つの記事タイトルのキャッシュをするためのエンドポイントを用意して、呼び出してみます。

@RestController
@RequestMapping("cache/sample")
@RequiredArgsConstructor
public class CacheSampleController {
    private final NewsArticleService newsArticleService;

    @GetMapping("put")
    public void putCache() {
        newsArticleService.putCache(1);
        newsArticleService.putCache(2);
        newsArticleService.putCache(3);
    }
}

valueはSerializeされ、かつ一覧では一部しか表示されていませんが、確かにキャッシュがされているようです。
keyで、newsCache::の後にある数字はnewsIdで、以後例えばnewsId=1でアクセスがあった場合には、newsId::1のエントリからデータを取得してメソッドの返り値として返します。

@CacheEvictでキャッシュを削除する

それでは、キャッシュを削除してみます。
キャッシュを削除するには、@CacheEvictというアノテーションを利用します。
@Cacheableを利用するために追加する'org.springframework.boot:spring-boot-starter-cache'と同じライブラリに含まれているため、追加で依存関係を設定する必要はありません。

基本的な使用法

@Service
@RequiredArgsConstructor
public class NewsArticleServiceImpl implements NewsArticleService {

    〜〜 略 〜〜

    @Override
    @Cacheable(cacheNames = "newsCache")
    public String putCache(final Integer newsId) {
        return newsArticleTitleMap.getOrDefault(newsId, "");
    }

    @Override
    @CacheEvict(cacheNames = "newsCache")
    public void deleteCache(final Integer newsId) {
    }
}

@CacheEvictcacheNamesに削除したいキャッシュのキー名を指定します。
newsId=1deleteCacheメソッドを呼び出すとnewsId::1のエントリが削除されます。

キャッシュを全て削除する

先ほどは、メソッドの引数newsId=1に対応するエントリのみが削除されました。
メソッドの引数に関わらず、newsCacheのすべてのエントリを削除したい場合には、allEntries = trueを指定します。

    @Override
    @CacheEvict(cacheNames = "newsCache", allEntries = true)
    public void deleteCache(final Integer newsId) {
    }

メソッドの引数とkeyが異なる場合

次に、ニュース記事のタイトルを変更する以下のようなメソッドを考えます。
(正確には、すでにタイトルが登録されている場合には更新し、登録されていない場合は新たに追加するメソッドですが。)

    @Override
    @CacheEvict(cacheNames = "newsCache")
    public void updateNewsArticleTitle(final Integer newsId, final String title) {
        newsArticleTitleMap.put(newsId, title);
    }

この時、newsId=2を指定したとしてもnewsCache::2のエントリは削除されません。
なぜなら、@Cacheable@CacheEvictなどのアノテーションは、デフォルトですべての引数をキャッシュに含めようとするためです。
この場合は、newsCache::2,{title引数で指定した文字列}でキャッシュを削除しに行こうとします。
当然、そのようなエントリは存在しません。

titleの値に関わらずキャッシュを削除するには、以下のようにkey属性を指定します。

    @Override
    @CacheEvict(cacheNames = "newsCache", key = "#newsId")
    public void updateNewsArticleTitle(final Integer newsId, final String title) {
        newsArticleTitleMap.put(newsId, title);
    }

keyには、キャッシュのキーに利用する「パラメータや値」を指定します。
複数のパラメータを指定する場合には、"{#newsId, #title}"のように指定できます。

この状態で、newsId=2, title="〇〇地方で大雨"で、updateNewsArticleTitleメソッドを呼び出すと、newsCache::2のエントリが削除されます。

おわりに

キャッシュをする場面と比較して、キャッシュを明示的に削除したい場面は少ないかもしれません。 しかし、もし必要になった場合には、利用してみてください。

では、また次回。

SpringdocでAPIの情報を補足する際、リクエストパラメータには@Parameterを使うべきという話

こんにちは、エキサイト株式会社の平石です。

今回は、SpringdocでAPIのリクエストに対して、付与するアノテーションをご紹介します。

なお、今回のソースコードは以下の環境で動作確認をしています。

  • 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
  • Java 21
  • SpringBoot 3.2.2

はじめに

SpringdocはOpenAPI仕様のAPIドキュメントを自動で生成してくれるライブラリです。
ドキュメントを書く手間も省けますし、Swagger UIを利用すればAPIの動作確認もできるので、事業部でも積極的に活用しています。
Springdocでは、Controllerを記述するだけで最低限のドキュメントを作成してくるのですが、ライブラリで提供される専用のアノテーションを付与することで、情報を補足することもできます。
特に、Controllerを書くだけでは「どのような動作をするAPIなのかの説明」「それぞれのパラメータやレスポンスの詳細な説明」といった情報は表示されませんので、アノテーションを利用して補足する必要があります。

例えば、以下のようなControllerを作成したとします。

@RestController
@RequestMapping("sample")
@Tag(name = "sample")
public class SampleController {
    @GetMapping
    @Operation(summary = "サンプルのAPI")
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "201",
                    description = "正常に処理が終了した場合"
            ),
            @ApiResponse(
                    responseCode = "500",
                    description = "API内部でエラーが発生した場合"
            )
    })
    public String sample() {
        return "Hello, Excite";
    }
}

この時、Swagger UIを確認すると以下のように反映されています。

サンプルのAPI正常に処理が終了した場合といった文言はアノテーションで補足しないと反映されない内容です。

@Schemaを乱用して発生した問題

これまで、私の所属するチームではSpringdocでAPIの情報を補足する際に、リクエストにもレスポンスにも@Schemaのみを付与していました。

@SchemaはOpenAPI仕様上でデータモデルを定義するために使われるアノテーションです。
データモデルと言いつつも、単一のフィールドやパラメータを表現するのにも使用することができます。

しかし、@Schema単体ではリクエストの際に適切に型が反映されないという問題がありました。

例えば、以下のようなControllerを考えます。

@RestController
@RequestMapping("sample")
@Tag("sample")
public class SampleController {
    @GetMapping
    public String sample(
            @ParameterObject SampleRequestDto requestDto
    ) {
        return "Hello, Excite!";
    }
}

SampleResponseDtoには、これから様々なフィールドを記述していきます。

リクエストの場合

APIのリクエストは、Controller内のメソッドの引数にStringIntegerの状態で直接渡すこともできますし、全てのパラメータをまとめたDTOを渡すこともできます。

今回は、DTOを作成してみます。
Javaのレコード・クラスを利用すると便利です。

public record SampleRequestDto(
        String stringField,
        Integer integerField,
        Long longField
) {
}

この状態でSwagger UIを確認してみます。

各パラメータが認識され、型もJava側で宣言した型からOpenAPI仕様で定義されている型に自動で変換してくれています。

ちなみに、()内に$マークと一緒に記述されているint32date-timeformatです。
integer($int64)integerというtype(型)の$int64というフォーマットであることを表しています。
int64はLong型のことなので、Javaの型がOpenAPI仕様で利用できる適切な型(+ フォーマット)に自動的に変換されていることがわかります。

では、リクエストに、descriptionexampleを記述して情報を補足してみます。

public record SampleRequestDto(
        @Schema(description = "文字列のパラメータ", example = "文字列")
        String stringField,
        @Schema(description = "Integer型のパラメータ",  example = "1")
        Integer integerField,
        @Schema(description = "Long型のパラメータ", example = "2")
        Long longField
) {
}

Swagger UIを確認してみます。

おや?確かに、Descriptionでコメントによる補足は反映されていますが、型が全てstringになってしまっています。

Springdocではリクエストパラメータでは@Schemaで補足情報を加えようとすると、型を自動で変換することができず全てstringになってしまうようです。

この仕様が謎であったため、公式ドキュメントを眺めているとリクエストパラメータの時には@Parameterというアノテーションを利用すると良いようです。
@Parameterでもdescriptionexampleをそのまま指定することが出来ます。

public record SampleRequestDto(
        @Parameter(description = "文字列のパラメータ", example = "文字列")
        String stringField,

        @Parameter(description = "Integer型のパラメータ",  example = "1")
        Integer integerField,

        @Parameter(description = "Long型のパラメータ", example = "2")
        Long longField
) {
}

Swagger UIを確認してみます。

今度は、型が反映されています。

@Parameterではパラメータを入れる場所(ヘッダやクエリパラメータ等)を指定するin、空の値を許可するかどうかを示すallowEmptyValueなどのように、リクエストパラメータに特化した属性値をセットすることもできるため、今後はこちらを利用していきたいと思います。

おわりに

今回は、Springdocでアノテーションを利用して情報を付加するときに、リクエストパラメータには@Parameterを利用すべきであることを紹介しました。

では、また次回。

参考文献

社内カンファレンス「TechCon2024」の裏側!ハイブリット開催の配信設定について

こんにちは。エキサイト株式会社の Hiroshi Sakai です。 2024年も無事TechConが開催されました!開催にあたっての記事は以下をご覧ください。

tech.excite.co.jp

このブログでは、TechCon2024で初めて行われたオフライン、オンラインのハイブリット開催の配信設定について記載します。
専門的な機材や配信を本業とされている方と同じような機材を利用したわけではないので、様々な方が真似できるような構成になっているかと思います。
ハイブリット開催の参考になったら幸いです。

機材配置、配線等について

機材配置は、下記の通り

機材配置図

それぞれ説明していきます。

配信PC (OBS, Zoom, meet)について

配信PC (今回はMacを使用) 1台にOBS, Zoom, meetを起動しておきます。

OBSは、スイッチャーに取り込んだビデオカメラの映像やマイクの音声などを拾いつつ、Zoomに画面共有する画面を作成しています。
OBSのソースに「発表タイトルや登壇者名」「発表資料」「ビデオカメラの映像」「Zoomのコメント欄」「休憩中のCM映像」などを作成しています。
下記のオーバーレイをデザイナーの方に作っていただき、それぞれ必要な素材を配置していってます。
*本番では、モーション付きのオーバーレイ動画を用意していただき、それをOBSで読み込んでいました。
OBSでは、「仮想カメラ開始」を選択しておきます(ZoomにOBSの画面を映すために必要です)

Zoomは、ウェビナー主催者としてスケジュールを作成し、参加URLを視聴者に共有しております。次に、Zoomの画面共有で「第二カメラのコンテンツ」を選択します。選択後、OBSで設定している画面がZoomのウェビナー画面に画面共有されていればOKです。

meetは、登壇者が発表資料を画面共有するために利用しました。登壇者は、meetで発表資料を画面共有してもらいます。
OBSのソース選択でウィンドウキャプチャでmeetを選択肢、meetで画面共有されている内容をOBS上に映します。
OBS, Zoom, meetの設定は、以上になります。OBSでもっと配信ぽくしたり、コメントを横流しにしたり色々できますので、お試しください。

スピーカー、マイクについて

今回の構成では、マイクから聞こえる音は、スピーカーからオフラインの会場に流れるのと、オンラインに流れるのと二つに流しております。オンラインに流す方法としては、スイッチャーにライン出力ケーブルを差し込み、スイッチャーから配信PC(OBS)に取り込むようにすることでマイクの音をオンラインに流しています。
*本来は、オーディオインターフェイスなどがあれば良いかと思います。

利用したスイッチャーについて

ATEM Miniというスイッチャーを利用しています。
ソフトウェアなどもダウンロードでき、ソフトウェア上で音の調整など可能です。(ハウリング防止などは、主にOBS上で設定していました。)

この構成のメリット・デメリット

  • メリット

    • 比較的簡単にできます。(社内にない可能性が高いのは、スイッチャーくらい)
    • 配信が止まらず、安定配信を提供できる(OBSが長時間配信してても落ちない。)
    • 登壇者は、登壇者自身のPCで画面共有しながら発表できるので、オンラインで画面共有しているのと変わらない操作で発表できる
    • Zoomのコメント欄表示、ビデオカメラ表示など技術カンファレンスぽいことができる
  • デメリット

    • 発表資料をOBSに映す時に画角がずれる
    • 配信PC, 登壇者PCなどから音声を流してもオンライン側に音声が載らない

オフラインの配信風景

やってみての感想

今回は、初めてのハイブリット開催ということもあり、最小構成で配信設定してみました。最小構成でありながらオフライン、オンラインハイブリット開催を実現できたのは、よかったと思います。また、私はOBSを少しだけ触っていましたが、より配信周りの知見が広がったのは個人的にも嬉しかったです。
拙い文章で申し訳ないですが、以上とさせていただきます。
今回の記事は、実際にやってみないとわからない点などが多いかと思いますので、実際にテスト配信等をしてみていただけると良いかと思います。

PHPerKaigi 2024 ルーキーズLT大会枠で登壇するまで

概要

こんにちは、エキサイト株式会社のまみです。(Xのリンク貼りたいがために挨拶しました)

PHPerKaigi 2024 のルーキーズLT大会枠で登壇しました :)

登壇するまでの日記?記録?を書きます。

  • 「社外での発表」は私にとっては挑戦でした。同じようにちょっと新しいことをしたいと思っている人の後押しになれたらうれしいです。
  • 今回の私のプロポーザルの書き方を解説しています。プロポーザル書いたことないようという方の参考になったらうれしいです。

登壇するまでの記録

何か新しいことをしたいと思う

新卒でエキサイト株式会社に入社して6年目、 コーディングもそこそこにはできるようになっていて、周囲の方ともそこそこにうまいこと仕事をできるようにはなっていました。

何か新しいことをして、自分が得意なことを見つけたい!と思っていました。(曖昧すぎるけどそれでもいいよね??)

何かないかな、と考えていたところ、 同じ部署に所属しているまさきちあはれんPHPerKaigi 2024に応募すると知りました。

社外での技術的な発表をしたことがなかった私は、 便乗して応募することにしました(単純!)

応募してみる(プロポーザルの作成)

応募の仕方もよくわからない、プロポーザルもどう書いていいかわからない、という状態でのスタートでした。 社内の経験者の方々(※自分が話しやすい人に限るくらいの気軽な感じ)に色々聞いてみました。

nukisashineko が紹介してくれた↓

speakerdeck.com

のスライドに大きな影響を受けて プロポーザル↓

fortee.jp

が完成しました。

社外登壇のためのプロポーザル作成は初めてで、試行錯誤し、満足したものができたと思っている(強気)ので、 どのように考えて書いたのかを共有します。

プロポーザルをどのように作ったのか解説

全体的にとにかくトークをきくリスナーにとってのメリット」を強く意識して作成しました。

トークタイトル

通話プラットフォームTwilioを用いた通話システムの作成と電話占いサービスの提供
  • リスナーのメリットとして「どんな新しい知識を獲得できるか」がわかるように心がけました。
  • トーク概要」を書いてからChatGPTでタイトル候補を出して眺めてみて、良い言い回しを考えました。

トーク概要

弊社の「エキサイト電話占い」は、弊社通話システムを通すことで安心安全に占い師と通話できます。
(電話越しに占いができるんです!) 

↑幅広い方に興味を持ってもらうために、最初の一文では「かんたん」「わかりやすい」言い回しを意識しました。

「エキサイト電話占い」には、
・ユーザと占い師お互いの電話番号は非表示
・電波状況などユーザと占い師の環境に合わせた通話手段(通常の電話 or 通話アプリ)を用意
・分単位でのサービス利用料金の計算ができる
などの要件があります。

↑「エキサイト電話占い」という特有のワードについて説明しつつ、 リスナーであるエンジニアが興味を持ちそうなサービス要件を書き並べます。 (「要件」というものに興味が湧くエンジニアの方って、多いと思う。)

私はTwilioを用いて上記の要件をみたした通話システムを構築しました。

↑本題である「通話システム」に話を持っていきます。

この通話システムを構築するという経験はとても新鮮だったため、
皆さんに共有させてください。

↑「通話システムの構築」というレアな体験の話であることをあっぴるして、興味をひくように頑張ります。

このLTでは、
・Twilioを用いた通話システムの作成方法
・上記課題の解決方法
について話します。

↑ここでもリスナーのメリットを意識して、結局何について話すのかということを短くまとめました。

●●●LTを聞くメリット●●●
・Twilioの基礎を知ることができる
・電話占いの裏側を知ることができる

↑リスナーのメリットを目立つように、「●●●」で項目を分けて箇条書きしました。 項目分けをするとスッキリ見えてプロポーザルを読む気持ちが高まるはず。

●●●対象者●●●
・通話を用いたサービス提供を検討中の方
・通話の実装を考えたことがない方
・電話占いを知らない方
・占いが好きな方

↑いろんな人がそれぞれの立場で、実はきっと興味がある話であることに気がついてもらえるように、 いろんな視点での対象者を列挙しました。

採択されて、採択の承諾をする

採択されておおよろこびしてたら、 手順を間違えていたのか「採択の承諾」できてなかったというおそろしい事件がありました。

期間内にまさきちが教えてくれて気がつくことができ、無事「採択の承諾」ができました。 ほんとうにありがとうございます。

知り合いが参加してることの大きなメリットです(真顔)

登壇準備

大学での研究発表(仮定、実験、考察、結論)のためのスライド作成などの発表準備は慣れていたのですが、 「結論」がない知識共有のような発表は慣れておらず、苦戦しました。力技で乗り越えました。

発表の1〜2週間前にざっくりの流れを掴んだスライドと台本はできていましたが、 うーんいまいち。というものでした。パッとしないし聞き心地が悪そう。

そこからとにかく何回も声に出して読みあげて

  • 話しにくいところ
  • 聞き取りにくそうなところ
  • 理解しにくそうなところ

を改善しました。

何度か自分で声に出して読んでいるうちに、

  • ここで笑いを取れそうだな
  • ここでPHPのことに触れよう
  • ここでコーディング的におもしろいと感じてもらえそう

などの興味をひけるポイントの発見と強調をし、スライド修正しつつ、そのような細かい部分も台本に反映しました。

台本命(いのち)です。ほんとに。いまのところ絶対20分も30分も喋ることはできないです草

登壇当日会場に着く

たった5分のLTでもとても緊張していたので、 他の方の登壇を聞いたり、いろんな企業のブースに行ってきました。

最後に「おまけ」としてくわしく書きます :)

登壇

私の接続方法の問題で、台本(カンペ)がみられないことに気がつきました。ピンチ。

台本を捨てましたが、 リスナーの皆様や司会者のかたが大きなリアクションをくれたおかげて無事に楽しくお話しすることができました。

ありがとうございます。

登壇の時の様子

発表の際にXのアカウントを載せて「ネット上の友達が欲しいです」と言ったら多くの方がフォローしてくださいました。

フォロバしたもののここからどう友達になっていくかは考え中ですが、 このように社外の方とのつながりが一気に作れることを実感しました。

(社外イベントに参加で繋がりを???そんな簡単にできないようと内心思っていました(小声))

登壇後

ニコ生やXでリアルタイムにコメントをいただける環境なので、 発表がうまくいったかをすぐに確認することができました。

たとえばXだとこんな感じで確認できます。(「最新」を押す必要があるかも)

発表後の休憩時間など、直後にも見ることができるのはとても楽しいです。

うまくいったところだけ書きます。興味をひくのに必死すぎる。

  • かわいいドヤ犬の画像で、みんなの興味をひく
  • PHPのインターフェースでの上手い共通化の話をして、「エンジニア」からの興味をひく
  • イベント駆動であることを話して、「エンジニア」からの興味をひく
  • (何とは言わないけど、自分を含めたちょっと年齢層高い方からの興味をひく)

登壇してみての感想

登壇してみてよかったです。 社会人として、エンジニアとしての自信がつきました。

新しいことを始めて時間をかけて取り組んでみるって大切で、 それができる会社に居させてもらえてありがたいなと、改めて思いました。

(めっちゃ媚び売ってるみたいで恥ずかしいし草)

おまけ

もはやおまけではなくメインかもしれない。

楽しかった企業ブース

「Postman」のブースが楽しかったです。

ノベルティのノートが最高でした。文房具付きにはたまらんです。

もちろんそれだけではなく、普段から「Postman」にはお世話になっており、 お話を聞いて、普段の利用方法とは異なる利用方法があることに気がつきました(そりゃたくさんあるだろうよ、私)

postman

楽しかった企画(ネイル)

プロのネイリストさんにネイルをしてもらえました。

びっくりしました。 PHPerKaigi開催の前日〜当日にそのようなコーナーがあると気がつき、うきうきで会場に向かいました。

男女関係ないとはいえ、やっぱりこういう企画があると女性エンジニアの気持ちは高まる!うれしい!と思っていましたが、

座談会?で聞いた話だと、こちらの企画をした男性の方が、ご自身がネイルをしてみたかったから企画したとこのこと。 神。

nail

おしまいです。

PHPerKaigi 2024に参加&登壇してきました

こんにちは!エキサイト株式会社のまさきちです。

先日、PHPerKaigi2024に参加&登壇してきました。

「ブログを書くまでがPHPerkaigi」 という事でさっそく書いていきます。

PHPerKaigi2024とは

2024年3月7日(木)〜3月9日(土)の間で開催された国内最大級のPHPカンファレンスです。 エキサイトはスポンサーとして参加しました。 参加者も多く、ノベルティや企画もかなり充実して参加者が楽しめる工夫がされていました。

phperkaigi.jp

私は「ルーキーズLT」という初めてPHPerkaigiで登壇する人の枠で登壇してきました。

弊社から2名ルーキーズLT登壇し、1名ポスターセッションとして会場にポスターを掲載しておりました。

ルーキーズLT

fortee.jp

fortee.jp

ポスターセッション

fortee.jp

事前に練習会に参加

初めてのカンファレンス登壇ということもあり、事前の練習会にも参加してスタッフの方にフィードバックをいただきながらトークや資料を改善していきました。

声が通りやすいマイクの持ち方のコツや、LT資料の視認性を上げるために文字サイズや図の工夫の仕方などを教えていただき、 参加後はとてもクオリティが上がりました!

LT登壇者同士で交流も出来たので嬉しかったです。

phperkaigi.connpass.com

そして本番当日

弊社のシステムで使用されているPHPフレームワークのBEAR.Sundayについてトークしました。

speakerdeck.com

広い会場で多くの人の前で発表することに慣れていないので緊張しましたが、無事登壇をやり切りました!!

LT終了30秒前にスピーカーの推し色のサイリウムを振って応援するので会場は盛り上がっていて楽しめました! (LT司会者のテンションもアゲアゲでした笑)

フレームワークの作成者の郡山さんにフィードバックもいただけたので良い経験になりました。

トーク視聴

day2はBEAR.Sunday作者の郡山さんのトークを聞いて理解を深めました。

speakerdeck.com

「インフォメーションよりもインスピレーション」 という言葉が自分に刺さり、開発者としての在り方を考えさせられる様な内容でした。

懇親会

全てのプログラムが終わった後、懇親会に参加しました。 ケータリングの料理美味しかったですし、他社のエンジニアさんとも交流できて楽しかったです!

まとめ

他の方々のトークも勉強になり、モチベーションが上がったのでまた次回も参加したいと思います! 運営の皆様をはじめ関係者の方々、素敵なカンファレンスをありがとうございました。

P.S.

4/13日に小田原で行われるPHPカンファレンス小田原の当日スタッフもやります!

phpcon-odawara.jp

次は小田原でお会いしましょう!

Aurora MySQLを2系から3系に上げる際の懸念点

こんにちは。 エキサイト株式会社の三浦です。

AWSのAurora MySQL 2(MySQL 5.7互換)は、2024年10月31日で標準サポートが終了します。

docs.aws.amazon.com

一応料金を支払えばサポートは延長できますが、このタイミングでAurora MySQL 3(MySQL 8互換)へのバージョンアップを考えている方も多いのではないでしょうか。

今回は、バージョンアップに際して考えるべきことをまとめます。

なお、今回説明する内容はまだ考察段階であり、実際のアップデートの適用前です。 あくまで参考として見ていただけると幸いです。

アップデート前

SQLやデータ構造が非互換

古いバージョンだと動くものの、バージョンアップすると動かなくなる可能性がある構文やデータ構造があります。

例えば、以下のような構文やデータ構造です。

  • GROUP BY を使っていて、条件的に一意に定まらないカラムを SELECT で指定している
  • MySQL 8から予約語になった RANK を、エスケープせずに使っている
  • 0の日付・タイムスタンプを使っている

これらはあくまで例であり、これ以外にも動かなくなる構文やデータ構造がある可能性があるので、必要に応じて対応しましょう。

文字セット・照合順序の変更によるエラー

古いバージョンだと文字セット utf8 とは utf8mb3エイリアスですが、すでにこれは非推奨であり、今後は utf8mb4 を使う必要があります。

いずれは utf8mb3 は使えなくなる可能性があるほか、 utf8 という文字セットも utf8mb3 ではなく utf8mb4エイリアスになっていく可能性があるようです。

必ずしもAurora MySQL 3への変更の際にすぐに変える必要はないかもしれませんが、この機会に変えてしまったほうが安全かもしれません。

また関連して、照合順序も変える必要が出てきます。

照合順序は、選び方次第で「は」「ぱ」「ば」を区別してくれなかったりなどいろいろな違いがあるので、適切なものを選んでいきましょう。

すでに utf8mb4 を使っている場合でも、MySQL 8からは utf8mb4 のデフォルトの照合順序が変わっているようです。

もしデフォルト値を使っている場合は予期せぬ変更が起きてしまう場合があるので、注意しましょう。

アプリケーションからの接続問題

アプリケーションからDBに接続する際、接続用のライブラリが古いバージョンだと問題が起こる可能性があります。

アップデート前に問題ないか確認しておき、必要に応じてライブラリのバージョンアップや設定変更をするなど対応しておきましょう。

パラメータグループの変更必要性

場合によっては、クラスタインスタンスに設定しているパラメータグループの値を変更する必要があるかもしれません。

適切に動くよう、検証・対応しましょう。

アップデート時

サービス停止

通常、Aurora MySQLのメジャーバージョンアップをそのまま行うと、クラスタは一時的に使用できなくなります。

docs.aws.amazon.com

サービスが一時的に止まっても問題ないのであればそれでも構いませんが、停止したくないのであれば、以下のような手順を踏む必要があるでしょう。

手動ダブルライト

  1. 最新バージョンの新クラスタを用意しておき、既存クラスタからデータをコピーする
  2. アプリケーションから新旧クラスタ両方にデータを書き込む(ダブルライトする)ようにし、データを完全に同期する
  3. アプリケーションからのデータ読み取り先を新クラスタに変更する
  4. クラスタへの書き込みを停止し、旧クラスタを削除する

ブルーグリーンデプロイ

Aurora標準機能のブルーグリーンデプロイを使用する

docs.aws.amazon.com

アプリケーションからの向き先が自動的に新DBにならない

アプリケーションによっては、アプリケーション起動時にDBのIPアドレス等を取得・キャッシュしておき、以降はキャッシュに保存されたIPアドレスを使ってDBを見に行く、という場合もあります。

その場合、クラスタがアップデートしてIPアドレスが変わってしまうと、アプリケーションがアップデート後のDBを見に行ってくれない可能性があります。

使っているアプリケーションを確認し、もしそのような設定になっている場合は、設定を変更するか、どうしようもなければクラスタアップデート時にアプリケーション自体も再起動するようにしましょう。

アップデート後

パフォーマンスの低下・必要なスペックの増加

基本的にはバージョンが上がるとパフォーマンスも上がるはずですが、SQLの書き方やDBへの接続方法によっては、逆にパフォーマンスが下がったり、必要なCPU・メモリ等のスペックが上がってしまう可能性があります。

少なくとも、MySQL 8からはクエリキャッシュ機能がなくなるため、もしパフォーマンスをクエリキャッシュ機能に依存してしまっている場合はパフォーマンスの低下に繋がる可能性があります。

アップデート後は特に注視しておきましょう。

最後に

メジャーバージョンのアップデートは、避けられない上に非常に困難を伴う作業です。

慎重・着実にやっていきましょう。

また、上記では代表的なものを上げていきましたが、これら以外にも以下の公式ドキュメントにも書いてあるような様々な考慮点が存在します。

docs.aws.amazon.com

docs.aws.amazon.com

dev.mysql.com

dev.mysql.com

これらも参考にしつつ、障害なしのアップデートに向けて頑張っていきましょう!

【社内カンファレンス登壇記】TechCon2024でFigma Variantsのお話をしました!

こんにちは。 エキサイトで内定者アルバイトとしてデザイナーをしている齋藤です。

2024年2月16日に行われた、技術者向けの社内カンファレンス「Excite × iXIT TechCon」のLT枠で登壇させていただきましたので、その体験談を記したいと思います。

発表したこと

私は『チーム内のUIデザインのコミュニケーションを円滑にするFigmaの機能「Variants」をおさらい!』と称し、Figmaの機能であるチーム内(デザイナー・エンジニア・ビジネスの職域を超えて)のUIデザインをめぐるコミュニケーションに有用であることをお話させていただきました。

登壇に際し心がけたこと

技術に関した題材での発表は初めてであったこともあり、自分の伝えたいことを伝えきるために登壇に際して以下の事柄を心がけました。

いきなりVariantsの説明に入らない

今回の発表の主題はFigmaの機能であるVariantsがチーム内のUIデザインをめぐるコミュニケーションに有用であることなので、まずは聞き手の「チーム内のUIデザインをめぐるコミュニケーション」とは何なのかの認識を一致させることが必要と考えました。

というのも、いきなりVariantsの説明から入ってしまうと、「チーム内のUIデザインをめぐるコミュニケーション」の定義が聞き手の中でバラバラになってしまい、伝えたいことが伝わりきらない可能性があると考えたためです。

そこで、

  1. 「チーム内のUIデザインをめぐるコミュニケーション」の定義
  2. Variantsの説明
  3. 「チーム内のUIデザインをめぐるコミュニケーション」にVariantsを適用

の順で話を進めることにしました。

また、話の順序を発表の冒頭に示すことで、聞き手が話の順序を理解できるようにしました。

実際のスライド・冒頭で「お話すること」として流れを説明

Variantsを触ったことがない方でもイメージしやすくする

Variantsは少し複雑な機能であり言葉だけではイメージしにくいため、Variantsを触ったことがない方でもその操作感が伝わるように、簡易的な図式に表したり、実際の画面のキャプチャも交えながら説明しました。

実際のスライド・機能を図式で表現

実際のスライド・画面のキャプチャのGIFも交えて説明

聞き手の属性に合わせた+αのコンテンツを盛り込む

TechConは技術者向け、なかんずく聞き手にはエンジニアの方々が多いため、開発体験向上のためのVariantsの活用法も紹介しました。

実際のスライド・エンジニア向けの活用法

Figmaはデザイナーだけのものではなく、エンジニアにも親和性が高いことのアピールの一助になりました。

登壇を終えて

TechCon、さらには社内カンファレンスの登壇は初めてでしたが、実行委員会をはじめとした社員の方々のサポートもあり、無事に発表を終えることができました。

また、参加者からのアンケートでは、

  • 最高の発表でした。ぜひ20分セッション枠で発表してもらいたい!ってレベルでした。ボタンを押すとこんな感じに変わるっていうのが直感的でよさそうでした!
  • すぐに試したい!と思える内容で、LTとしての完成度が高かったです。
  • UIをめぐるコミュニケーション多々あるなと思い、とても勉強になりました!名前の付け方などもフロントエンドと合わせることでfigmaで作ったUIとフロントエンドがさらにつながるなと思い勉強になりました!

などなど、身に余るお言葉までいただきました。 これに満足せずに、より一層精進しなければと改めて認識しました。

最後に、TechCon開催に際しご尽力いただき、また、私のような内定者アルバイトにも登壇の機会をくださった実行委員会の皆さま方に改めて厚く御礼申し上げます!

Excite × iXIT TechConに関する記事は他にも投稿されています!ぜひこちらからご覧ください!!

【インターン体験記】UIの修正・改善提案で学んだこと

こんにちは!エキサイトインターン生のやのふきです。

この記事では、エキサイトでの就業型インターン中に担当した業務とそこから得た学びを紹介します。

自己紹介

大学では情報系の学問を広く学んでおり、画像処理を専門とする研究室に所属しています。デジタルなものづくりは学生IT団体に出会ったことがきっかけで大学生になってから始めました。個人開発だけでなく、所属メンバーとチームを組んでハッカソンに参加するなど楽しく活動しています。

担当した業務内容

インターン期間中はSaaS・DX事業部に配属され、主にKUROTENに関わるデザイン業務に携わらせていただきました。

この記事では、KUROTENのUIに関する業務で担当した以下の3つを紹介します。

  • メッセージ機能のレイアウト修正

  • 部門別分析のアラート設定に関するUI改善・提案

  • 詳細画面のメニュー要素に関するUI改善・提案

メッセージ機能のレイアウト修正

概要

現在KUROTENでは、技術移行に伴いデザインの修正を進めています。今回担当したメッセージ機能では、モーダルで表示していたUIをスプリットレイアウトに修正する作業を行いました。KUROTENはデザインシステムが細かく作り込まれており、作業自体はコンポーネントを組み合わせることでスムーズに行うことができました。

プロトタイプを用いて実際の画面の動きを再現しつつ不備がないか確認しながら進めました。最後はエンジニアの方とのコミュニケーションを円滑に行うため、画面遷移を書き起こした設計図を作成しました。

感想

デザインシステムによって、大量の画面や機能を持ってもブレのないデザインを実現できることを実感しました。今まではデザインシステム作成に時間がかかるので理由なく避けていたところがあったなと反省しました。使い続けてもらうには新しいものを作るだけではなくより良くなるようにアップデートし続ける必要があり、その点でもデザインシステムの重要性を感じました。また、KUROTENではデザイン原則やそれぞれのコンポーネントの使い方などが文書化されていました。プロジェクトに関わるデザイナー間の共通認識を揃えるためだけでなく、プロジェクトに関わる全員が理解できるようにまとめられており、改めてデザイナーの仕事がデザインだけではないということを感じました。

部門別分析のアラート設定に関するUI改善・提案

概要

部門別分析は、部門や科目ごとに計画と実績・見通しの差異を見るために利用します。アラートは大きな差異が出ているところを目立たせる機能で、アラート設定でそのアラートを出す基準を設定します。科目の数、部門の数が多いほど設定する項目が多くなってしまう構造なので、どうしたら設定しやすいか?の視点を持ってUIを作成していきました。

作業内容としては、まず最初に自分が使ってみて感じた使いづらさの原因を探して言語化・視覚化する作業を行いました。今回私が着目したのはアラート設定と部門別分析の構造が対応していなかった点です。

その後新たなUIを作成し、エンジニアの方へ提案を行いました。新たに提案したUIでは改修部分が多く、コストの高いものでしたが視覚化した図などをもとに説明し、納得していただくことができました。

感想

サービスがどんな場面で、どんな人に使われるのかを理解してデザインをすることの大切さを感じました。KUROTENが経営企画などを主に担当する方向けのサービスということもあり、最初は事前知識のなさから機能の扱いに戸惑いましたが、利用シーンなど具体的な使われ方を共有してもらうことでどこに重きを置いてデザインするべきかを考えることができました。改善案の提案をする際は、「なぜこの改善が必要なのか」をきちんとエンジニア側に伝えることで双方がユーザーの使いやすさを向上するという同じ目的のもとで話を進められているなと実感できました。

詳細画面のメニュー要素に関するUI改善・提案

概要

PLの管理に利用される実績や計画などの各画面のメニュー項目が多くてわかりにくいという課題を解決するために、UIの改善提案を行いました。

画面の常に見える場所に配置すべき機能、使用頻度が高くないのでメニューの中にあっても良い機能などの選定はKUROTENの商談を担当しているマネージャーの方に実際それぞれの機能がどれくらい使われているのかを聞いて進めました。

今回担当した箇所は特に機能が多かったため、機能を説明するテキストに関して、使う時のわかりやすさを担保することと決められた画面サイズ内に機能を収めることの両立に苦戦しました。

感想

機能の整理に関しては、煩雑になっていた要素のグルーピングや重要度のキャッチアップが非常に有効だと感じました。また、デザイン原則を守った上で実装上無理のない案を複数個出すことの難しさを感じました。トグル・テキスト入りのトグル・ボタン・アイコンのみのボタンなどを活用して差別化することで、それぞれのメリットデメリットをいろいろな立場の方からの意見を含め検討することができました。

最後に

インターン期間中は上記以外にもサムネイル作成や資料のリデザインなど幅広い業務に関わらせていただきました。実際に社会で活躍するデザイナーの方々にアウトプットに対してコメントをいただけたことはもちろん、他職種の方ともお話しする機会を設けていただき、本当に貴重な体験になりました。

担当メンターの鍛治本さんをはじめとするデザイナーの皆さん、事業部の皆さん、本当にありがとうございました!

Excite × iXIT TechCon 2024 のランチタイム企画として「ききソースコード」 を実施しました。

こんにちは。エキサイト株式会社の あはれん です。

エキサイトで第3回目となるTechConが開催されました! TechConの概要については以下の記事を御覧ください。

tech.excite.co.jp

私は運営チームとしてランチタイム企画を担当し、社内エンジニアの個性あふれるコードを当てるオリジナルゲーム「ききソースコード」を開催しました。 弊社で盛り上がったので紹介いたします。

「ききソースコード」とは

「ききソースコード」とは、事前に作成してもらったソースコードを、作成者を伏せて公開し、誰が書いたコードかを当てるゲームです。 利きワインから着想を得て運営チームメンバーが発案したオリジナルゲームです。

企画の目的

  • カンファレンス参加者にカンファレンスをより楽しんでいただく
  • 他事業部やあまり接点がない人(デザイナー・ビジネス)に、エンジニアを知ってもらうきっかけを作る

具体的な内容

  1. 前準備:社内エンジニアにソースコード作成とアンケート回答をしてもらう
  2. 本番:ランチタイム前に、作成者を伏せたソースコードと作成者のプロフィールを公開
  3. 本番:ランチタイム中に、参加者はソースコードの特徴とプロフィール情報をもとに誰が書いたコードかを当てる
  4. 本番:ランチタイム終了前に、答え合わせと作成者のコメントを公開

1. 前準備:社内エンジニアにソースコード作成とアンケート回答をしてもらう

ソースコード

ソースコードの作成ルール

・お題:FizzBuzz問題

・提出方法:ソースコードを閲覧またはダウンロードできるURLを共有

・利用言語やフォーマットに関してはなんでもOK

それぞれのソースコードを比べられるように書く内容はFizzBuzz問題と決めていましたが、それ以外はなんでもOKというルールで依頼しました。

作成者の個性が出るように、あえて詳細なルールは設けませんでした。 その結果、ChatGPTでFizzBuzzを解いてきたり、アセンブラ言語で書いていたりと色々なソースコードが出てきたのでよかったです。

作成者に関するアンケート

ソースコードと作成者を結びつけられるように、作成者本人に関する情報も集めました。

作成者のコーディングの特徴が分かるような質問項目を用意するように努めましたが、 「コーディングの特徴が分かるような質問項目」というのはシンプルに何なのか分からず苦戦しました。 ここに関しては改善の余地がありそうです。

作成者への質問

・好きな言語を2つ挙げてください。

・書ける言語を2つ挙げてください。

・興味のある技術分野がありましたら、教えてください。

・コードを書く際に意識していること

FizzBuzz問題解答時の意図や工夫等を一言

2. 本番:ランチタイム前に、作成者を伏せたソースコードと作成者のプロフィールを公開

ソースコードの紹介スライドイメージ

作成者の紹介スライド、作成者名と紹介文
作成者の紹介スライドイメージ

参加者には、作成者を知らない人もいるため、ソースコードだけでなく作成者の紹介も必要でした。 何も知らない人もできるだけソースコードと作成者を結び付けられるように、紹介文を作成しました。 例えば、作成者の紹介で「bashが好き」と説明して、「bashソースコードを書いたのはこの人かも?」と分かるようにしました。

3. 本番:ランチタイム中に、参加者はソースコードの特徴とプロフィール情報をもとに誰が書いたコードかを当てる

回答フォームイメージ

googleアンケートフォームで回答フォームを作成しました。 回答時に作成者のプロフィールと照らし合わせながら選べるように、作成者のプロフィールまとめも掲載しました。

4. 本番:ランチタイム終了前に、答え合わせと作成者のコメントを公開

ソースコードの答え合わせスライドイメージ

ただ答えを伝えるだけでなく作成意図も伝えることで、そういうことだったのかー!という納得感がでてよかったです。

実際やった結果

10名にお願いしてソースコードを用意したのですが、最大10問中8問を当てることができていました。 また、参加者から「人それぞれのコーディングスタイルを知れて楽しかった!」「分かりそうで分からなくて面白い!」等のコメントをいただき、楽しんでいただけました。

まとめ

改善の余地はまだまだありますが面白かったので、ぜひ、他のカンファレンスや社内イベントでも試してみてください! やってみたレポをお待ちしております!