【実験記】data属性を使用してスタイリングする方法

こんにちは。エキサイトでデザイナーをしている齋藤です。

今回は、HTMLのdata属性を使用してスタイリングする実験をしてみたので、その方法や所感について記したいと思います。

data属性とは?

data属性は、HTMLタグに対して独自の属性を付与できる機能です。

data-に続けて任意の名前を付与することで設定でき、JavaScriptCSSから参照できます。

data-以下の命名は基本的に自由ですが、以下の通りの制限があります。

  • 大文字小文字にかかわらず、名前を xml で始めてはならない。
  • 名前にコロン (:) を含めてはならない。
  • 名前に大文字を含めてはならない。

(引用元:data-* - HTML: ハイパーテキストマークアップ言語 | MDN

data属性の使用例

<p id="fruit" data-color="red" data-shape="round">Apple</p>

JavaScriptから参照する例

const fruit = document.querySelector("#fruit");

fruit.dataset.color; // "red"
fruit.dataset.shape; // "round"

data属性はCSSからも参照できる

data属性はCSSからも参照できるので、スタイリングに使用することもできます。

先述の例の場合、特定のdata属性の有無でスタイルを分けたい場合は以下のようなCSSになります。

p {
  &[data-color="red"] { ... };
 
  &[data-shape="round"] { ... };
}

実用例

data属性を使用したスタイリング手法は、状態を多く持つような要素(サイト内で汎用的なボタン)と相性が良いかもしれません。 例えば、色・サイズ・デザインパターンが指定できるようなボタンを作成するとします。

従来のスタイリングではBEMなどを用いて状態に応じたclassを追加していきます。

<button class="button button--large button--blue button--outlined">...</button>

一方で、classの代わりにdata属性を使用すると以下のようになります。

<button class="button" data-button-size="large" data-button-color="blue" data-button-theme="outlined">...</button>
.button {
  &[data-button-size="large"] { ... };

  &[data-button-color="blue"] { ... };

  &[data-button-theme="outlined"] { ... };
}

class属性を長くせずにスッキリさせることができることが大きな利点でしょうか。

使ってみた所感

data属性を使用することでclass属性を長くせずにスッキリできる一方で、名称が重複してしまうとスタイルが崩れてしまうのでチームで使用する場合は命名ルールを徹底することが必要かと思われます。

すでにBEMなどのCSS設計を取り入れている場合は、data属性を用いた手法を組み合わせてしまうと混乱を招く可能性もあるため特に注意が必要そうと感じました。

さいごに

今回は、HTMLのdata属性を使用してスタイリングする実験をしてみたので、その方法や所感についてお話をしました。

実務に落とし込むことは難しそうという結論に至った一方で、スタイリング手法の一選択肢としてdata属性を使用する方法が知れたことは個人的に収穫でした。

data属性が気になっている方の参考となれば幸いです。

ご精読ありがとうございました。

[Java] Spring BootでInterceptorを使う方法

はじめに

こんにちは、新卒2年目の岡崎です。

Interceptorを使用すると、コントローラーで処理を実行する前後に共通の処理を行うことができます。今回は、Spring BootでInterceptorを使う方法をご紹介します。

環境

openjdk version "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)
  • Spring boot
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.1)
  • gradle
------------------------------------------------------------
Gradle 8.5
------------------------------------------------------------

Build time:   2023-11-29 14:08:57 UTC
Revision:     28aca86a7180baa17117e0e5ba01d8ea9feca598

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.3 aarch64

実装方法

最初に、Interceptorの作成を行います。Interceptorを作成する際には、HandlerInterceptorインターフェースを実装します。Interceptorでは、以下の三つのメソッドが実装できます。

preHandle

preHandleでは、コントローラーで処理を実行する前に割り込み、共通の処理を実行できます。

postHandle

postHandleでは、コントローラーで処理を実行した後に、共通の処理を実行できます。ただし、コントローラーで例外が発生した場合、処理は実行できません。

afterCompletion

afterCompletionでは、レスポンスが完了した後に、共通の処理を実行できます。コントローラーで例外が発生した場合でも、処理を実行できます。

以下は、全てのレスポンスにContent-Typetext/html; charset=utf-8に設定する方法の例です。

まず、Interceptorの作成を行います。

@Configuration
public class SampleInterceptor implements HandlerInterceptor {
    @Override
    public void postHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler,
            ModelAndView modelAndView
    ) throws Exception {
        response.setHeader("Content-Type", "text/html; charset=utf-8");
    }
}

Interceptorを登録するには、WebMvcConfigurerインターフェースを実装するクラスを作成します。このクラスでaddInterceptorsメソッドをオーバーライドして、作成したInterceptorを登録します。

@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
    private final SampleInterceptor sampleInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(sampleInterceptor);
    }

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setUseTrailingSlashMatch(true);
    }
}

全てのレスポンスにContent-Typetext/html; charset=utf-8に設定できていたら、実装完了です。

最後に

Spring BootでInterceptorを使用する方法を紹介しました。コントローラーで毎回同じ処理を実装するのは効率的ではありませんし、どこかで間違いが生じる可能性もあります。そこで、こういった共通処理をInterceptorにまとめて実装する方法を検討してみてはいかがでしょうか。

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

募集職種一覧はこちらになります。

wantedly.com

embulkでBigQueryからデータベースにデータを移行した話

はじめに

新卒2年目の岡崎です。最近、embulkでBigQueryからデータベースにデータを移行しました。その時のことを備忘録として記事にします。

データ移行を行った理由

今までは、過去のデータをGoogleが提供していたUAから見て、分析できました。しかし、UAはGA4に移行を完了するにあたり、過去のデータを見れなくなりました。過去のデータが自社のデータベースにないと、毎回BigQueryを使ってデータを見なくてはいけません。この問題を解決するため、embulkを使ってBigQueryからデータベースにデータを移行します。

