Amazon SESでの運用と注意点

エンジニアのAです。今回はAmazon SESについてご紹介します。

AmazonSESとは

Amazon SESはAmazon Simple Email Serviceの略で、SES経由で自身のメールアドレスとドメインを使用してメールを送受信するためのコスト効率の高いメール配信サービスです。

AWS SESは送受信されるメールの量に基づいて料金が発生します。無料枠に関しては、以前は月に62,000通まで無料で送信できていましたが、2023年8月の改訂により

  • 送信 E メール

  • 受信 E メール

  • Virtual Deliverability Manager の送信 E メール処理

の3つのメッセージ料金が毎月最大 3,000 件分無料になります。 (SESの使用開始後の最初の12 か月間)

2024年現在の料金は

  • メール送信 1000件ごとに0.10USD、1GB ごとに0.12 USDの追加料金

  • メール受信 1000件ごとに0.10USD、受信メールチャンク1000 件につき0.09 USDの追加料金

となっています。

最新の料金は下記をご参照ください。 aws.amazon.com

無料枠が減ったとはいえ、月に32KBのメールを200万件送信しても

  • (2,000,000 (メールの件数) - 3,000 (無料枠のメッセージ)) × 0.0001 USD (1 件あたりの料金) = 199.70 USD

  • (0.000032 GB × 2,000,000 - 3,000(無料枠のメッセージ)) × 0.12 USD (データ 1 GB あたりの料金) = 7.67 USD

上記を合わせて207.37USD、日本円換算(145円/ドル)で約3万円に収まります。

これだけ大量のメールを送信してもこの値段で収まり、送信が数万程度ならば数百円程度で済むため、コストパフォーマンスは非常に良いと言えるでしょう。

運用上の注意点

AWS SESにはメールを送れる制限が秒間(最大送信レート)と日間(送信クォータ)が存在しています。

  • 送信クォータ 過去 24 時間に送信したメール数

  • 最大送信レート Amazon SES がアカウントから 1 秒あたりに受け入れることができるメールの最大数

メッセージの送信がアカウントの 1 日の最大数を超える場合、Amazon SES への呼び出しは拒否されます。その為、短期間にメールを大量に送信しようとするとこの送信レートに引っかかる可能性がある為、状況に応じて送信クォータの引き上げをリクエストする必要があります。

引き上げの手順としては、

  1. AWSマネジメントコンソールにサインインし、Amazon SESコンソールを開く

  2. [アカウントダッシュボード]を選択し、送信制限欄にある[制限の引き上げのリクエスト]を押す

  1. サービスクォータの中から、引き上げたいクォータかレートを選択し、[アカウントレベルでの引き上げをリクエスト]を押して、希望する一日あたりの送信クォータか、最大送信レートを入力してリクエス

となっています。

必ずしも承認されるわけではないようなので、クォータ増加リクエストが拒否された場合は、AWS サポートに問い合わせると良いようです。

送信の引き上げを行う際は、[アカウントダッシュボード]の[1 日のE メール使用量]欄を見つつ、送信制限に近づかないように適切に設定しましょう。

また、メールの各種イベントの統計ではなくログを取るには設定が必要です。

参考

repost.aws

Gmailのスパム対策に抵触しないためにもCloudWatchでBounce rate(返送率)やComplaint Rate(苦情率)の閾値設定や監視設定を行った方が良いです。

参考

repost.aws

さらに返送が連続した場合や苦情が発生した場合の処理や通知の設定もしておくと良いでしょう。

PHPでの実装の注意点

実際にSESへのリクエストをPHPのバッチなどで運用している場合、そのまま送信リクエストを送り続けてしまうと最大送信レートを超えてしまうため、usleepなどで秒間のリクエスト間隔を調整する必要があります。

最大送信レートはある程度ならばバーストしても耐えることはできるようですが、長くは続かないようです。

また、他のバッチなどでもリクエストをしていた場合、気付かぬうちに送信レートがバーストしてメールの送信ができないといったことも起こりうるため、SESへの自動送信機能を新たに設定する際は必ず[アカウントダッシュボード]で現在の状態を確認するようにしましょう。

OpenAPI仕様のYAMLファイルからDartコードを自動生成する際に、タグに日本語は使わない方が良いという話

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

エキサイトホールディングス Advent Calendar 2023の22日目を担当いたします。

OpenAPI仕様に基づいてAPIの仕様を定義したYAMLファイルから、アプリ用のDartコードを自動生成する際に、タグの指定で注意すべき点がありました。

実際に、APIとアプリ側の繋ぎ込みを行なう際に問題となったため、ブログとして残しておこうと思います。

OpenAPIとは

本ブログでは、Dartコードを自動生成する部分の説明は省きます。

以下で紹介されていますので、こちらもご覧ください。

tech.excite.co.jp

タグに日本語は使わない方が良い?

結論から述べますと、タグに日本を指定してしまったことで、問題が発生しました。
以下で、日本語を指定することで発生する現象を詳細にご紹介します。

今回はYAMLファイルからコードを自動生成することを考えます。

以下のように、APIを定義したYAMLファイルを用意します。

openapi: 3.0.1
info:
  title: OpenAPI definition
  version: v0
servers:
- url: http://localhost:8190
  description: Generated server url
paths:
  /article:
    get:
      tags:
      - 記事
      summary: 記事取得
      operationId: getArticle
      responses:
        "200":
          description: 正常に処理が終了した場合
          content:
            application/text:
              schema:
                type: string
components: {}

余談ですが、このYAMLファイルは以下のようなSpringBoot製のControllerを持つAPIから生成しました。

@RestController
@RequestMapping("article")
@Tag(name = "記事")
public class ArticleGetController {
    @GetMapping
    @Operation(summary = "記事取得")
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "正常に処理が終了した場合",
                    content = @Content(mediaType = "application/text", schema = @Schema(implementation = String.class))
            )
    })
    public String getArticle() {
        return "Article";
    }
}

YAMLファイルの11, 12行目でtagsに「記事」というタグを指定しています。

この、tagsに指定した文字列によってエンドポイントが分類されます。
また、自動生成されるDartのコードの「どのクラスに、どのエンドポイントに対応するコードが生成されるか」もこのタグを元に決定されます。

しかし、このタグの設定に上記の「記事」のような英数字以外の文字を入れてしまうと、コード生成がうまくいきません。

この挙動を見てみましょう。

同じ「記事」というタグを設定した記事投稿のエンドポイントと、「ユーザー」というタグを設定したユーザー取得のエンドポイントの定義を追加してみます。

openapi: 3.0.1
info:
  title: OpenAPI definition
  version: v0
servers:
- url: http://localhost:8190
  description: Generated server url
paths:
  /article/post:
    post:
      tags:
      - 記事
      summary: 記事投稿
      operationId: postArticle
      responses:
        "201":
          description: 正常に処理が終了した場合
          content:
            application/text:
              schema:
                type: string
  /user:
    get:
      tags:
      - ユーザー
      summary: ユーザー取得
      operationId: getUser
      responses:
        "200":
          description: 正常に処理が終了した場合
          content:
            application/text:
              schema:
                type: string
  /article:
    get:
      tags:
      - 記事
      summary: 記事取得
      operationId: getArticle
      responses:
        "200":
          description: 正常に処理が終了した場合
          content:
            application/text:
              schema:
                type: string
components: {}