前提

今回はembulkを使い、BigQueryからデータベースへデータを移行することを目的とします。また、環境構築はdockerでサクッと行いたいので、dockerを使える環境の準備をしてください。

実装

実装の紹介をします。

DockerFile

ここでは、主にembulkのインストールをします。

embulkの公式ドキュメントによると、Java8が推奨されていますが、今回の実装であればJava21の環境でも問題なく動きました。

FROM openjdk:21-jdk

WORKDIR /embulk

# jRubyのインストール
RUN curl --create-dirs -o "./jruby-complete-9.4.5.0.jar" -L "https://repo1.maven.org/maven2/org/jruby/jruby-complete/9.4.5.0/jruby-complete-9.4.5.0.jar"
RUN chmod +x ./jruby-complete-9.4.5.0.jar

# embulkのインストール
RUN curl --create-dirs -o "./embulk-0.11.4.jar" -L "https://dl.embulk.org/embulk-0.11.4.jar"
RUN chmod +x ./jruby-complete-9.4.5.0.jar

ENV PATH="/root/.embulk/bin:${PATH}"

COPY ./embulk/embulk.properties /root/.embulk/embulk.properties
RUN java -jar embulk-0.11.4.jar gem install embulk -v 0.11.4

# プラグインのインストール
RUN java -jar embulk-0.11.4.jar gem install embulk-output-mysql

ENV GEM_HOME="/root/.embulk/lib/gems"
RUN java -Xmx2g -jar embulk-0.11.4.jar gem install embulk-input-bigquery

RUN java -jar embulk-0.11.4.jar gem install liquid -v 5.5.0

COPY embulk .

RUN chmod +x embulk.sh

ENTRYPOINT ["sh", "embulk.sh"]

JRubyのインストール

今回の場合はプラグインのインストールでJRubyのインストールが必要でしたが、不要な場合は飛ばしてください。

RUN curl --create-dirs -o "./jruby-complete-9.4.5.0.jar" -L "https://repo1.maven.org/maven2/org/jruby/jruby-complete/9.4.5.0/jruby-complete-9.4.5.0.jar"
RUN chmod +x ./jruby-complete-9.4.5.0.jar

embulk.properties

JRubyのプロパティを設定します。

jruby=file:///embulk/jruby-complete-9.4.5.0.jar

embulkのインストール

今回は最新のバージョンである0.11.4を指定しました。

RUN curl --create-dirs -o "./embulk-0.11.4.jar" -L "https://dl.embulk.org/embulk-0.11.4.jar"
RUN chmod +x ./jruby-complete-9.4.5.0.jar

ENV PATH="/root/.embulk/bin:${PATH}"

プラグインのインストール

RUN java -jar embulk-{embulkのバージョン}.jar gem install {インストールしたいプラグイン} -v {バージョン}

今回は、以下のプラグインをインストールしています。

用途によって、使用したいプラグインは違うと思います。必要に応じて、プラグインのインストールをしてください。

liquidのインストール

RUN java -jar embulk-0.11.4.jar gem install liquid -v 5.5.0

liquidをインストールすることで、変数を使用することができます。

embulk.sh

embulkのシェルスクリプトの実装例を紹介します。

export TZ="Asia/Tokyo"

current_hour=$(date +%H)
today=$(date +%Y%m%d)

export REPORT_DATE=$today

java -jar embulk-0.11.4.jar run bigquery_to_mysql_report_daily_session_raw.yml.liquid

echo "データを入れることに成功しました"

以下のコマンドで、embulkを実行します。

java -jar embulk-{embulkのバージョン}.jar run ファイル名

bigquery_to_mysql_report_daily_session_raw.yml.liquid

今回は一例として、BigQueryから日別のセッション数を取得し、それをデータベースに挿入する方法を紹介します。

in:
  type: bigquery
  project: 'sample'
  keyfile: ./keyfile.json
  sql: |
    SELECT
      DATE(timestamp_micros(event_timestamp), 'Asia/Tokyo') AS report_date,
      'SERVICE' AS service,
      platform,
      device.category AS device_category,
      CASE
        WHEN device.operating_system IS NULL THEN 'UNDEFINED'
        ELSE device.operating_system
      END AS device_os,
      COUNT(*) AS session_count
    FROM
      `analytics_153613109.events_*`
    WHERE
      _TABLE_SUFFIX = '{{ env.REPORT_DATE }}'
      AND event_name = 'session_start'
    GROUP BY
      report_date, platform, device.category, device.operating_system
    ;

  columns:
    - {name: report_date, type: string}
    - {name: platform, type: string}
    - {name: device_category, type: string}
    - {name: device_os, type: string}
    - {name: user_count, type: long}

out:
  type: mysql
  host: test
  user: test
  password: password
  database: test_db
  table: report_daily_active_user_raw
  mode: merge
  column_options:
    report_date: {value_type: string, timestamp_format: '%Y-%m-%d'}

embulkでは、inoutに分けて実装をしました。

今回のin句では、BigQueryから日別のセッション数を取得しました。この時の./keyfile.jsonには、BigQueryからデータを取得するためのクレデンシャル情報が書かれています。

また、out句では、MySQLへデータを入れるための実装を行っています。必要に応じて、パスワードなどは環境変数に置き換えてください。

docker-compose.yaml

docker-compose.yamlの実装例です。

name: 'sample_service'
services:
  embulk:
    build:
      context: .
      dockerfile: Dockerfile
    image: latest
    tty: true

最後に、docker-composeを実行し、実際にデータが入ることが確認できたら、実装完了です。

最後に