これらのエンドポイントを作成した上で、swagger-ui(http://localhost:8190/swagger-ui/index.html)を確認すると以下のようになっているはずです。

指定したタグによって、「記事」と「ユーザー」で分類がなされていることがわかります。
Swaggerまでは英数字以外のタグも認識してくれるようです。

それでは、アプリ用のDartコードを自動生成してみます。

OpenAPIを使って、Spring Boot製のAPIからアプリ用のDartコードを自動生成する - エキサイト TechBlog. の内容に従って、自動生成を行ないます。

すると、generated/dart/lib/src/api配下にdefault_api.dartファイルが作成されます。

この中身を見ると、すべてのエンドポイントに対応するコードがdefault_api.dart内に生成されてしまっています。

これは、タグを全く指定せずにコードを自動生成した時と同じ結果です。

一方、記事articleユーザーuserのように英数字のみを使うようにすると、以下のように2つのファイルに分かれて生成されます。

このように、dartファイルの自動生成の段階では。英数字のタグしか認識されず、日本語のタグは無視されてしまうようです。
タグがファイル名やクラス名の一部になるという仕様になっているようなので、当然と言えば当然の挙動かもしれません。

もう一つ、以下のような現象も発生します。

「記事API」「ユーザーAPI」というタグを設定した2つのエンドポイントを作成することを考えます。

〜 略 〜

paths:
  /user:
    get:
      tags:
        - ユーザーAPI

〜 略 〜

  /article:
    get:
      tags:
        - 記事API

〜 略 〜

「記事」と「ユーザー」という2つの異なる要素を扱うエンドポイントに対応する自動生成のコードが、別々のファイルに生成されることを期待した設定です。
しかし、この場合、api_api.dartというファイルに2つのエンドポイントに対応するコードが生成されるという、予期しない動作をします。

日本語の部分(「記事」「ユーザー」)が無視され、英数字である「API」という部分だけが認識された結果、どちらも「API」というタグを指定した時と同じ結果になったと考えられます。

最も、全てのコードがdefault_api.dart内等の同じファイルに生成されたとしても、実際のコードには動作上問題ありません。
しかし、自動生成されたコードが分類されていた方が、可読性や利便性の観点で優れていると考えられます。
そのため、タグの指定には英数字以外は使わないようにするのが無難でしょう。

終わりに

今回は、OpenAPI仕様のYAMLファイルからアプリ用のDartコードを自動生成する際には、タグの指定に注意する必要があるということを紹介しました。

では、また次回。

Laravel10 + vue3の環境でInertia.jsを利用する時はresolveComponentを利用する

こんにちは。エキサイト株式会社の奥川です。 本記事ではvue+laravelの環境にInertia.jsを導入する時に発生したエラーの解決方法について紹介します。

環境

Laravel: 10.38.1

Vue: 3.3.13

発生したエラー

Inertiaのドキュメント通りに進め、app.jsに次のように記載しました。ディレクトリ構成の都合上、パスを変更しています。

inertiajs.com

import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'

createInertiaApp({
  resolve: (name) => {
    const pages = import.meta.glob('./components/**/*.vue', { eager: true })
    return pages[`./components/${name}.vue`]
  },
  setup({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) })
      .use(plugin)
      .mount(el)
  },
})

ここで、下記のようなエラーが発生しました。

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'default')

解決方法

調べてみると、laravelのドキュメントとInertiaのドキュメントに差異があることが分かりました。laravelのドキュメントのようにresolvePageComponentを利用するとエラーが発生しなくなりました。

laravel.com

import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';

createInertiaApp({
  // Inertia.jsのドキュメントの書き方
  // resolve: (name) => {
  //   const pages = import.meta.glob('./components/**/*.vue', { eager: true })
  //   return pages[`./components/${name}.vue`]
  // },

  // Laravelのドキュメントの書き方
  resolve: (name) => resolvePageComponent(
      `/resources/js/${name}.vue`,
      import.meta.glob('/resources/js/components/**/*.vue')
  ),
  setup({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) })
      .use(plugin)
      .mount(el)
  },
})



Canvaで記事サムネイルを作る方法

はじめに

こんにちは、エキサイト株式会社3年目デザイナーの山﨑です。

エキサイトホールディングス Advent Calendar 2023 23日目は、山﨑が担当させていただきます。

qiita.com

今回は、ブログなどでよく使われる記事サムネイルがどのようにして作られているのか記していきたいと思います。

完成系はこちらです。

タイトルを記入

まず最初にサブタイトルの「誰にも教えたくない」メインタイトルの「最高の旅をする方法」という文章を打っていきます。

フォントを選択し、文字の大きさや位置を調整

この2つの文章のフォントを変更します。

Canvaで和文フォントを使う際は、「Noto San JP」をオススメします。

Noto San JPは視認性が高く、ウェイト(文字の太さ)の種類が豊富なため、同じフォントで太さを変えてメリハリをつけたい時にとても便利なフォントです。

この時、メインタイトル「最高の旅をする方法」の文字サイズはサブタイトル「誰にも教えたくない」より大きく設定します。

メインタイトルの方が情報の優先順位が高いので、サブタイトルと差別化した方がより読みやすいサムネイルとなります。

記事に関連するオブジェクトを配置

今回のタイトルには「旅」というワードが入っているので、「地球」と「飛行機」のオブジェクトを配置します。

Canvaの素材で「地球」「飛行機」で検索して好きなデザインの素材を選択します。

この時のポイントは、なるべく使っている色数が少ないシンプルなデザインを選ぶことです。

サムネイル全体がまとまりのあるデザインになるため、【使用している色が3色以内】の素材を選ぶことをオススメします。

地球は大きい素材なので、半分ほど隠して配置し飛行機は地球の周りを回っているように配置してみました。

この時、無理に地球が全て映るように配置しない方がダイナミックな印象になるよう意識しています。

このように、素材が全て映るように配置するとこじんまりとした印象になってしまうことがあります。

【全体を映さなくても素材を素材と認識できるもの】であれば、多少大きく配置しても問題ないと思います。

色を選定

色はなるべく黒白を除いた3色内に抑えたいところなのですが、今回は黄色・青色・水色・黄緑の4色を使っています。

理由としては、水色は青色の明度を上げたもの(青色の配下)で、黄緑は黄色と青色の中間色なので、極端に色相が異なるものではありません。

3色以上使ってしまっても、色相・彩度・明度のどれかが一致していれば、全体の調和は保たれるのでそのポイントを意識していれば問題ないと思います。

これが、青色・ピンク・黄色・赤など、色相・彩度・明度が異なるものを使ってしまうとデザイン全体にまとまりがなくなってしまうので、ご注意ください。

あしらいを追加

最後に仕上げとして、強調したい文章に中黒を追加、サブタイトル全体を吹出しで囲ってみました。

吹き出しは素材から吹き出しを選択しても良いのですが、今回は「図形」から各丸四角形を選択し、「角丸の丸み」を100にした上で、下に三角形を追加して作っています。

完成

いかがだったでしょうか?

Canvaを使えば簡単に記事サムネイルが作れるので、ぜひ挑戦してみてください。

最後に

最後に、エキサイトではデザイナー、フロントエンジニア、バックエンドエンジニア、アプリエンジニアを絶賛募集しております!

興味があればぜひよろしくお願いいたします!🙇

www.wantedly.com

【今日から使える!】デザイナーがよく使うあしらいをまとめてみた

はじめに

こんにちは、エキサイト株式会社3年目デザイナーの山﨑です。

エキサイトホールディングス Advent Calendar 2023 25日目は、山﨑が担当させていただきます。

qiita.com

今回は、今日から簡単に使える「デザイナーがよく使うあしらい」について紹介していきたいと思います。

あしらいとは、テキストを強調するための装飾のことを指します。

この文章を強調させたいけどいい装飾が思いつかない…」と悩む時に選択肢の一つとして参考にしていただければ幸いです!

アンダーライン

強調の王道、アンダーライン。

ポイントは少し太めに、文字に掛かるように配置するとより良く見える場合が多いです。

◾️四角形◾️

ほんの少しだけ硬い印象を与えたい時によく使います。柔らかい印象を与えたい場合は丸を使う事が多いです。

"クォーテーションマーク"

強調したい文面群がある場合、クォーテーションマークを入れる事があります。デザイン性を上げるために最近よく使用しています。

\スラッシュ/

補足事項だけではなく、メインタイトルなどでスラッシュは幅広い用途で使う事ができます。点線にすると柔らかく可愛い印象になります。

吹き出し

「第一回」「本当は誰にも教えたくない」「〇〇会員限定」など、補足事項で吹き出しを使う事が多いです。

最後に

最後に、エキサイトではデザイナー、フロントエンジニア、バックエンドエンジニア、アプリエンジニアを絶賛募集しております!

興味があればぜひぜひ連絡ください!🙇

www.wantedly.com

SpringBootでの複数キャッシュサーバがある場合の切り替え

エキサイト株式会社エンジニアの佐々木です。エキサイトホールディングスのカレンダー | Advent Calendar 2023 - Qiitaの9日目を担当させていただきます。SpringBootでキャッシュ先を複数にする場合の設定をご紹介します。

前提

下記、環境で動作検証をしています。

## Java
$ java --version
openjdk 17.0.9 2023-10-17 LTS
OpenJDK Runtime Environment Corretto-17.0.9.8.1 (build 17.0.9+8-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.9.8.1 (build 17.0.9+8-LTS, mixed mode, sharing)

## Gradle
$ ./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:          17.0.9 (Amazon.com Inc. 17.0.9+8-LTS)
OS:           Mac OS X 12.5 aarch64

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

モチベーション

サービスを提供するにあたって、RedisやMemcachedなどの複数キャッシュサーバを使うモチベーションですが下記が考えられます。

  1. トラフィックの増大によりキャッシュといえど負荷やレイテンシーが気になる
  2. セッション情報とコンテンツのキャッシュは分けたい

1. トラフィックの増大によりキャッシュといえど負荷やレイテンシーが気になる を解決する際にリモートキャッシュとローカルキャッシュを併用する場合が考えられます。SpringBootでは、設定ファイル(application.yml)に設定を書くことで単体のキャッシュサーバはすぐに動作するようになるのですが、キャッシュサーバが複数になると設定をコードで書く必要があるので、それをご紹介します。

コード

キャッシュ設定は下記のみになります。

@Configuration
public class CacheConfig {

    @Bean
    @Primary  // <- デフォルトの設定を明示するためにつけています
    public CaffeineCacheManager caffeineCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        Arrays.stream(CacheType.values()).forEach(e -> {
            Cache<Object, Object> cache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(e.getTtl())).build();
            cacheManager.registerCustomCache(e.getName(), cache);
        });
        return cacheManager;
    }


    @Bean(CacheLocalType.LOCAL_CACHE_MANAGER_NAME) // キャッシュ名を明示して定義します
    public CacheManager localCacheManager() {
        ConcurrentMapCacheManager concurrentMapCacheManager = new ConcurrentMapCacheManager();
        List<String> list = Arrays.stream(CacheLocalType.values()).map(e -> e.getName()).toList();
        concurrentMapCacheManager.setCacheNames(list);
        return concurrentMapCacheManager;
    }

}



// ここから下は、キャッシュキーをタイプセーフにしたいのでenumにて定義しているだけです
/**
* 通常キャッシュ用
*/
enum CacheType {

    SECOND_5(CacheType.SECOND_10_KEY, 10), SECOND_10(CacheType.SECOND_20_KEY, 20);

    public static final String SECOND_10_KEY = "SECOND_10";
    public static final String SECOND_20_KEY = "SECOND_20";

    @Getter
    private final String name;
    @Getter
    private final long ttl;

    CacheType(String name, long ttl) {
        this.name = name;
        this.ttl = ttl;
    }
}

/**
* ローカルキャッシュ用
*/

enum CacheLocalType {
    SECOND_5(CacheLocalType.FOREVER);

    public static final String FOREVER = "FOREVER";

    public static final String LOCAL_CACHE_MANAGER_NAME = "localCacheManager";

    @Getter
    private final String name;

    CacheLocalType(String name) {
        this.name = name;
    }
}

上記のように複数キャッシュの設定はあわせても20行以内のコードになります。各キャッシュサーバの設定をメソッド内で行うだけになります。下記は、使用例になります。SpringBootでキャッシュを使う場合は、アノテーションを使用したキャッシュ指定が便利です。@Cacheableは、キャッシュがあれば使用する、なければメソッドを実行し、結果をキャッシュに保存してくれる便利なアノテーションになります。

    @Cacheable(cacheNames = CacheType.SECOND_10_KEY)    // cacheManagerの設定がない場合は、@Primaryの設定が使用されます。
    @Override
    public String useMainCache() {
        return "MainCache created at:" + LocalDateTime.now();
    }

    @Cacheable(cacheNames = CacheLocalType.FOREVER, cacheManager = CacheLocalType.LOCAL_CACHE_MANAGER_NAME)  // cacheManagerの設定がある場合は、@Beanで設定したBean名の設定が使用されます。
    @Override
    public String useLocalCache() {
        return "LocalCache created at:" + LocalDateTime.now();
    }

上記のように@Cacheable などのアノテーションで指定したCacheManagerが採用されてキャッシュされます。宣言的でコードを汚さなくて済むので我々の事業部ではよく使用します。

まとめ

サービス規模が大きくなったり、トラフィックが増えるとこのように複数のミドルウェアを使用することがよくあります。ローカルキャッシュやリモートキャッシュを適宜使い分けができるとより様々な要件に対応できるようになるかと思います。

最後に

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

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

[Java] どこで実装をするかで可読性が異なった話

はじめに

こんにちは、新卒1年目の岡崎です。エキサイトホールディングス Advent Calendar 2023の25日目を担当します。

最近、記事に対する画像URLの変換処理を実装しました。この実装をどこでするかで可読性が異なったので、備忘録として記事に残します。

環境

openjdk version "17.0.2" 2022-01-18
OpenJDK Runtime Environment Temurin-17.0.2+8 (build 17.0.2+8)
OpenJDK 64-Bit Server VM Temurin-17.0.2+8 (build 17.0.2+8, mixed mode)
.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.6)

前提

記事が持っている画像に対して、仕様変更で画像URLの変換処理が必要になったと仮定します。

改善前の実装

画像用の処理を集約したImageComponentクラスに、画像URLの変換を行う処理がありました。これをService層で呼び出して変換を行うと、以下のような実装になります。

@Service
@RequiredArgsConstructor
public class SampleService {
    private final SampleRepository sampleRepository;
    private final ImageComponent imageComponent;

    public ArticleModel getArticle(final String articleCode) {
        return this.convertImageUrl(sampleRepository.findArticle(articleCode));
    }

    /**
     * 画像URLの変換処理を行い、記事にセットする
     *
     * @param articleModel 記事
     * @return 画像URLの変換処理を行い、セットした記事
     */
    private ArticleModel convertImageUrl(ArticleModel articleModel) {
        final String imageUrl = imageComponent.convertImageUrl(
                articleModel.getImageUrl()
        );

        articleModel.setImageUrl(imageUrl);

        return articleModel;
    }
}
@Repository
@RequiredArgsConstructor
public class  SampleRepository {
    private final SampleMapper sampleMapper;
    private final ModelMapper modelMapper;