今回は、embulkでBigQueryからデータベースにデータを移行しました。embulkを使うことで、簡単にデータ移行できます。機会があったら、ぜひ皆さんも使ってみてください。

参考文献

www.embulk.org

zenn.dev

【Flutter】pubspec.yamlでのライブラリバージョン指定とセマンティックバージョニングについて

こんにちは。エキサイト株式会社でエンジニアをしている岡島です。

今回は、pubspec.yamlでのパッケージの管理について、調べたことを共有していこうと思います。

pubspec.yamlでのバージョン指定方法

pubspec.yamlファイルでは、さまざまな方法でパッケージのバージョンを指定できます。主な指定方法は以下の通りです。

バージョン指定なし

空欄やanyで指定すると全てのバージョンを許容します。

dependencies:
  hoge:
  fuga: any # 空欄のバージョン制約の明示的な宣言として機能

指定されたバージョン

指定されたバージョンのみを使用します。

dependencies:
  hoge: 1.2.3

バージョンの範囲指定

不等号を使用することで、指定バージョン以上や以下などの範囲指定ができます。組み合わせることも可能で、値を >=1.2.3 <2.0.0のように指定すると、1.2.3以上2.0.0未満の任意のバージョンという意味になります。

dependencies:
  hoge: >=1.2.3 # 1.2.3以上のバージョンを使用
  fuga: >1.2.3 # 1.2.3よりも後のバージョンを使用

キャレット構文での指定

キャレット^ を用いることで、指定されたバージョンからメジャーバージョンが変わらない範囲で最新のものを使用できます。

キャレット構文でのバージョン指定は、パッケージ管理のベストプラクティスとしてDartのドキュメント紹介されています。

https://dart.dev/tools/pub/dependencies#use-caret-syntax

dependencies:
  hoge: ^1.2.3  # 1.2.3以上2.0.0未満のバージョンという意味

パッケージバージョンのアップグレード

flutter pub upgradeコマンドを使用して、依存関係を最新のバージョンに更新できます。

通常のアップグレード

flutter pub upgrade

このコマンドは、pubspec.yamlファイルで指定された制約内で、最新の互換性のあるバージョンにアップグレードします。

メジャーバージョンを含むアップグレード

flutter pub upgrade --major-versions

このコマンドは、メジャーバージョンの更新も含めて、利用可能な最新のバージョンにアップグレードします。pubspec.yamlファイルの制約も自動的に更新されます。

セマンティックバージョニングとは

プロジェクトが多くの外部ライブラリやパッケージに依存している場合、「依存性地獄(dependency hell)」と呼ばれる問題がありました。このような問題を解決するために考えられたのがセマンティックバージョニングです。

「依存性地獄(dependency hell)」の問題

ソフトウェアシステムが大きくなり、多くのパッケージを統合すると、依存関係の管理が困難になる問題です。

依存性地獄の2つの主な課題:

  1. バージョンロック:依存関係の指定が厳しすぎて、パッケージのアップグレードが困難になる状態。
  2. バージョン乱用:依存関係の指定が緩すぎて、将来のバージョンとの互換性を過度に仮定してしまう状態。

このような課題を解決するために、バージョン番号の割り当てとインクリメントに関する簡単なルールを決めたのが、セマンティックバージョニングと呼ばれるものです。

セマンティックバージョニングの基本原則

バージョン番号は

  1. メジャーバージョン
  2. マイナーバージョン
  3. パッチバージョン

の3つの部分に分かれおり、X.Y.Z(Major.Minor.Patch)のようにピリオドで区切られています。

メジャーバージョン: 1.x.x

後方互換性のない変更が導入された場合にインクリメントされます。 メジャーバージョンが上がると、既存のコードが動作しなくなる可能性があるので注意が必要です。

例: APIの大幅な変更、既存機能の削除、アプリケーションのアーキテクチャの変更など。

マイナーバージョン: x.1.x

後方互換性を保ちつつ、新機能が追加された場合にインクリメントされます。 マイナーバージョンが上がっても、既存のコードは通常影響を受けません。

例: 新しいメソッドの追加、オプションのパラメータの追加、既存機能の拡張など。

パッチバージョン: x.x.1

バグ修正や小さな改善が行われた場合にインクリメントされます。 パッチバージョンの変更は、既存のコードに影響を与えません。

例:セキュリティ修正、パフォーマンス改善、タイポの修正など。

さいごに

今回はpubspec.yamlでのバージョン指定方法とセマンティックバージョニングについて紹介しました。キャレット構文やセマンティックバージョニングを適切に理解して開発に取り組もうと思います。

参考文献

Package dependencies | Dart
Package versioning | Dart
Upgrading | Flutter
[Flutter] pubspec.yaml の文法/チートシート #Dart - Qiita
セマンティックバージョンについて

Alpine.jsで特定の要素の外側がクリックされたことを検知する方法

こんにちは。エキサイトでデザイナーをしている齋藤です。

今回はAlpine.jsで、特定の要素の外側がクリックされたことを検知できる、x-onのModifierの一つである.outsideが便利だったのでシェアしたいと思います。

作りたいもの

冒頭に作りたいものをお示しします。CodePenをご用意しましたので、ぜひ触れてみてください。

ドロップダウンの場合、ボタンのみではなく、ドロップダウンの外側をクリックしても閉じられるようにすることで、ユーザーはより直感的に操作ができ、体験も向上すると考えます。

See the Pen Untitled by AyumuSaito (@ayumusaito-excite) on CodePen.

これを実現するためには、ユーザーが特定の要素の外側をクリックしたことを検知する必要がありますが、純粋なJSですと複数行の記述が必要となる一方で、Alpine.jsであれば1行で解決します。

まずはドロップダウンを作ってみる

まずは、Alpine.jsで簡単なドロップダウンの挙動を作ってみます。

<div x-data="{ isOpen: false }">
  <button @click="isOpen = !isOpen">
   ボタン
  </button>
  <div x-show="isOpen">
    コンテンツ
  </div>
</div>

これでボタンをクリックすると、isOpenの真偽値が入れ替わり、x-show属性のある要素の表示/非表示も切り替わるシンプルなドロップダウンの仕組みを作ることができました。

x-on.outsideで要素の外側がクリックされたことを検知する

先の例のままですと、ボタンのみでしか操作できません。

ボタンに加えて、x-show属性のある要素の外側がクリックされた場合にもisOpenfalseになるようにしたいです。

このような場合に、x-on:click.outsideを使用すると、要素の外側がクリックされたことを検知できます。

<div x-data="{ isOpen: false }">
  <button @click="isOpen = !isOpen">
   ボタン
  </button>
  <div x-show="isOpen" @click.outside="isOpen = false">
    コンテンツ
  </div>
</div>

x-show属性のある要素に@click.outside="isOpen = false"を追加します。

これで、x-show属性のある要素の外側がクリックされた場合に、isOpenfalseにすることができるようになりました。

なお、x-on:@で代替することができます。(例:x-on:clickの場合は@click

さいごに

Alpine.jsは少ない記述量で処理を実現できる上、HTMLタグの属性としてJSを記述するため、JSと対象となるHTML要素が散らばらずに一体となることから、開発体験も良いと感じています。

この記事が、これからAlpine.jsを使い始めるという方の一助となれば幸いです。

ご精読ありがとうございました。

[AWS Copilot CLI] 環境作成時に作られたロールに対し、デプロイ用のロールを信頼ポリシーに追加する方法

はじめに

新卒2年目の岡崎です。今回は、AWS Copilot CLIで環境作成時に作られたロールに対し、デプロイ用のロールを信頼ポリシーに追加する方法を紹介します。

問題点

Github ActionsでAWS上のサービスにデプロイする時、Github Actionsのデプロイ用のロールを使っています。この時、AWS Copilot CLIでの環境作成時に作られたロールに対し、Github Actionsのデプロイ用のロールを信頼ポリシーに追加しければ、デプロイできませんでした。

よって、この問題を解決し、無事にGithub Actionsでデプロイすることを今回の目的とします。

解決策

今回は環境作成時に作られたロールに変更を加えたいので、まずは以下のコマンドを打ちます。

copilot env override --tool yamlpatch

これにより、~/copilot/enviroments配下に、以下のようなファイルが自動で作成されます。

YAMLパッチでは、addreplacedeleteを指定できます。今回は、ロールに信頼関係の追加をしたかったので、以下のような実装をしました。

- op: add
  path: 作成時に作られたロール
  value:
    Effect: Allow
    Principal:
      AWS: "デプロイ用のロール"
    Action: 必要な権限

補足

以下のコマンドで、AWS Copilot CLIによって反映したCloudFormationの実装内容を確認することができます。

copilot env package --name 環境名

差分のみを確認する場合は、—diffをつけます。

copilot env package --name 環境名 --diff

ここまで実装し、環境の再デプロイをします。環境作成時に作られたロールに対し、Github Actionsで使用するロールの信頼関係が追加できていれば、実装完了です。

最後にGithub Actionsでデプロイできれば、問題解決です。

最後に

環境作成時に作られたロールに対し、デプロイ用のロールを信頼ポリシーに追加する方法を紹介しました。AWS Copilot CLIでは細かな変更をYAMLパッチでできるので、今後も適宜使っていこうと思います。

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

募集職種一覧はこちらになります。

www.wantedly.com

新卒がアプリバグ出し会に取り組み、アプリのクラッシュ率を改善した話

こんにちは!エキサイトでアプリエンジニアをしている岡島です。今回はアプリ開発に携わるようになり、アプリクラッシュ率の改善につながった「バグ出し会」の取り組みについてお話しします。

バグ出し会とは?

私たちのチームでは、2週間に1回、30分間のバグ出し会の取り組くを始めました。この会では、開発メンバー全員がウーマンエキサイトのアプリを実際に操作し、バグや不具合を見つけ出す作業を行います。バグ出し会は 「みんなでアプリを良くしていく」という共通認識のもと集まり、アプリの改善点についての会話から雑談の会話を交えて、チームの交流の場にもなっています。バグを発見したときは、弊社で使用しているタスク管理ツールであるJiraに、バグの内容と再現手順を簡潔に記述します。可能な限り、スクリーンショットや画面録画なども添付して原因の特定をスムーズにしています。

なぜバグ出し会を始めたのか?

バグ出し会を始めた主な目的は以下の2つです:

  • アプリのクラッシュ率の低下
  • 不具合、バグの早期発見と解消

担当しているアプリのクラッシュの影響を受けていないユーザーが95%~96%となっており、約5%のユーザーにクラッシュの影響が出ていました。そこで、定期的にバグを洗い出し、迅速に対応することで、アプリの品質向上を目指すことにしたのです。

クラッシュ率の改善によって期待したいこと

FROSKの「アプリクラッシュ調査レポート2019年下半期」によると、10代・20代の約25%が「強制終了したり固まる」アプリをひどいアプリと認定しており、特に15~19歳の女性では32.6%がこのような評価をしています。さらに、同調査では約6割のユーザーが4回以下のクラッシュでアプリを使用しなくなると回答しています。

www.bcnretail.com

これらの調査結果から、クラッシュ率の改善によって以下のような効果が見込めます。

  1. ユーザーの継続利用率の向上
  2. アプリの評判改善

ユーザー体験をより良くしていくことで、気持ちよくアプリを使っていただけるように努めたいと思います。

バグだし会の成果

この取り組みを始めてから約1ヶ月が経過し、Firebase Crashlyticsのクラッシュの影響を受けていないユーザーの数値が以下のように改善されました。

  • Android 96.53% (6/1時点)→ 99.57% (7/28時点)
  • iOS 95.88% (6/1時点)→ 97.59% (7/28時点)

まとめ

今回の記事では、バグ出し会を通じてアプリのクラッシュ率の改善につながったというお話を共有しました。バグの再現方法がわかると、プログラムのどの部分が原因であるかを特定しやすくスピーディに修正することができました。クラッシュ率はユーザーの離脱につながりやすいので、今後も引き続き安定したアプリにしていきたいと思います。

ウーマンエキサイト

iOS

ウーマンエキサイト

ウーマンエキサイト

  • Excite Japan Co.,Ltd.
  • ライフスタイル
  • 無料
apps.apple.com

Android

play.google.com

AWS Copilot CLIのサイドカーのログドライバーにFireLensを適用させる

はじめに

エキサイト株式会社 バックエンドエンジニアの山縣(@zsp2088dev)です。 現在、既存サービスのリビルドにあたり、AWS Copilot CLIを使用してコンテナ環境を構築しています。 コンテナ環境には、サイドカーコンテナにnginxを使用しており、またログをFireLensで管理しています。 この時、Manifestにloggingセクションを定義し、メインコンテナのログドライバにFireLensを適用できたものの、サイドカーコンテナにはFireLensを適用できない問題がありました。

本記事では、上記の原因と解決策についてまとめます。 また、本記事内で扱うAWS Copilot CLIのバージョンはv1.34.0です。

概要

以下の画像は、今回扱うコンテナ環境の全体図です。 ALBからのリクエストを、サイドカーコンテナであるnginxコンテナで受けるような構成にしています。 Manifestにhttp.target_containerセクションを定義することで、サイドカーコンテナでもリクエストを受け付けることができるようになります。

ログの設定では、Manifestにloggingセクションを定義することで、FireLensを使用することができます。 これにより、アクセスログはS3に、エラーログはS3とCloudWatchに流すといった柔軟なログの管理ができるようになります。

logging:
  image: example-fluentbit
  configFilePath: /fluent-bit/etc/fluent-custom.conf

この時、メインコンテナのログドライバと、サイドカーコンテナのログドライバを見てみると、メインコンテナではawsfirelensサイドカーコンテナではawslogsが適用されていることが確認できました。 loggingセクションを定義しているので、サイドカーコンテナでもFireLensが適用されることを期待していたため、なぜ適用されないのか調査することになりました。

原因

aws/copilot-cliリポジトリを見てみると、サイドカーコンテナの場合、ログドライバはawslogsに固定されてるようです。 そのため、Manifestにloggingセクションを定義したとしても、サイドカーコンテナのログドライバはawsfirelensに変更することができませんでした。

{{- if $sidecar.Secrets}}
  Secrets:
  {{- range $name, $secret := $sidecar.Secrets}}
  - Name: {{$name}}
  {{- if $secret.RequiresImport}}
    ValueFrom:
      Fn::ImportValue: {{ quote $secret.ValueFrom }}
  {{- else}}
    ValueFrom: {{if not $secret.RequiresSub }} {{$secret.ValueFrom}} {{- else}} !Sub 'arn:${AWS::Partition}:{{$secret.Service}}:${AWS::Region}:${AWS::AccountId}:{{$secret.ValueFrom}}' {{- end}}
  {{- end}}
  {{- end}}
{{- end}}
  LogConfiguration:
    LogDriver: awslogs
    Options:
      awslogs-region: !Ref AWS::Region
      awslogs-group: !Ref LogGroup
      awslogs-stream-prefix: copilot

github.com

解決策

AWS Copilot CLIの生成するCloudFormationテンプレートに対して、YAMLパッチを当てることで、サイドカーコンテナのログドライバをawsfirelensに変更することができます。

生成されたCloudFormationのテンプレートを見ると、サイドカーコンテナは、メインコンテナ、firelens_log_routerコンテナの次に位置します。 そのため、配列の2番目を指定してログドライバにawsfirelensを指定することで、サイドカーコンテナのログドライバをawsfirelensに変更できます。

# ログドライバをawsfirelensに置き換える。
- op: replace
  path: /Resources/TaskDefinition/Properties/ContainerDefinitions/2/LogConfiguration/LogDriver
  value: awsfirelens

# 不要なオプションを除去する。除去しない場合はエラーになる。
- op: remove
  path: /Resources/TaskDefinition/Properties/ContainerDefinitions/2/LogConfiguration/Options

YAMLパッチの詳細については、以下のドキュメントをご参照ください。

aws.github.io

おわりに

AWS Copilot CLIを使用することで、手っ取り早くコンテナ環境を構築できます。 一方で、より踏み込んだ設定をしたい場合は、YAMLパッチを当てるためにCloudFormationのテンプレートと向き合う必要があります。 本記事がお役に立てれば幸いです。

採用アナウンス

エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しています。 また、長期インターンも歓迎しています。 少しでもご興味がございましたら、お気軽にご連絡頂ければ幸いです。

▼ 募集職種一覧 ▼ recruit.jobcan.jp

Figma VariablesのScoping機能で使用先を間違わないトークン設計をする

こんにちは。エキサイトでデザイナーをしている齋藤です。

今回はFigmaのVariablesでデザイントークンを設計する際に、Scoping機能を用いて使用先を間違わないようなトークンを設計する方法をご紹介します。

解決したいこと

例えば、角丸用のトークンを作成したとします。

Variablesで角丸用のトークンを作成

単にトークンを作成した初期状態ですと、GapなどCorner Radius以外の箇所でもトークンを呼び出せてしまいます。

Gapの値でも角丸用のトークンが呼び出せてしまう

このような状態にしておくと、トークン設計者が意図しない箇所でトークンが使用されてしまい、デザインの一貫性が失われたり、変更の際に漏れが生じてしまう恐れがあります。

Scoping機能で使用先を制限する

Scoping機能を用いることで、トークンごとに使用先を制限することができます。

Variablesの各行右端の設定アイコンから、Scopingを設定することができます。

Scopingの初期値

角丸用のトークンの場合は、Corner Radiusにのみ使用されたいため、「Corner Radius」にチェックを入れ、その他からはチェックを外します。

その際、全行選択して上でScopingを設定することもできます。

Corner Radiusでのみ使用できるように設定

こうすることで、Corner Radius以外の箇所からトークンを呼び出せなくすることができ、一貫性と保守性が担保された状態になりました。

さいごに

今回は、Figma VariablesのScoping機能で使用先を間違わないトークン設計をする方法をご紹介しました。

Figmaを用いてデザインシステムを開発する場合にはとても有用ですので、ぜひお試しください。

ご精読ありがとうございました。

Tailwind CSSで文章を行数制限したい場合はline-clamp-*が便利

こんにちは。エキサイトでデザイナーをしている齋藤です。

今回はTailwind CSSを使用しているプロジェクトで、文章を行数制限(超えた部分は3点リーダーで省略)したい場合に使える便利なユーティリティクラス line-clamp-*をご紹介します。

純粋なCSSの場合

純粋なCSSの場合、行数制限をしたい場合は overflow display -webkit-box-orient -webkit-line-clampの4つのプロパティを組み合わせることで設定することができました。

p {
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
}

しかし、毎度設定することが面倒だったり、メディアクエリを使用してレスポンシブに行数制限しようとすると更に長くなったりと、煩雑になりがちです。

Tailwind CSS場合

Tailwind CSSの場合、先述の4つのプロパティがひとまとまりになったユーティリティクラスとしてline-clamp-*が用意されています。

*に表示させる行数を与えることで、簡単に行数制限をスタイリングすることができます。

例えば3行で制限したい場合は以下の記述で足ります。

<p class="line-clamp-3">...</p>

更にレスポンシブにしたい場合も、ブレイクポイント用のユーティリティクラスと組み合わせることで簡単に実現できます。

<p class="line-clamp-3 md:line-clamp-2">...</p>

ブレイクポイントについては以下の記事もご覧ください。

tech.excite.co.jp

さいごに

今回はTailwind CSSで行数制限したい場合にline-clamp-*が便利であることをご紹介しました。

これからTailwind CSSを活用される方の一助となれば幸いです。

ご精読ありがとうございました。

【新卒研修体験談】社内ツールの実運用で見えてきた課題と反省点

こんにちは、エキサイト株式会社でエンジニアをしている2024年度新卒の岡島です。 今回は、新卒研修プロジェクトで開発した社内ツールの実運用が始まったので、このプロジェクトを振り返り見えてきた課題と反省点を共有したいと思います。

はじめに

新卒研修では、2人のデザイナーと2人のエンジニアで約1ヶ月間の技術研修を行いました。この研修では、エキサイトグループの全社員を対象としたメンタル状況を定期的にアンケート形式で集計する社内ツールの開発に取り組みました。

↓こちらの記事でも紹介しているのでぜひご覧ください。 tech.excite.co.jp

社内ツールの運用が始まっての反響

7月から実際に運用が開始され、予想以上の好評を得ることができました。今回開発した社内ツールは全社員が利用するということもあり、問題が起こらないか心配でしたがいい評価をいただき嬉しく思いました。 アンケート回答者からは、自由解答欄に「使いやすい」などといった声が届いていたようです。上司からもこの社内ツールが好評であるとの声をいただきました。管理者の方からもこれまで手作業で行っていたリマインドの作業が自動化されて業務時間が短縮され楽になったとのことでした。

エンジニアとして、必要な機能を実装できたことや業務効率化へ貢献している実感が湧き非常にいい経験になったと感じました。

エンジニアとしての反省点

運用面での課題

実運用が始まってから、いくつかの課題が明らかになりました。最も大きな反省点は、運用時にプログラムの変更が必要になる場面が多かったことです。 理想的には、プログラムに手を加えることなく、設定変更だけで対応できるような設計にすればよかったと反省しています。 実際に以下のようなケースに遭遇し、運用が大変であると感じました。

1. イレギュラーケースへの対応が大変

例えば、特定の社員は例外で別の部署の所属として集計を行いたいというケースが発生しました。実装が間に合わなかったこともあるのですが、このような要望に対して、例外用のテーブルを作成しその都度データをエンジニアが追加していました。管理者が専用の管理画面からこのような設定を行えるようにすれば、エンジニアが対応する必要がなくなり、運用面での改善ができたのではないかと感じました。

2. 将来の拡張性を意識できていなかった

エキサイトはグループ会社であり、各社で社員情報を取得するAPIキーが異なります。そのため、8月に新たな会社がアンケート対象に追加された際、プログラムの修正が必要になりました。管理画面から新しい会社のAPIキーを追加できるような機能を実装しておくべきでした。そうすることにより、プログラムの変更なしに今回行ったような対応でき運用が容易になると思いました。

学んだ教訓と今後について

実際に開発した社内ツールが利用され始めて、運用を見据えた設計の重要性を痛感しました。設計の段階では、ユーザーの使用用途をヒアリングすることばかりに目が行き、運用や保守などといった将来を見据えた実装という視点が抜けていた気がします。今後は管理画面の機能追加といった運用面の改善に努めたいと思います。

Flutter 3.22アップデートで起きたビルドエラーの解決方法 RangeError (offset): Invalid value: Not in inclusive range

こんにちは。エキサイト株式会社でアプリエンジニアをしている岡島です。Flutter SDKのバージョンアップをする際、iOSAndroidともにビルドができなくなりました。今回はなぜビルドができなくなってしまったのか、そのエラーの原因と解決方法について共有したいと思います。

はじめに

Flutter 3.22にアップデートを行った際に、APKビルド時に次のようなエラーが発生し、ビルドを完了することができませんでした。

Unhandled exception:
RangeError (offset): Invalid value: Not in inclusive range 0..1241: 10068
#0      RangeError.checkValueInInterval (dart:core/errors.dart:313)
#1      Source.getLocation (package:kernel/ast.dart:14992)
#2      Component.getLocation (package:kernel/ast.dart:14787)
#3      _getLocationInComponent (package:kernel/ast.dart:15181)
#4      Procedure._getLocationInEnclosingFile (package:kernel/ast.dart:3329)
#5      TreeNode._getLocationInEnclosingFile (package:kernel/ast.dart:207)
#6      TreeNode._getLocationInEnclosingFile (package:kernel/ast.dart:207)
#7      TreeNode.location (package:kernel/ast.dart:203)
#8      localFunctionName (package:vm/transformations/type_flow/utils.dart:449)
#9      SummaryCollector.createSummary (package:vm/transformations/type_flow/summary_collector.dart:628)
#10     TypeFlowAnalysis.getSummary (package:vm/transformations/type_flow/analysis.dart:1782)
#11     _DirectInvocation._processFunction (package:vm/transformations/type_flow/analysis.dart:398)
#12     _DirectInvocation.process (package:vm/transformations/type_flow/analysis.dart:287)
#13     _WorkList.processInvocation (package:vm/transformations/type_flow/analysis.dart:1663)
#14     _WorkList.process (package:vm/transformations/type_flow/analysis.dart:1606)
#15     TypeFlowAnalysis.process (package:vm/transformations/type_flow/analysis.dart:1811)
#16     transformComponent (package:vm/transformations/type_flow/transformer.dart:121)
#17     runGlobalTransformations (package:vm/kernel_front_end.dart:589)
#18     compileToKernel (package:vm/kernel_front_end.dart:489)
<asynchronous suspension>
#19     FrontendCompiler.compile (package:frontend_server/frontend_server.dart:639)
<asynchronous suspension>
#20     starter (package:frontend_server/starter.dart:101)
<asynchronous suspension>
#21     main (file:///b/s/w/ir/x/w/sdk/pkg/frontend_server/bin/frontend_server_starter.dart:13)
<asynchronous suspension>

Target kernel_snapshot failed: Exception


FAILURE: Build failed with an exception.

この記事では、こちらのエラーの解決方法について共有します。

環境

Dart 3.4.3
Flutter SDK: 3.22.2

解決した方法

同様のエラーを調べていく中でFlutterのissueに、エラーに関連する問題が報告されていました。

https://github.com/flutter/flutter/issues/148668

内容としては、このエラーはDartSDK側に原因があるようで、flutter_state_notifier:1.0.0を使用すると起こるエラーであることがわかりました。

こちらのコミットによってエラーが修正されたようなので、この修正が反映されているFlutterの3.23.0-0.1.preを使用することで対応することにしました。

まとめ

今回はFlutter SDKのアップデートを行う際に遭遇したエラーの解決をまとめました。SDKやライブラリのissueを見ることや詳細のログを見て原因を特定することの大切さを実感しました。

Tailwind CSSでブレイクポイントをカスタマイズする方法

こんにちは。エキサイトでデザイナーをしている齋藤です。

今回はTailwind CSSでレスポンシブデザイン用のブレイクポイントをカスタマイズする方法をご紹介したいと思います。

はじめに

Tailwind CSSではブレイクポイント用のユーティリティクラスが用意されており、メディアクエリを書くことなくレスポンシブデザインを実装することができます。

Tailwind CSSによって用意されたブレイクポイント

従来のCSS

.container {
  display: flex;

  @media (min-width: 640px) { 
    display: block;
  }
}

Tailwind CSSのユーティリティクラスを用いたレスポンシブデザインの実装

<div class="flex sm:block">...</div>

ユーティリティクラスの前にsm:lg:をつけることでブレイクポイント用にスタイリングできます。

カスタマイズ方法

ブレイクポイントはTailwind CSSによって予め用意されていますが、独自に定義することもできます。

カスタマイズしたい場合は、tailwind.config.jsのtheme設定のscreenセクションに記述をします。

/** @type {import('tailwindcss').Config} */
module.exports = {
  theme: {
    screens: {
      // @media (min-width: 480px) { ... }を "mobile"として定義
      'mobile': '480px',
    },
  }
}

その際、プレフィックスは自由に設定することができます。

max-widthで定義したい場合

デフォルトの記述方法ですと、設定した値はmin-widthとして認識されます。

max-widthとして定義したい場合は以下のように記述することで設定することができます。

/** @type {import('tailwindcss').Config} */
module.exports = {
  theme: {
    screens: {
      // @media (max-width: 480px) { ... }を "mobile"として定義
      'mobile': { max: '480px' },
    },
  }
}

さいごに

今回はTailwind CSSでレスポンシブデザイン用のブレイクポイントをカスタマイズする方法をご紹介しました。

プロジェクトに合わせて拡張できることがTailwind CSSの魅力ですね。

Tailwind CSSを使っている、これから使いたいという方の一助となれば幸いです。

ご精読ありがとうございました。

SpringBootのローカル環境でSSOしているプロファイルでAWS Parameter Storeから値を取得する方法

エキサイト株式会社メディア事業部エンジニアの佐々木です。タイトルがわかりにくいですが、SpringBootのローカル開発環境でもAWS PamareterStoreにある値を使用したいのでご紹介します。通常はAWS ParameterStoreのパッケージを入れるだけで、すぐできるのですが弊事業部はAWS SSOを全面的に採用しています。($HOME/.aws配下に設定を書かないです)このAWS SSOの場合の設定が割となかったので書きます。

前提

$ java --version
openjdk 21.0.3 2024-04-16 LTS
OpenJDK Runtime Environment Corretto-21.0.3.9.1 (build 21.0.3+9-LTS)
OpenJDK 64-Bit Server VM Corretto-21.0.3.9.1 (build 21.0.3+9-LTS, mixed mode, sharing)

$ ./gradlew --version

------------------------------------------------------------
Gradle 8.5
------------------------------------------------------------

Build time:   2023-11-29 14:08:57 UTC
Revision:     28aca86a7180baa17117e0e5ba01d8ea9feca598

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

$./gradlew bootRun
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.5)