    public ArticleModel findArticle(String articleCode) {
        // DB等から記事をとってくる処理
        Article article = sampleMapper.findArticle(findArticle(articleCode));

        return modelMapper.map(article, ArticleModel.class);
    }
}
@Data
public class ArticleModel {
    private String articleCode;

    private String imageUrl;
}
public class ImageComponent {

    public String convertImageUrl(String imageUrl) {
        // ここで画像URLの変換処理を行う
    }

}

このコードには問題点があります。それは、Service層で画像URLの変換処理を行なってしまうことです。

この実装では、Modelの構造が複雑になるほど、Service層の可読性が低下します。
現在のModelは複雑ではありませんが、Modelの構造が複雑になるにつれて、Service層で行う変換処理が増えてしまいます。この結果、Service層の複雑性が高まってしまい、可読性の低下を招いてしまいます。

改善策

それでは、どうしたらいいのでしょうか。

改善策の一つは、Model内で実装をすることです。実装例を以下に示します。

@Data
public class ArticleModel {
    private String articleCode;

    private String imageUrl;

    public String getImageUrl() {
        return ImageComponent.convertImageUrl(this.imageUrl);
    }
}
@Service
@RequiredArgsConstructor
public class SampleService {
    private final SampleRepository sampleRepository;

    public ArticleModel getArticle(final String articleCode) {
        return sampleRepository.findArticle(articleCode);
    }
}

Model内で画像URLの変換処理を実装することで、Modelを作るタイミングで変換処理を実行できます。前述した実装のように、いちいちModelを分解して、セットし直す必要はなくなります。

まとめ

今回の場合、Modelに閉じ込めることができる処理を、わざわざService層でやろうとしたことが問題でした。同じような処理でも、どこに実装をするかで可読性は変わることがあります。実装をする時は愚直にやるのではなく、どこに実装をすれば最善なのかを考えて行う必要だと改めて感じました。

また、この記事は、先輩エンジニアのMさんが指摘して下さった内容で書いています。Mさん、今回もありがとうございました!精進します。

最後に

最後に、エキサイトではデザイナー、フロントエンジニア、バックエンドエンジニア、アプリエンジニアを絶賛募集しております!

興味があればぜひぜひ連絡ください!

www.wantedly.com

【2023年12月Firefox対応!】CSSの擬似クラス:has()とは?

こんにちは。

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

今回は2023年12月にFirefox(121)が対応したことにより主要ブラウザが完全対応となった、CSSの擬似クラス:has()についてその概要や使用方法についてお話したいと思います。

Firefoxもついに対応

Firefox120以前では:has()は対応していませんでしたが、今月リリースされたFirefox121ではついに対応することになり、主要ブラウザでは完全対応となりました。

2023年12月25日(メリークリスマス~)時点の各ブラウザでの対応状況は以下の通りです。

ブラウザ バージョン
Google Chrome 105以降
Microsoft Edge 105以降
Safari 15.4以降
Firefox 120以降
Opera 104以降
iOSSafari 15.4以降
AndroidGoogle Chrome 105以降
AndroidFirefox 121以降
AndroidOpera 72以降
Android WebView 105以降

なお、対応状況は今後変化する可能性があります。使用前にCan I use...などで確認してください。

そもそも:has()とは?

そもそも:has()とはどんな擬似クラスなのでしょうか。MDNでは以下のように紹介されています。

:has()CSS の疑似クラス関数で、引数として渡される相対セレクターのいずれかが、その要素から辿ってアンカーとして少なくとも一つの要素とマッチする場合に、その要素を表します。この疑似クラスは、相対セレクターリストを引数として取ることで、参照している要素に関して親要素や前の兄弟要素を選択する方法を提供します。

簡単に言い換えると、:has()は指定した要素がある場合にのみスタイルを適用できる擬似クラスです。

条件分岐については、

  • 要素の存在の有無
  • 要素の個数
  • 要素の属性の値

によって適用するスタイルを変化させることができます。

どうやって使うの?

前項でもご説明した通り、:has()を使用することにより特定の要素の有無などで条件分岐をすることにより適用するスタイルを変更することができます。

例えば、「3つ並ぶアイテム内の画像のうち、xmas.jpgという画像を含む要素のみ境界線色を赤に変えたい」としましょう。

実現したいこと

スタイルの条件分岐なしに作ると

まずはスタイルの条件分岐なしに作ってみましょう。

画像付きのアイテム(.item)を3つ並べて、それぞれ左から順にhalloween.jpgxmas.jpghalloween.jpgと画像を挿入します。

<ul>
    <li class="item">
        <img
        alt=""
        class="item__image"
        src="halloween.jpg"
        height="320"
        width="480"
        />
    </li>
    <li class="item">
        <img
        alt=""
        class="item__image"
        src="xmas.jpg"
        height="320"
        width="480"
        />
    </li>
    <li class="item">
        <img
        alt=""
        class="item__image"
        src="halloween.jpg"
        height="320"
        width="480"
        />
    </li>
</ul>

スタイルの条件分岐なしに作った場合

このままですと境界線の色は同じままです。

:has()を使って条件に合った要素のスタイルを変える

今回のお題は「xmas.jpgという画像を含む要素のみ境界線色を赤に変えたい」ですので、画像のクラス名と属性を.itemの擬似クラス:has()の引数に指定することで条件分岐をすることができます。

引数は.item__image[src='xmas.jpg']と、クラス名→属性の順に指定します。

Google Chrome 120からPure CSSでもCSS Nestingに対応しているため入れ子にしています。

.item {
    &:has(.item__image[src='xmas.jpg']){
            border: 2px solid red;
    }
}

:has()を適用させた場合

xmas.jpgの画像を含む要素のみ境界線色を変えることができました。

さいごに

今回はCSSの擬似クラス:has()についてその概要や使用方法についてご紹介しました。

エキサイトホールディングス Advent Calendar 2023では弊社のエンジニア・デザイナーによる記事が多数公開されていますので、ぜひご覧ください!

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

参考文献

第7回テクデザBeer Bashを開催しました

こんにちは。エキサイトでエンジニアをしている吉川です。

エキサイトホールディングス Advent Calendar 24日目の記事になります。

先日12/15(金)に社内交流イベントである「テクデザBeer Bash」の第7回を開催したので、そのレポートを書いていきます。

前回の様子はこちらからご覧ください! tech.excite.co.jp

テクデザBeer Bashとは

テクデザBeer Bashは、主に社内のエンジニアやデザイナー同士のコミュニケーション活性化のためのイベントです。

beer(ビール)+ bash(にぎやかなパーティー)を合わせた造語で、真面目な部分を残しつつ、社員同士がカジュアルな雰囲気で交流を行うことが目的としています。同じチーム内の人はもちろん、業務ではあまり関わることがない他部署の人たちとも繋がる場でもあります。

当日の様子

今回は、

  • 技術ブログの取り組みプレゼン
  • 2023年振り返りクイズ大会

の2つをメインコンテンツとして、終了後はフリートークの場としました。

技術ブログの取り組みプレゼン

アドベントカレンダーを始め高頻度に更新されている当テックブログですが、毎回ネタを考えるのは大変です。

エンジニアの中でも特に執筆いただいている方から、テックブログは自分自身のために書いているということ、テックブログをさらに盛り上げるために実践していることを発表していただきました。

来年はもっと多くの記事を発信できるよう、私個人としても身になる発表でした。

2023年振り返りクイズ大会

昨年も行った振り返りクイズ大会ですが、好評だったため今年も開催させていただきました。