AWSの設定

AWS ParameterStore を下記のような設定にします。

key: /config/sample/techblog.demo
value: デモです

依存関係の設定

build.gradleに下記を書きます。

dependencies {
....
        implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.1.0")
        implementation platform("software.amazon.awssdk:bom:2.26.7")
        developmentOnly "software.amazon.awssdk:sso"
        developmentOnly "software.amazon.awssdk:ssooidc"
        implementation "io.awspring.cloud:spring-cloud-aws-starter-parameter-store"
....
}

記述量が多いですが、spring-cloud-aws-dependenciesAWSがSpringBoot用に提供してくれているライブラリを統合管理してくれています。あとは必要なものだけ追加するような感じです。AWS SSO を使用している場合に必要なのは、下記の2つです。

developmentOnly "software.amazon.awssdk:sso"
developmentOnly "software.amazon.awssdk:ssooidc"

Application.ymlの設定

application.ymlの設定にて、ParameterStoreの読み込み指定を行います。

spring:
  config:
    import: aws-parameterstore:/config/sample

aws-parameterstore という接頭辞で起動時にParamterStoreからパラメータを読み出してくれます。

コード例

application.ymlの設定で、SpringBootの起動時にParameterStoreからの読み込みはできるようになりました。これを下記のようなコードでJavaConfigにマッピングを行います。

    @ConfigurationProperties("config.sample")
    public record S3ConfigProperties(TechBlog techblog) {
        record TechBlog(String demo) {}
    }

最近は、record の対応が進んで、application.ymlからのプロパティの読み込みもイミュータブルで読み込めるようになりました。

起動します

SpringBootを起動する前に、aws sso login コマンドを実行してログインする必要があります。

aws sso login --profile ${sample-app} 

これでログインしたあとにSpringBootを起動するとデータが取得できています。

ログインしない場合

aws sso login でログインせずにSprginBootを実行すると下記のようなエラーがでます。

Config data resource '[ParameterStoreConfigDataResource@77795936 context = '/config/sample', optional = false]' via location 'aws-parameterstore:/config/sample' does not exist

まとめ

セキュリティの重要性が高まっている昨今、ローカルでもAWSパラメータストアが使いたいケースはあるかと思います。この設定をしておくと、AWSの他のサービスへの接続もライブラリが提供されている範囲内でローカル環境から接続できるので便利です。ぜひ使ってみてください。

さいごに

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

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

AWS Copilot CLI の manifest.yml で定義できない設定をYaml Patchを使用して設定する

エキサイト株式会社エンジニアの佐々木です。AWS Copilot CLIはECS Fargateを使うにはかなり便利なツールですが、その分設定ファイルでできないことも結構あります。AWS Copilot CLIのチームもそれがわかっているのか、YamlでPatchを当てる機能をリリースしています。