今年1年を振り返って、インターン参加人数やGitHubのプルリクエスト数などエキサイトにまつわる数字について出題しました。 また生成AI画像のように今年話題になった技術についての出題も行いました。

クイズ大会はチーム対抗にしており、優勝チームにはケーキを進呈しました! 今年も好評だったので、年1回はこういうエンタメに寄った企画があっても良いなと感じました。

さいごに

いかがだったでしょうか。社内の雰囲気を少しでも感じていただけたら幸いです。

昨年度から始まったこの企画ですが、ようやく社内の知名度も得られ、軌道に乗ってきた感触があります。 2024年はいよいよ2桁台になる予定ですので、引き続き活気のあるイベントにしていきたいと思います!

エキサイトではデザイナー、エンジニアを絶賛募集しております! ご興味があればこちらからご連絡ください!

www.wantedly.com

IntelliJでThymeleafの補完が効くように設定する

エキサイト株式会社メディア事業部佐々木です。エキサイトHDアドベントカレンダー2023の15日目を担当させていただきます。メディア事業部では、SpringBoot/ThymeleafでMPAの開発を行っているところもあるのですが、HTML上での補完の効きが悪いというのがあります。これを設定することである程度補完が効くようになります。その設定をご紹介します。

前提

## Java
$ java --version
openjdk 17.0.9 2023-10-17 LTS
OpenJDK Runtime Environment Corretto-17.0.9.8.1 (build 17.0.9+8-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.9.8.1 (build 17.0.9+8-LTS, mixed mode, sharing)

## Gradle
$ ./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:          17.0.9 (Amazon.com Inc. 17.0.9+8-LTS)
OS:           Mac OS X 12.5 aarch64

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

Thymeleafの式構文を補完する

Thymeleafの式構文(th:textなどth:がつくもの)を補完してくれるのは、xmlns:th="http://www.thymeleaf.org"<html> タグ内に入れれば補完が効くようになります。

thの補完

ThymeleafでPOJO(Plain Old Java Object)を補完する

上記だけだと、Thymeleafの式構文を補完するのみになります。結構便利になりましたが、for-each等でメソッドや変数のアクセスに補完を使いたくなります。@ThymesVarを使用して、Thymeleaf上でサーバ側で設定したPOJOを補完できるようにします。、

シンタックスは下記のようになります。

<!--/* @thymesVar type="${POJOの型}" id="${変数名}" */-->

型は、フルパスで書く必要があります。サンプルでは下記のようにしました。

<!--/* @thymesVar type="java.util.List<jp.co.excite.adventcalendar.ThymeleafDemoController.Info>" id="infoList" */-->

このように書くとIntelliJで補完がされます。

まとめ

最近はフロントエンドの技術選定はReactやVueが多いですが、ちょっとした管理面などには、構築に手間がかからないMPAがまだまだ便利だったりします。Javaを書く時にIntelliJは大変便利なIDEです。それをテンプレートエンジンにも適用できたらもっと便利になります。@thymesVarsを使用して、少しでも書きやすくなればと思います。

最後に

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

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

JavaでのEnumの実装の仕方と、よく使用するメソッド

はじめに

こんにちは。新卒1年目の岡崎です。エキサイトホールディングス Advent Calendar 2023の21日目を担当します。

よろしければ他の記事もどうぞ!

qiita.com

最近の業務ではEnumを使って実装をしました。なので、今日は改めてJavaEnumについて紹介していきたいと思います。

環境

openjdk version "17.0.2" 2022-01-18
OpenJDK Runtime Environment Temurin-17.0.2+8 (build 17.0.2+8)
OpenJDK 64-Bit Server VM Temurin-17.0.2+8 (build 17.0.2+8, mixed mode)

Enumの実装

Enumは、「列挙型」と呼れています。

Enumの基本的な実装は以下のようになります。

public enum BookType {
    SF(1, "SF"),
    ESSAY(2, "エッセイ"),
    HISTORY(3, "歴史")
    ;

    // フィールドの定義を行う
    private final Integer id;
    private final String type;

    // コンストラクタの定義を行う
    BookType(final Integer id, final String type) {
        this.id = id;
        this.type = type;
    }
}

また、フィールド・コンストラクタの定義をせずに実装することもできます。

public enum BookType {
    SF,
    ESSAY,
    HISTORY
    ;
}

Enumで用意されているメソッド

JavaEnumではさまざまなメソッドが用意されていますが、その中でも私がよく使うメソッドを3つ紹介します。

name

name()では、Enumで定義したオブジェクトの名前を、宣言された通りに返します。

public class Main {
    public static void main(String[] args) throws Exception {
        String name = BookType.ESSAY.name();
        
        System.out.println(name);
    }
}

結果

ESSAY

values

values() では、Enumで定義したオブジェクトを全て返します。

public class Main {
    public static void main(String[] args) throws Exception {
        BookType[] list = BookType.values();
        
        Arrays.stream(list)
        .forEach(bookType -> System.out.println(bookType));
    }
}

結果

SF  

ESSAY  

HISTORY  

valueOf

valueOfでは、引数で指定した文字列から、Enumで定義したオブジェクトを取得できます。

public class Main {
    public static void main(String[] args) throws Exception {
        BookType essay = BookType.valueOf("ESSAY");
        System.out.println(essay);
    }
}

結果

ESSAY

もし、引数で指定した文字列からEnumで定義したオブジェクトを取得できなかった場合、IllegalArgumentExceptionが返ります。

public class Main {
    public static void main(String[] args) throws Exception {
        BookType essay = BookType.valueOf("AAA");
        System.out.println(essay);
    }
}

結果

Exception in thread "main" java.lang.IllegalArgumentException: No enum constant Main.BookType.AAA
    at java.base/java.lang.Enum.valueOf(Enum.java:273)
    at Main$BookType.valueOf(Main.java:9)
    at Main.main(Main.java:5)

補足: Enumを使わなかった実装と、Enumを使った実装の比較

ここまで、Enumの実装の仕方と、よく使用するメソッドを3つ紹介しました。
それでは、補足としてEnumを使わなかった実装と使った実装を比較します。

前提として、IDから本のタイプを取得するようにします。

まずは、Enumを使わなかった場合の実装例です。

public class Main {
    public static void main(String[] args) throws Exception {
        final String type = getBookType(1);
        System.out.println(type);
    }
    
    private static String getBookType(Integer id) {
        if (id.equals(1)) {
            return "SF";
        }
        
        if (id.equals(2)) {
            return "歴史";
        }
        
        if (id.equals(3)) {
            return "エッセイ";
        }
        
        return "";
    }
}

結果

SF

これを、Enumで実装すると以下のようになります。

// Enumに加える実装です

    static Map<Integer, BookType> map = Arrays.stream(BookType.values())
        .collect(
            Collectors.toMap(
                booktype -> booktype.id,
                booktype -> booktype
            )
        );

    public String getType() {
        return type;
    }
    
    public BookType getBookTypeById(Integer id) {
        return map.get(id);
    }
public class Main {
    public static void main(String[] args) throws Exception {
        BookType type = BookType.getBookTypeById(1);
        System.out.println(type.getType());
    }
}

結果

SF

このようにEnumを使うと、変数を代入する場所を限定することができたり、少ない行数で実装することができます。
これにより、可読性の向上や実装ミスを減らすことが期待できます。

最後に

今回は、JavaでのEnumの実装の仕方と、よく使用するメソッドを3つ紹介しました。Enumは上手く使うと、楽に実装をすることができます。ぜひ使ってみてください。

うちのサイトて、ウェブアクセシビリティを確保できていますか?

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

エキサイトホールディングス2023アドベントカレンダー 22日目をお届けします。

qiita.com

「法律が改正されるから、サイトのウェブアクセシビリティを気にしたほうがいい」というのを風の便りに聞きました。

私はこれまで「ウェブアクセシビリティ」という言葉を耳にしたことはある程度でしたが、具体的な内容については理解が不十分でした。

そこで調査を行いました。 以下にウェブアクセシビリティに関する知見を共有いたします。

ウェブアクセシビリティてなに?

ウェブアクセシビリティとは、利用者の属性(障がいの有無、年齢、利用環境)に関わらず、すべての人がウェブサイトを利用できることを意味しています。

ウェブアクセシビリティは、障がい者の方向けの対応だけではありません。

荷物を持っており手が塞がっている状態でもウェブサイトを利用可能にすることも含まれます。

ウェブアクセシビリティは努力義務

令和3年(2021年)に、障害者差別解消法(障害を理由とする差別の解消の推進に関する法律)が改正され、国や地方公共団体などに義務付けられている合理的配慮の提供が、民間の事業者も義務化されることになり、令和6年(2024年)4月1日に施行されます。

:

その合理的配慮を的確に行うため、環境の整備が努力義務となっており、ウェブサイトの場合ではJIS X 8341-3:2016に準拠したウェブサイトを作り、ウェブアクセシビリティを確保することがこれに当たります。

引用URL:ウェブアクセシビリティとは? 分かりやすくゼロから解説! | 政府広報オンライン

障害者差別解消法は改正されますが、ウェブアクセシビリティは以前同様に努力義務のままです。

努力義務ではありますが、提供するウェブサイトがすべての人に使ってもらえる状態が理想ですので、ウェブアクセシビリティの確保は重要なことです。

ウェブアクセシビリティを確保するには、 JIS X 8341-3:2016 に準拠したウェブサイトを制作する必要があるとのことですが、 JIS X 8341-3:2016 とはなんでしょうか?

ウェブアクセシビリティガイドライン「JIS X 8341-3:2016」とは?

「JIS X 8341-3:2016」の正式名称は、『高齢者・障害者等配慮設計指針-情報通信における機器,ソフトウェア及びサービス-第3部:ウェブコンテンツ』 と言います。

「JIS X 8341-3:2016」は、ウェブアクセシビリティの確保を目的して、ウェブサイトが満たすべきアクセシビリティの品質基準として、「レベルA」「レベルAA」「レベルAAA」という3つのレベルの達成基準が定められています。

例えば、レベルA には「達成基準 2.4.2 ページタイトル」があり、以下のことが書いてあります。

達成基準: ページタイトル: ウェブページには、主題又は目的を説明したタイトルがある。

意図:各ウェブページにその内容を示すページタイトルを付けることによって利用者がコンテンツを見つけやすくするため

事例:HTML で制作したウェブページの内容を示したタイトルが、ユーザエージェントのタイトルバーに表示されるようにマークアップする

引用URL:達成基準 2.4.2 を理解する | WCAG 2.0解説書

他にはどのような達成基準があるか知りたい場合は、レベルA、レベルAAの達成基準 早見表がおすすめです。 以下のURLを参照ください。

JIS X 8341-3:2016 達成基準 早見表(レベルA & AA)

ウェブアクセシビリティに対応したウェブサイトにするにはどうすればいいの?

「JIS X 8341-3:2016」で、ウェブアクセシビリティの達成基準が定義されているので、この達成基準を満たしたウェブサイトを用意すればいいのです。

と、言うのは簡単ですが、実際には基準を達成させるのは難しいです。 ですので、ウェブアクセシビリティを高めるプロセスとして、以下の流れが推奨されています。

  1. ウェブアクセシビリティの方針策定
  2. アクセシブルなウェブコンテンツの制作
  3. 試験の実施と結果の公開

1. ウェブアクセシビリティの方針策定

「適用範囲」「目標とする適合レベル及び対応度」を策定します。 実際に公開しているサイトを見てみると、どのようなものかイメージがつきやすいと思います。

www.ajinomoto.co.jp

www.kao.com

2. アクセシブルなウェブコンテンツの制作

ウェブアクセシビリティ方針において定めた目標を達成できるように、ウェブコンテンツを制作します。 制作すると言っても、まず達成基準を知る必要があります。それには以下の解説集が参考になります。

waic.jp

また、達成すべきことはわかっても、実装する方法はわからないよ。というときには、以下の達成方法集が参考できます。

waic.jp

3. 試験の実施と結果の公開

制作したコンテンツが、目標達成できているのか試験し、その結果を公開します。 ウェブアクセシビリティの試験のチェックツールは存在しますが、チェックツールで確認できるのは達成基準の2〜3割程度だそうです。 試験は、必ず人が目視による確認や操作が必要とされています。

詳細な試験の進め方は、以下のサイトが参考になります。

waic.jp

また、実際に公開しているサイトを見てみると、どのようなものかイメージがつきやすいと思います。

www.ajinomoto.co.jp

www.digital.go.jp

うちのサイトて、ウェブアクセシビリティ確保できていますか?

ウェブアクセシビリティや、ウェブアクセシビリティに対応したウェブサイト制作のプロセスについて知ることはできました。

試しに、弊社のサービスがウェブアクセシビリティを確保できているのか簡単にチェックしてみたいと思います。

ツールでのチェック

ウェブアクセシビリティでのチェックツールはいくつかありますが、 今回は、ChromeLighthouseを利用しました。

Lighthouseの使い方は以下のページを参考ください。

developer.chrome.com

以下が検査結果のページになります。

Ligthouseでのアクセシビリティテスト結果サムネイル

googlechrome.github.io

結果は90点で、指摘項目は以下になります。

  • 背景色と前景色のコントラスト比が不十分です。
  • リンクには識別可能な名前がありません
  • 画像要素には、冗長テキストである [alt] 属性があります。

手動でのチェック

今回は、以下の達成基準についてチェックします。

キーボード操作だけで、サービスのすべての機能にアクセスすることができるようにする

・キーボード操作時に、フォーカスインジケーター(選択中の要素を枠線等で囲んで示すこと)が表示されるようにしましょう

・キーボード操作時に、フォーカス・入力がキャンセルされたり、フォーカス・入力した瞬間に何かが勝手に動作することがないようにしましょう

引用url :https://www.digital.go.jp/assets/contents/node/basic_page/field_ref_resources/08ed88e1-d622-43cb-900b-84957ab87826/9f89625f/20230512_introduction_to_weba11y.pdf

以下の動画で、Tabキーでページを巡回しEnterキーでリンク移動操作をすることを確認できました。

Tabキーでページを巡回しEnterキーでリンク移動操作している動画
Tabキーでページを巡回しEnterキーでリンク移動操作している動画

以下の動画では、カウンセラーリストのフォーカスインジケーターが見えづらくなっていることが確認されました。

フォーカスインジケーターが見えづらくなっている動画
フォーカスインジケーターが見えづらくなっている動画

まとめ

ウェブサイトの提供者として、広く多くの人々に利用していただきたいと考えています。 そのためには、ウェブアクセシビリティの確保が非常に重要です。

今回、ウェブアクセシビリティガイドライン「JIS X 8341-3:2016」を調べ、 普段の開発においてアクセシビリティに十分な意識が行き届いていなかったことに気づきました。

知識不足も一因でしたが、目が使わずウェブサイトを見るなどの通常よりアクセスしづらい状況での利用ケースを想像できていませんでした。

ウェブアクセシビリティを向上させるには、既存のガイドラインを学びつつ、 様々なユーザーの利用ケースを想像し、実際にアクセスが容易かどうかを検証しながら開発していく必要があると感じました。

すぐにウェブアクセシビリティを達成することは難しいかもしれませんが、学びながら着実に改善を進めていきたいと考えています。

ウェブアクセシビリティについて学ぶ際に参考になるサイトとして、以下の2つをご紹介いたします。是非ご覧いただければと思います。

waic.jp

www.digital.go.jp

「サムネイル制作」で学んだこと- 第2弾-

こんにちは!デザイナーとして内定者アルバイトをしている澤田です! エキサイトホールディングス Advent Calendar 2023 シリーズ2の21日目の記事です!

qiita.com

1年ぶりにサムネイル制作についてまとめてみる

昨年夏にインターンシップでサムネイル制作を任せていただきました! 改めて読み返してみて「もしかしたら少しだけ成長できたかも...?」と思ったので、改めて同じ題材をまとめてみようと思います。 お恥ずかしいですが1年前の私の記事はこちらです...!

tech.excite.co.jp

今回は、先月に行われたエンジニア採用イベントでのサムネイル制作を通じて学んだことを共有したいと思います。 このイベントはエンジニア志望の学生が多く登録している就活サイト「LabBase」での単発のイベントになります。

まずはユーザー理解することが重要!

最初に取り組んだことは、サムネイルの対象となる学生像を明確にすることでした。採用担当の方が口にしていた言葉や就活生たちのTwitterでのつぶやきを参考にしながら、就活生と採用側のニーズを言語化しました。これにより、後の市場調査やイメージボード制作時にどのキーワードから検索をかけるべきか、またどんなデザインが参考になるかというサムネイル作りの足掛けができました。

サムネイルを通して関わる二人の人物像

イメージボードはアイデアを増やすためにも必要!

ユーザー像をクリアにしていく中で「エキサイトらしさってなんだろう?」という壁に当たりました。そこで、先輩デザイナーの方々が以前にエキサイトホールディング株式会社のサイトを作成する際に用いたキーワードを参考にしました。そのキーワードを参考にピンタレストで魅力的なサムネイルを見つけ、これらをカテゴリー別に分類しマッピングしました。また見つけてきたサムネイルがどのような要素を含んでいるのか言語化することで、いつもより多くのデザインアイデアが思いつきました。

集めたサムネイルをマッピングし要素を言語化

実際のサイズで確認しながらレイアウト調整する!

画面いっぱいに広げて制作していたため、実際のサイズで確認すると、文字が小さく読みづらいなどの問題が見えてきました。そこで、Figmaを使って実際に掲載されるサイトを再現し、文字の読みやすさや他のサムネイルと比較し魅力的に見えるかどうかを何度も確認しました。 またフィードバックいただく際もこのFigmaリンクを共有することで、相手の方にも実際のイメージを共有しながらアドバイスをいただくことができました。

確認前と最終提案のデザイン比較

最後に

執筆後に改めて1年前の自分と比較してみると、昨年にはなかった視点で制作できている...!と思いました。このような学びの多い環境にいられることにありがたい...と思うと同時にさらなる成長を目指していこうと思いました。最後まで読んでいただき、ありがとうございました。

GitHub Actionsのworkflow_callイベントを使用してワークフローファイルを分割する

はじめに

エキサイト株式会社 バックエンドエンジニアの山縣(@zsp2088dev)です。

エキサイトホールディングス Advent Calendar 2023の6日目を担当します。

私の担当するエキサイトブログでは、アプリケーションのテスト、ビルド、デプロイにGitHub Actionsを使用しています。 GitHub Actionsを使用するにあたり、ワークフローファイルを.github/workflowsに定義していますが、 初回に定義したワークフローファイルのメンテナスが中々できていませんでした。 このような中で、GitHub Actionsでワークフローを再利用できることを知り、これを適用できそうだと判断し、実際にワークフローファイルを分割することにしました。

本記事では、workflow_callイベントを使用したワークフローの分割とその際にハマった箇所、今後取り組みたいことについて紹介します。

概要

ワークフローファイルを分割する前のファイル一覧です。

❯ tree .github/workflows 
.github/workflows
├── deploy_production.yml
├── deploy_staging.yml
└── deploy_test.yml

それぞれのワークフローファイルで、以下の処理を定義しています。 このように、共通の処理が多く存在し、コピペでワークフローファイルを作成することが常態化していました。

テスト環境

  1. コンテナをビルド
  2. Amazon ECRにビルドしたイメージをプッシュ
  3. Amazon ECSにデプロイ

ステージング環境/本番環境

  1. 静的ファイル(CSS/JavaScript)をAmazon S3にアップロード
  2. コンテナをビルド
  3. Amazon ECRにビルドしたイメージをプッシュ
  4. Amazon ECSにデプロイ

ワークフローファイルの分割

GitHub Actionsでワークフローを再利用できることを知り、早速ワークフローファイルを分割することにしました。 ワークフローを再利用するには、workflow_callイベントを使用します。 これにより、別のワークフローからワークフローを呼び出すことができるようになります。

docs.github.com

以下に分割後のワークフローファイルの一例を示します。

今回のケースでは、workflow_dispatchイベントからworkflow_callイベントを呼び出しており、静的ファイルのアップロードとコンテナデプロイの処理を、それぞれworkflow_callイベントに切り出しました。 これにより、それぞれの処理が一箇所にまとまり、タスクの追加や修正の手間を低減することができるようになります。

静的ファイルのアップロード (upload_to_s3.yml)

on:
  workflow_call:
    inputs:
      aws-s3-path:
        type: string
        required: true
    secrets:
      sample-secret-value:
        required: true

jobs:
  upload-to-s3:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
       # 以下で、静的ファイルのアップロード処理

コンテナデプロイの処理 (deploy_to_ecs.yml)

on:
  workflow_call:
    inputs:
      image-name:
        type: string
        required: true
      cluster-name:
        type: string
        required: true
      service-name:
        type: string
        required: true
    secrets:
      sample-secret-value:
        required: true

jobs:
  deploy-to-ecs:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      # 以下で、コンテナビルド→コンテナデプロイの処理

テスト環境のデプロイ (deploy_test.yml)

name: 'deploy test'
on:
  workflow_dispatch:

jobs:
  skip-upload:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Skip upload"

  deploy:
    needs: skip-upload
    uses: ./.github/workflows/deploy_to_ecs.yml
    with:
      image-name: sample-test
      cluster-name: sample-test-cluster
      service-name: sample-test-service
    secrets:
      sample-secret-value: ${{ secrets.SAMPLE_SECRET_VALUE_TEST }}

ステージング環境のデプロイ (deploy_staging.yml)

name: 'deploy staging'
on:
  workflow_dispatch:

jobs:
  upload:
    uses: ./.github/workflows/upload_to_s3.yml
    with:
      aws-s3-path: s3://sample/staging/path
    secrets:
      sample-secret-value: ${{ secrets.SAMPLE_SECRET_VALUE_STAGING }}

  deploy:
    needs: upload
    uses: ./.github/workflows/deploy_to_ecs.yml
    with:
      image-name: sample-staging
      cluster-name: sample-stating-cluster
      service-name: sample-staging-service
    secrets:
      sample-secret-value: ${{ secrets.SAMPLE_SECRET_VALUE_STAGING }}

本番環境のデプロイ (deploy_production.yml)

name: 'deploy production'
on:
  workflow_dispatch:

jobs:
  upload:
    uses: ./.github/workflows/upload_to_s3.yml
    with:
      aws-s3-path: s3://sample/production/path
    secrets:
      sample-secret-value: ${{ secrets.SAMPLE_SECRET_VALUE_PRODUCTION }}

  deploy:
    needs: upload
    uses: ./.github/workflows/deploy_to_ecs.yml
    with:
      image-name: sample-production
      cluster-name: sample-production-cluster
      service-name: sample-production-service
    secrets:
      sample-secret-value: ${{ secrets.SAMPLE_SECRET_VALUE_PRODUCTION }}

ハマった箇所

workflow_callイベントを使用するにあたり、ハマった箇所について2つ紹介します。

stepsでワークフローファイルを呼び出すとエラーになる

再利用可能なワークフローを呼び出すときに、steps内で呼び出していたところエラーになりました。 jobs内で呼び出さないといけないようです。

以下の通り、公式ドキュメントの制約事項にも記述がありました。

Reusable workflows are called directly within a job, and not from within a job step. You cannot, therefore, use GITHUB_ENV to pass values to job steps in the caller workflow.

1つだけ別のワークフローを呼び出すとエラーになる

テスト環境のデプロイで使用するワークフローには、skip-uploadジョブを定義しています。 これは、以下のエラー文の通り、workflow_callイベントのみを使用したジョブを定義することができないため使用しています。

The workflow must contain at least one job with no dependencies.

今後取り組みたいこと

ワークフローファイルで、workflow_callイベントを使用したワークフローファイルの分割以外にも取り組みたいことについて、以下にまとめます。

Environments secretsの活用

GitHub Actionsでは、Environments欄より環境を設定できます。 さらに、環境ごとに環境変数とシークレット変数を定義できます。

現状では、リポジトリ単位でシークレット変数を定義しており、変数名の末尾に_TEST_PRODUCTIONといった環境名を定義しています。 Environments secretsを活用して、それぞれの環境ごとにシークレット変数を用意したいと考えています。

docs.github.com

on.workflow_dispatch.inputsの活用

workflow_dispatchイベントを使用して手動実行するときに、inputsを使用することで、セレクトボックスを配置できます。 例えば、以下のように環境名を選択肢に入れることで、その値をワークフローファイル内で使用することができます。 また、上記のEnvironments secretsと併用することで、より使いやすいワークフローファイルを定義できそうです。

on:
  workflow_dispatch:
    inputs:
      ENV:
        description: 環境を指定します
        default: test
        required: true
        type: choice
        options:
          - test
          - staging
          - production

docs.github.com

おわりに

本記事では、workflow_callイベントを使用したワークフローの分割とその際にハマった箇所、今後取り組みたいことについて紹介しました。 エキサイトブログでは、既存機能の改修や新機能の開発などに取り組みやすい環境を作るために、CI/CDの整備にも力を入れていきます。

採用アナウンス

エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しています。 また、長期インターンも歓迎しています。

カジュアル面談からもOKです。少しでもご興味がございましたら、お気軽にご連絡頂ければ幸いです。

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

デザイナーが今年買った本を振り返る

こんにちは!SaaS・DX事業部デザイナーの鍜治本です!
エキサイトホールディングス Advent Calendar 2023 シリーズ2の20日目の記事です!

qiita.com

新卒で入社してから早三年。仕事も慣れた部分や生活環境が変わったこともあり、例年に比べて本を読む機会が多くありました。
ペースが遅く1冊を読みきれないことも多々あるので、しっかりとした紹介はできませんが、購入した本をいくつかご紹介します。

今年購入した本一覧

購入した本のうち、仕事柄関連していそうな本をリストアップしてみました。リンクも記載したので、もし興味がある本があればぜひ読んでみてください🙆

2023年に購入した本たち

読み終えた本・読みきれなかった本

読み終えて二週三週と繰り返し読んでいる本もあれば、冒頭に記載した通り、読みきれなかった本も入っています。
詳しい内容までは触れませんが、読み終えた本とそうでなかった本の特徴に触れ、それぞれ一言程度でご紹介します。

読み終えた本

こちらの4冊は、読み終えたり読み返している本です。

左から、「銀行とデザイン」「ちいさくはじめるデザインシステム」「決算書が読めるようになる!」「会計の地図」

左の2冊はデザインに関連しており、実務レベルの事柄や事例を紹介されています。「銀行とデザイン」は、大手金融業界に変革をもたらしたデザインと、デザインを浸透させた過程・課題解決についてふんだんに盛り込まれています。業界は違えど、デザイナーがプロダクト・サービスに対して何ができるか?を改めて認識できた一冊です。
「ちいさくはじめるデザインシステム」は、デザインシステムについて具体的な事柄から抽象的な事柄まで広く描かれており、自分が現在進行形で制作しているUIの参考にもしました。実務と重ねて考えられ、読み進める上でも納得感を持ちながら読めた2冊です。

一方、右の2冊は会計や会社の数値にまつわる書籍です。担当しているKUROTENは大きな括りで言えば会計分野、お金周りの知識を増やすべく読み始めました。
馴染みのない用語や実体験のない仕組みにすぐには理解できない部分もあり、前の2冊と比べても読むのに時間を要した覚えがあります。ただ、書籍自体はかなり易しく書かれていたこともあり、周回して少しずつ知識化しています。

読みきれなかった本

反対に、読んでみたものの途中で挫折した本や、完読できなかった本はこちらの3冊。

左から、「Googleアナリティクス4」「デジタル・ブランディング」「思考技術」
左の「Googleアナリティクス4」は、GA4の勉強のために購入したものの、基礎知識がない状態で読むには全くわからずだったため挫折しました。UXをより良くするべく分析方法を学ぶ為だったのですが、まずはGA4自体を触りながら慣れていった方が良いと判断し、現在は積読しています。
中央の「デジタル・ブランディング」は、ブランド構築におけるメソッドやヒントを得るべく読み始めました。書籍の内容は、事例紹介や著書の経験談などで少し冗長だったため、すぐ読む温度感ではないとし、優先度を下げて保留しています。
右の「思考技術」については、考え方・アウトプットの仕方が言語化されており分かりやすいものの、ワークアウト的に進めた方が良い構成だったため積読中です。

業務と関係なく今年買った本

ここからは業務・デザインが関係ない今年購入した本です。おまけ程度でご紹介します。

一目惚れで買った本

左から、「イラストで見るゴーストの歴史」「47㎡、2人暮らし」
イラストで見るゴーストの歴史」はSNSで見かけ、イラストのかわいらしさに一目惚れして購入。世界各国伝承されたゴーストたちや、それらにまつわる事例など、見た目とは裏腹にかなり濃密にまとめられた書籍です。
47㎡、2人暮らし」もSNSで見かけて衝動買いした本で、部屋を作る上で意識すべきポイントを簡単なルールにしてまとめられています。生活環境が変わったので、年末の大掃除で取り入れておしゃれ部屋を目指したい所存です。

完全に趣味で買った本

左から、「未踏の蒼穹」「ガニメデの優しい巨人」「巨人たちの星」「内なる宇宙 上下」
全てSF小説です。学生時代に読んだ「星を継ぐもの」の新版が出るニュースに触発され、ジェイムズ・P・ホーガンの本を一気買い。とんでもなく面白いのですが、私の語彙では説明しきれないので、興味を持った方はぜひ読んでみてください。

おわりに

蛇足が長くなってしまいましたが、デザイナーとして業務で活かせるインプットとして本を読んできました。インプットとして読書を選んでいるのは、知識として蓄積できるもの・今はまだ自分にふさわしくないもの、これらを実際に読まねば分からないワクワク感が好きなのだと感じています。
ちょうど大きい本棚を購入したので、知識を蓄えられるようにたくさんインプット・アウトプットして、本棚をぎゅうぎゅうに埋めたいです。

最後までご覧いただきありがとうございました🙇