実現したいこと

AWS Copilot CLI で作成したECS Fargateの環境のログ送信をAWS CloudWatch LogsからFirelensに変更する になります。CloudWatch Logsは料金が高いのでエラーログ以外はFirelensからS3に転送して節約したいと思います。

manifest.ymlではできないこと

AWS Copilot CLIは標準的な設定やポリシーなどは生成されますが、それ以外のことはmanifest.ymlではできません。

copilot svc override コマンド

manifest.ymlでは設定できないことをcopilot svc override コマンドで設定ファイル出力します。

copilot svc override --name sample-app --tool yamlpatch

※ --tool cdk もできます

下記ファイルが出力されます。

overrides/
  - cfn.patches.yml
  - README.md

cfn.patches.yml に定義を追加していきます。

cfn.patches.yml に定義を追加

書き方は独特ですが、下記のように書くことが可能です。あくまでパッチですので、既存の設定を書き換えたり、追加したりがメインになります。 下記の設定は、S3へのPutObjectGetObjectを設定しています。

 - op: add
   path: /Resources/TaskRole/Properties/Policies/-
   value:
     PolicyName: 'S3AccessPolicy'
     PolicyDocument:
       Version: '2012-10-17'
       Statement:
         - Effect: Allow
           Action:
             - 's3:GetObject'
             - 's3:PutObject'
           Resource: '*'
  • op: add/remove/replace から選べます。
  • path: 適用したいCloudFormationのpathを記述します。
  • value: 適用したい値を書きます。

copilot svc package コマンド

上記のようにファイルを作成したら、下記コマンドで差分を確認することが可能です。

copilot svc package --name sample-app --diff

差分を確認して期待どおりならデプロイするだけとなります。

またcolilot svc packageコマンドの別の使い方で、下記コマンドのように現在設定されているCloudFormationの設定をみることも可能です。

copilot svc package --name sample-app

まとめ

AWS Copilot CLIYaml Patchですが、割と簡単にあてることが可能です。ほとんどの設定がAWS Copilot CLIYaml Patchを組み合わせることで実現が可能です。

最後に

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

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