Excite就業型インターンでのフレームワーク移行タスク

2024年8月5日からおよそ一ヶ月間に渡ってエキサイト株式会社のLife&Wellness事業部で就業型インターンをさせていただいたかわいです。インターンの参加理由からこのインターンで得られたことを書いていきます。

誰向け?

インターンを探している、または参加しようか迷っている人向けです。私はインターン参加にあたりいくつか狙いを持って参加し、その狙いがどうだったかについて学びで書いています。何ができたかについても触れているので、私と同じような課題感を持っている人がインターンに参加するきっかけになれば、と思っています。

自己紹介

普段は大学院でセキュリティ関連の研究を行っています。研究とは別に個人の趣味でTypeScriptやGoを使ったWebアプリの開発を2年ほど行っています。これまで技術系のインターンやアルバイトに参加したことがなく、開発経験が足りていないと感じていました。経験不足を埋めるために、個人ではできないチーム開発の経験を積むこと、実運用されているコードがどのようなものかを知ること、が可能であるインターンに参加することを考えました。

そのような中でエキサイト株式会社のLife&Wellness事業部にインターン生として関わらせていただきました。リポジトリパターンやユースケースなど、これまで気にしてこなかった変更の容易さや保守性の点で大いに助けていただきました。

インターンでやったこと

Life&Wellness事業部は主に占いサービスの運営を行っています。その中で私が関わったタスクは「excite電話占い」のPHPフレームワークをBEARからLaravelへと移行するタスクです。同じタイミングでインターンに参加していた方とメンティー二人、メンター二人体制で進めていきました。

占い特集ページの移植が担当範囲でしたが、私にPHPの経験がなかったことから、ロジックを含まないページの表示のみを行うことから割り振っていただきました。その後、データの処理のロジックを含むページの移植に取り組みました。2つ目のタスクで移植元と処理のフローが変わったのでサンプルとして一部のクラスを用意してくださりました。開発をしやすくするためのCI/CDツールも豊富で、レビューも丁寧に行ってくださいました。何より、何故変更するのかの理由を明確に説明してくださりとても参考になりました。

学び

2種類の経験を積むことを目的にインターンに参加しました。それぞれどんな結果が得られたのかまとめました。

チーム開発

個人開発と最も異なる点として「明確にタスクが割り振られ期限が存在する」という点がありました。そのため「どこに時間を掛ければいいのか」に気を使いました。そのため、どこに時間を掛ければいいのか、という点には気を使いました。とはいえ、普段あまり意識していなかった時間の割り振りがすぐにできるとは考えていなかったので、自分で抱え込みすぎないことを意識していました。気をつけていたのは自分ができていない、または理解していないことへの対処方法です。自分が詰まっていることが調べればわかることなのか、それともプロジェクト固有で聞かなければわからないことなのかの判別をつけることが出来ないため、時間制限を設けていました。決まった時間調べ、それでも成果が得られなかったときにメンターに質問していました。どの程度時間をかけていいのかは初め自分で決めてしまいましたが、途中でメンターに聞いた方が良い事に気づきました。気づいた後で、ミーティングの際にどれだけ時間をかけて良いのか確認することができたので共通認識を作ることができました。自分の学びと進捗のバランスについてはある程度うまくいっていたと思います。

逆にもっと別のやり方があったかなと思ったことはメンターへの質問の仕方です。わからないところに対して何がわからないのかを把握して、調べたことや取り組んだことを説明することは意識していましたが、あまりうまくいきませんでした。ロジックを含むページの移植でデータ取得があったのですが、初めてユースケースに触れたので、全てがわからないことでした。そのため、調べた事と理解したことの区別がうまくできませんでした。実装のサンプルと睨めっこしながら、まだ整理できていないことをメンターに聞き続けることになってしまいました。わからないことがひとつ解消されたら、焦らず自分でまた次のエラーに取り組めば良かったのですが、大量のわからない事に押し流されてしまいました。今回の経験でわからない事を一度に全て解消しようとすることはかなり無謀だと分かりました。わからないことを整理するために問題の構造化をできるようになる、という課題が見つかりました。これは個人開発でもできることなので、意識して取り組んでいくつもりです。

コーディング

今回のインターンでの一番の学びは変更容易性やテストしやすい書き方です。

あるページの実装において、コンフィグからデータを読み込んでいるところがありました。このプロジェクトでは、レイヤードアーキテクチャのような構成になっています。そのため、ロジックの分離がリポジトリーやユースケースを用いることで行われています。私が取り組んだタスクでは次のような処理の流れになっていました。

  1. コンフィグからリポジトリーがデータの読み込みを行う
  2. ユースケースリポジトリーが取ってきたデータを整形し、コントローラーに渡す
  3. コントローラーはビューモデルを作り、bladeファイル(Laravelのテンプレートファイル)に引き渡しページビューになる

リポジトリーの実装として初めに私が書いたのは次のようなコードになります。

<?php

final readonly class ResultRepository implements RepositoryInterface
{
    public function __construct(ResultKey $resultKey)
    {
        $resultList = $this->getResultList()
        return $resultList[$resultKey]
    }

    public function get(): Result
    {
        return $this->result;
    }

    /**
     * @return array<string, Result> string is resultKey
     */
    private function resultList(): array
    {
        $result = include './resultList.php';
        return $result;
    }
}

問題点

  • includeで直接読み込みを行なっているのでテストしにくい

相対パスで指定先から読み込みをして、指定されたキーの有無で結果が変わります。テストを書いた方が望ましいコードですが、テストのたびに毎回ファイルアクセスをしなければなりません。並列実行できなくなってしまう上にコンフィグに正しく結果が入っている保証さえありません。

  • get()で直接result | nullを返している

たまたま返却値が1種類のデータのみで済んでいますが、複数のデータを返却しなければならなくなった時に大きな変更が必要になります。

解決策

  • ファイル読み込み

Laravelのヘルパー関数でファイル読み込みを行いました。この変更によって、相対パスを使わなくてよくなった上に、テストではモックすることでファイルアクセスをなくせました。

  • get()の返り値

値オブジェクトResultOutputを作り、resultOutputからのデータ取り出しはユースケースに任せます。ユースケースgetResult(), getKey()メソッドなど、取得データの処理を行ってもらいました。

これらの変更をコードに反映させると次のようになります。

<?php

use Illuminate\Contracts\Config\Repository as ConfigContract;

final readonly class ResultRepository implements ResultRepositoryInterface
{
    public function __construct(private ConfigContract $config)
    {
    }

    public function get(ResultKey $resultKey): ResultRepositoryGetOutput
    {
        $resultList = $this->getResultList();

        return new ResultRepositoryGetOutput($resultList[$resultKey->toString()] ?? null);
    }

    /**
     * @return array<string, Result> string is ResultKey
     */
    private function resultList(): array
    {
        $resultMaster = $this->config->get('Feature.resultMaster');
        $result = [];
        foreach ($resultMaster as $key => $value) {
            $result[$key] = new result(
               ...
            );
        }

        return $result;
    }
}

合わせてファイル読み込みのテストは次のようにモックできます。

<?php
$configMock = Mockery::mock(ConfigContract::class);
$configMock->shouldReceive('get')
        ->andReturn([...]);

これで問題になっていたテストしにくいファイル読み込みの処理をなくすこと、リポジトリーとユースケースの責任の分割が行えました。よりテストしやすく、変更に強いコードを書くことが出来ました。

最後に

今回のインターンで関わってくださった皆様に、この場を借りてお礼申し上げます。インターン期間中はメンターの方はもちろん、上長や話したいと希望した人との面談まで行ってくださいました。どなたも私の質問や説明に対して答えを返すだけでなく、理由や他の案も考慮に入れて応えてくださいました。就活やインターンの短い期間に限らず、社会に出た後のキャリアについてもとても参考になりました。

メンターやLife&Wellness事業部の方々のサポートによりインターンに参加した2つの目的は十二分に達成することが出来ました。目的を持ってこれができるようになろう、と参加するとそれ以上のものができるようになり、新しく課題も見つかります。エキサイトの就業型インターンは経験を積み、できることを増やしたい人にとてもおすすめです。ぜひ、申し込みましょう。

就業型インターンシップBooost!!!に参加して学んだこと

はじめに

こんにちは!エキサイト株式会社で1ヶ月の期間、就業型インターンシップ「Booost!!!」に参加させていただいた山根です。今回はインターンシップで取り組んだことや学んだことについてご紹介していきます。

自己紹介

私は現在情報系の大学に通っている学部3年の学生です。個人やハッカソンでwebアプリケーションを開発した経験はありますが、実務での開発経験はなかったため、夏休みの期間を利用して様々なインターンシップに参加をして経験を積みたいと考えていました。

その中で縁あってエキサイト株式会社のLife&Wellness事業部にインターン生として開発に携わらせていただきました。

インターンシップでの業務内容

Life&Wellness事業部でも様々なサービスがある中、エキサイト電話占いで使われているPHPフレームワークであるBEAR.Saturdayを最近主流のLaravelへ移行する業務に取り組み、その中でも無料占いページの移植タスクを行いました。

私はPHPでの開発経験がなかったため、初めはロジックを含まないタスクから割り振ってもらいました。2つのページの移植が終了したら、ファイルベースでデータの処理を行うタスクに取り組みました。このプロジェクトではレイヤードアーキテクチャが採用されており、リポジトリユースケースでロジックの分離を行っています。移植元とは構成が異なるため、メンターの方にサンプルを用意していただき、ディレクトリ構成についても詳しく説明してくださいました。また、PRのレビューも丁寧に行なってくださり、どこが悪いかだけでなく理由や改善案など提案していただけたり、良い点があれば評価もしてくださるため、高いモチベーションを保ったまま開発に取り組むことができました。

学んだこと

私は今回のインターンシップの目標として、実務での開発や会社の雰囲気について知るということを目標としていました。1ヶ月間インターンシップを通して様々なことを経験できましたが、中でもこれまでの開発でできなかったことや実務でのプロジェクトに参加したからこそ学べたと感じたことについてまとめていきます。

コーディング規則

部署内ではコーディング規則が決められており、タスクに取り掛かる前にまずこれを確認しました。特にPHPはPSRに準拠しており、理解が難しいものはありませんでした。記述方法に統一性がないと可読性が低く、変更容易性も失われる可能性が高いです。コーディング規則を決めておくことはチーム開発をする上で非常に重要だと感じました。

その点、本プロジェクトではCIが整備されており、規則に反している箇所を示してくれます。CIでの確認だけでなく、開発環境上でコマンドを叩くことで静的解析を行うこともでき、自動修正をすることまで可能です。短期間でコーディング規則を全て頭に入れることは難しいですが、これらのツールがあるおかげで、非常に開発が進めやすい環境だと感じました。

チーム開発

今回のインターンシップで最も学びが多かったのが、チーム開発になります。
ある実装において、実務でのチーム開発の考えが最も現れた部分について紹介します。

修正前

Laravelではbladeというテンプレートファイルを使用しています。今回のプロジェクトのアーキテクチャではフォーム入力から得た結果をbladeに表示するために、ViewModelでその結果を整形する必要がありました。 私は初め、bladeファイル内で変数を表示するために以下のようなコードを書いていました。

// blade
@section
    <p>{{ $vm->getResult()->getPersonality()->toString() }}</p>
@endsection

// ViewModel
<?php

final readonly class ResultViewModel
{
    public function __construct(
        private ResultIndexGetUseCaseOutput $useCaseOutput,
    ) {
    }

    public function getResult(): Result
    {
        return $this->useCaseOutput->getResult();
    }
}

このコードでも結果を表示することはできるのですが、いくつか問題点があります。

  • 変更容易性が低い

もしデータの取り出し元が変更になった場合、bladeでモデルクラスの実装をしていると修正箇所が広くなってしまいます。そのため、ViewModel内でモデルクラスの実装をまとめていた方が変更に対して強くなります。

  • bladeファイルはデザイナーも触れるため、処理を含まない方がいい

bladeファイルはユーザーの目に見える部分であるため、デザイナーが編集する可能性があります。そのため、bladeファイルに処理が存在する場合、思わぬ障害が発生しかねません。

修正後

先ほどの問題点を踏まえ、bladeではモデルクラスの実装を含まない、デザイナーもbladeに触れることがあるため処理を減らすということをポイントとし、以下のようなコードに修正しました。

// blade
@section
    <p>{{ $vm->getPersonality() }}</p>
@endsection

// ViewModel
<?php

final readonly class ResultViewModel
{
    public function __construct(
        private ResultIndexGetUseCaseOutput $useCaseOutput,
    ) {
    }

    public function getPersonality(): string
    {
        return $this->useCaseOutput->getResult()?->getPersonality()->toString() ?? '';
    }
}

このコードであればbladeでモデルクラスの実装をしないで済み、bladeファイルの変更容易性が高くなります。また、デザイナーへの配慮もできたコードとなっておりViewModelから何を取得しているかの意図が明確になります。

こういった変更容易性やデザイナーとの連携を意識した開発は実務でしか行えないことなので、非常に良い学びになりました。

コミュニケーション

エキサイト株式会社ではコミュニケーションツールとしてSlackを使用しています。インターン生はtimesチャンネルを持つことになっており、今回初めて自分のtimesチャンネルを持ちました。同じタイミングでインターン生として入社している方も頻繁にtimesへ投稿をしていたので、大変刺激を受けました。私も積極的にtimesを活用することでLife&Wellness事業部の方とコミュニケーションを取れるようになりました。

しかし、Slackやビデオツール上でメンターの方に質問をする際、私は説明が冗長になってしまったり、説明が分かりづらく相手に意図を汲み取ってもらっているということに気づきました。上長の方にこの悩みを相談したところ、結論ベースで物事を話すと相手に伝わりやすいというアドバイスを受けました。上長との面談以降、朝会やSlackでの報告はこれを意識して取り組むように心掛けました。まだまだコミュニケーションは自分の課題であるため、今回発見した課題を改善し、より自分からアクションを起こせるようにしていきたいと感じました。

まとめ

今回のインターンシップで関わってくださった皆様に、この場を借りてお礼申し上げます。実際のプロダクトの開発に携わることができ非常に貴重な経験を積むことができました。開発をするだけではなく、他の事業部のインターン生や新卒エンジニアの方との交流会であったり、人事や上長、CTOとの面談まで行うことができ、会社についても知る機会が多く非常に充実した1ヶ月となりました。

2名のメンターの方の手厚いサポートにより、様々な発見や学びのあったインターンシップとなりました。どんな質問でも回答してくださり、理由や手段など丁寧に説明してくださいました。エキサイト株式会社の就業型インターンシップ「Booost!!!」は様々な経験を積みたいという方に是非おすすめしたいインターンシップです。

Alpine.jsのx-modelで入力された値を参照する方法

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

今回は、Alpine.jsで、input要素などで入力された値を参照したい場合に有用なx-modelディレクティブをご紹介します。

x-modelとは

x-modelはAlpine.jsのディレクティブの一つで、これを使用するとinput要素に入力された値をAlpine.jsで扱えるデータとしてバインドできます。

基本的な使い方

まずは、基本的な使い方を単純な例でお示しします。

See the Pen x-modelの簡単な例 by AyumuSaito (@ayumusaito-excite) on CodePen.

<div x-data="{ inputedText: '' }">
  <input x-model="inputedText" type="text" placeholder="文字を入力" />
  <p>入力された値:<span x-text="inputedText"></span></p>
</div>
  1. x-datainputedTextを定義(空文字)
  2. input要素に付与したx-modelを使用して入力された値を格納
  3. x-textinputedTextの値を参照して文字列としてリアルタイムに出力

といった処理の流れです。

どんな使い道があるか

先の例では単純に入力された値を出力するだけでしたが、以下のようにバリデーションを視覚的に表したい場合にも使えます。(CodePenにぜひ触れてみてください)

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

<div x-data="{ inputedHandleName: '' }">
  <div>
    <label for="handlename">ハンドルネーム(4文字以上)</label>
    <input 
    x-model="inputedHandleName"
    type="text" 
    name="handlename" 
    id="handlename"
    pattern=".{4,}"
    />
  </div>
  <div x-show="inputedHandleName.length > 0 && inputedHandleName.length < 4">
      <p>ハンドルネームは4文字以上の必要があります</p>
  </div>
</div>

この例では、入力されたハンドルネームが4文字未満の場合は受け付けないバリデーションをinput要素のpattern属性を用いて設定しています。

しかし、通常はpattern属性はsubmit時に結果を返すため、ユーザーは自分が入力した値が条件にあっているかを一度送信してみないと分かりません。

入力すべき項目が多いほど、バリデーションエラーがあった場合に修正すべき項目を探す手間が増えてします。結果として、体験を損ね、フォームから離脱してしまうということも考えられます。

そこで、x-modelを使用して入力値を条件と照らし合わせて、違反がある場合にはエラー要素を表示することで、submitせずともリアルタイムでバリデーションエラー伝えられます。

ただし、この方法は、UIのためのバリデーションチェックに過ぎませんので、意図しないデータを受け取らないようにpattern属性と合わせて使用することを強く推奨します。

さいごに

今回は、Alpine.jsを使用している環境下で、input要素などで入力された値を参照したい場合に有用なx-modelディレクティブをご紹介しました。

Alpine.jsを使用している方の一助となれば幸いです。

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

輪読会から始めるドキュメント整備への取り組み

はじめに

こんにちは!エキサイト株式会社のまさきちです。 開発チームで「ユーザーの問題解決とプロダクトの成功を導く エンジニアのためのドキュメントライティング」という書籍で輪読会を行いました。 輪読会を行うことで得た学びをシェアいたします。

輪読会を始めたキッカケ

以前、開発プロジェクトのふりかえり会でドキュメント整備に次のような課題があることが判明しました。

  • フォーマットやまとめ方が統一されていない
  • 機能によってはドキュメントが存在しない
  • ドキュメントを見つけるまでに時間がかかる

tech.excite.co.jp

カイゼンに向けてのネクストアクションとして、ドキュメントの書き方や運用方法を学び実践しよう!という話になりました。 学ぶ手法として輪読会を行う事に決まりました。

実践方法

最初に輪読会の目的や進め方を話し合い、ルール決めしてから始めました。

開催頻度

開発メンバー3人で、月曜日と金曜日の週2回で各回30分程度で行い1回の輪読会に対して1章読み進める形で行いました。

進め方

事前にMiroを使って思ったことや参考になりそうな事を付箋に書き出し、輪読会は共有と意見交換メインで進めました。 ある程度読み進めた段階で、アウトプットのために実践する会も設けました。 ファシリテーターや共有する順番も会ごとに変えるなどマンネリ化しない工夫をしました。

学び

ドキュメントを作る前に誰のどのような課題を解決したいかを明確にすることが重要で、まず読み手に対しての深い理解が必要だということを再認識しました。 現状の現場のドキュメントの状態と書籍で紹介されているドキュメントの書き方や管理方法について比較することで、課題や改善点について洗い出すことができました。 ドキュメントを書いた後フィードバックを求める文化が無かったので、これからは積極的にフィードバックを貰ってカイゼンし続けようと思います。

ふりかえり

輪読会のふりかえりも行いました。

good
  • 定期的に集まってみんなで考える時間が取れたのは思考が深まった
  • 漠然とした「わからないからドキュメント欲しい」を言語化できた
  • ジャーニーマップは大変だったけど、課題感をより把握できた
  • 読みと実践を両方やったところはより理解度が進んだ
more
  • 輪読会を終えるまで3ヶ月かかってしまい、中弛みを感じた
  • Miroだけだと決定事項や学びが後から追いづらいので議事録や一覧で確認できるドキュメントが欲しい
  • 必要最低限の章だけ読めば長引かずに済んだかも

アウトプット

新規ドキュメントを書くためテンプレートを作り、記述項目を合わせることでフォーマットを統一して欲しい情報が探しやすいようにしました。 主要機能のドキュメントを輪読会メンバーで1つ作成して、カイゼン後のドキュメント記述の実例を示すことができました。 今回の取り組みを事業部の開発チームに共有して、ドキュメントの運用を見直しました。

まとめ

輪読会をして終わりではなく、ネクストアクションに繋げてアウトプットできて良かったです。 ひとりで読み進めるより、チームメンバーの意見を聞けたり認識の共有ができました。 ドキュメント運用に対しての具体的な課題が浮き彫りになり、解決方法を考えてカイゼンして成果を出す事が出来ました。

今後も開発組織のプレゼンス向上のための活動をしていきたいと思います。

【VS Code】VSCodeで削除したブランチが、GitHubに反映されていなかった【GitHub】

はじめに

【こちらはVSCodeGitHub触りたての超初心者さんに向けた記事です】

エキサイト株式会社デザイナーの山﨑と申します。

今回は、VSCodeでブランチを削除した時に、GitHubに反映されない事について調べてみました。

経緯

毎回使い終わったブランチの削除は、VSCodeから行っていました。

しかし、VSCode上でブランチを削除してもGitHubに反映されないので、GitHubでも同じブランチを消すという作業を行っていました。

原因

VSCodeで削除したのは「ローカルブランチ」で、GitHubにあるのは「リモートブランチ」のため、VSCodeで「ローカルブランチ」を削除してもGitHubにその削除が反映されることはありませんでした。

なぜ今までこのシステムを理解せずいじっていたのか…😭 これまで切ったブランチは必ずマージしており、マージ後はリモートブランチが自動で削除されるため、気づきませんでした。

解決方法

VSCode上でリモートブランチを削除する場所はないのかと探してみたのですが、どうやらVSCodeのブランチ削除画面では、仕様上ローカルブランチしか削除ができないようです。

そのため、VSCodeでブランチを削除してGithubのブランチも同時に削除したつもりになっていると注意が必要です。

GitHub上にあるリモートブランチを削除するには、以下の二つの方法があります。

①ターミナルからgit push --delete origin branch_nameを実行する(*「branch_name」に削除したいブランチの名前を入れてください。)

GitHubの「Branches」から直接削除する

最後に

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

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

www.wantedly.com

アイコンで説明するUIはアクセシビリティに気をつける

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

今回は、アイコンを用いて何かを説明しているUIはアクセシビリティに気をつけたいというお話をします。

アイコンで説明するUIとは

冒頭、「アイコンで説明するUI」とはどんなUIなのかを、具体例を用いてご説明します。

よくある例としては、以下のような比較表です。

アイコンを用いて説明しているUIの例

アイコンで利用可能/不可能を説明しています。直感的でわかりやすい見た目ですが、マークアップの仕方によってはハンディキャップのないユーザーにしか伝わらないUIになってしまいます。

ハンディキャップないユーザーにしか伝わらないマークアップ

先の例をマークアップしてみます。(前提として、アイコンにはアイコンフォントを使用しているとします)

<table>
  <thead>
    <tr>
        <th></th>
        <th>Aプラン</th>
        <th>Bプラン</th>
        <th>Cプラン</th>
    </tr>
  </thead>
  <tbody>
    ....
    <tr>
        <th>メールサポート</th>
        <td><span class="icon x-mark"></span></td>
        <td><span class="icon circle-mark"></span></td>
        <td><span class="icon circle-mark"></span></td>
    </tr>
    ....
  </thead>
</table>

見出し行にはtheadタグを用いていたりと適切なタグを用いているため、一見アクセシブルなマークアップに見えます。

どこに問題があるのか

アクセシビリティ上の問題はどこにあるのでしょうか。

それは、アイコンで説明をしているセルにあります。

<tr>
  <th>メールサポート</th>
  <td><span class="icon x-mark"></span></td>
  <td><span class="icon circle-mark"></span></td>
  <td><span class="icon circle-mark"></span></td>
 </tr>

この要素のアクセシビリティツリーは以下のようになります。

table
  row
    rowheader "メールサポート"
    cell
    cell
    cell

アイコンで説明をしているセルが空白として認識されてしまっています。

この状態では、スクリーンリーダーの読み上げがされないため、視覚にハンディキャップのあるユーザーが情報を入手できなくなってしまいます。

ハンディキャップのあるユーザーにも伝わるマークアップ

では、ハンディキャップのあるユーザーにも伝わるマークアップに修正します。

今回は、マルアイコンは「利用可能」、バツアイコンは「利用不可能」という意味がアイコンにあるため、それを明示するようにrole属性とaria-label属性を用いてマークアップします。

<tr>
  <th>メールサポート</th>
  <td><span role="img" aria-label="利用不可能" class="icon x-mark"></span></td>
  <td><span role="img" aria-label="利用不可能" class="icon circle-mark"></span></td>
 <td><span role="img" aria-label="利用不可能" class="icon circle-mark"></span></td>
</tr>

なにをしたのか

アイコンフォントを使用しているspanタグにrole属性とaria-label属性を付与しました。

まず、role属性ですが、これはタグに対して意味を持たせるために使用します。

HTMLのタグはそれぞれに意味を持っていますが、spanタグは意味を持たないタグです。

しかし、今回は「アイコン(画像)を表示する」という意味を持つため、role="img"を付与します。

次にaria-label属性ですが、これはタグにアクセシブル名を付与するために使用します。

role="img"で画像の意味を加えましたが、このままでは「画像」であることしか伝わりません。

どんな内容の画像なのか(=アクセシブル名)を示す必要があるため、マルアイコンにはaria-label="利用可能"バツアイコンにはaria-label="利用不可能"を付与します。

修正後のアクセシビリティツリーは以下のようになります。

table
  row
    rowheader "メールサポート"
    cell "利用不可能"
    cell "利用可能"
    cell "利用可能"

これで、アイコンを用いていて説明しているセルもスクリーンリーダーが読み上げられるようになりました。

さいごに

今回は、アイコンを用いて何かを説明しているUIはアクセシビリティに気をつけたいというお話をしました。

今回ご紹介した方法はWCAG達成基準 1.3.1を達成するための方法の一つとして、ウェブアクセシビリティ基盤委員会(WAIC)のページでも紹介されていますのでこちらも合わせてご覧ください。

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

展示会の備品を色々作ってみた件2-2

こんにちは!エキサイトお悩み相談室でデザイナーをしているサヅカです。

前回に引き続き、展示会出展で作成したクリエイティブについて紹介させていただきます。 tech.excite.co.jp

スタッフTシャツ作成

とにかく作っていて楽しかったです。今回は初めての出展だったので、まずは覚えてもらうことを目的にデザインしました。

イベントということもあり、お堅くなりすぎないようにロゴは今まで使ったことがない「ONAYAMI」をローマ字にして遊びも入れました。

裏はQRコードを配置してウェルカム感を追加。

Tシャツデザイン案

完成したデザイン

ついに完成しました!

やはりポイントは看板でも使用しているイラストの部分でしょうか。「Tシャツかわいいですね!」とお客さんにも言っていただけたのがとても嬉しかったです。

ちなみにTシャツは納品までに2週間かかり、展示会前々日の到着になってしまいめちゃくちゃ焦りました。印刷会社にもよりますが、発注する際は早めの入稿がおすすめです。

展示会当日の様子

Tシャツの色が思いのほか濃くて背景の備品と差はありますが、目立っていたので結果オーライです。

まとめ

デザイナーが現場で手伝うことは少ないと思うのですが、同じく出展していた他社のブースを間近で見ることができてとても勉強になりました。 どの企業様も目を引く工夫をしていて「なるほど!」の連続でした。

パンフレットを受け取って下さった方々、お話を聞いて下さった方々、また「エキサイトさんだ!」「翻訳使っていたよ〜(懐かしい)」「本当に24時間365日相談できるんですか!?(本当です)」等々とたくさん声を掛けていただきありがとうございました!

インターンシップとしてエキサイトブログのバックエンドに関わった話

こんにちは! エキサイト株式会社にて、8月の就業型インターンシップBooost!!! Excite Internship 2024に参加させて頂いたketakataと申します。 この記事では、インターンシップに参加した理由と、参加を通して得られたことを中心に書いていきます。

自己紹介

普段は大学院で修士1年として、情報科学を専攻しています。研究ではRegoという言語で書かれたコードの可視化ツールを、React/Goで開発しています。普段の開発でも、Web技術を用いて、自分や周りの人がほしいと思ったソフトウェアを書くことが多いです。一方で技術系のアルバイトやインターンの経験はまったくなく、そろそろエンジニアの一員として働く経験を積んでみたいと思うようになりました。いろんな分野に触れてみたいという思いはありつつも、まずは一番興味のあるバックエンド志望としてインターンを探すことにしました。

そのような中、エキサイト株式会社メディア事業部のバックエンドに、インターンとして携わらせていただくことになりました。初の実務経験でしたが、恵まれた環境の中で非常に多くの学びを得ることができました。

私が関わったプロダクト

エキサイトには様々な事業があります。その中でも私はメディア事業部の「エキサイトブログ」というサービスの開発に関わりました。 インターン生として関わったタスクは、主に以下の2つになります。

期間が限られていることもあり、タスクの大枠はある程度用意されていましたが、詳しい要件定義や実装の方針などはかなり任されている印象でした。 任されているとはいえ責任が重すぎることはなく、コードレビューも非常に丁寧に行われているため、大きな変更でも行いやすいと今までの開発と比べて感じました。

インターンの進め方

私が実務インターンを探していた理由の一つとして、「普段のエンジニアの業務がどんな感じなのか、自身で体験してみたい」という思いがありました。結果としては、かなり解像度の高い体験を得ることができました。というのも、環境構築が終わって以降は、かなり自由にタスクを進めさせていただいたからです。 実際に、各日の業務の流れはこのような感じです。

  • 10:00 出勤(貸与されるMacを使ったフルリモート)
  • 10:00~ 朝会
    • 進捗や相談事をチームで共有します
    • 皆が着実に自分のタスクを進めていて、いい刺激になります
    • 皆さん褒め上手なのでモチベが上がります
  • ~18:30 各自の業務
    • 随時、MTGや勉強会、休憩など
  • 18:30 業務終了

他の方を見ていても特に印象的だったのは、チームのメンバーがお互いを信頼し合っていて、困ったら気軽にTandemなどで聞ける環境であったことです。

私もインターン期間中、些細な質問など多くさせていただきましたが、毎回1聞いたら10返って来るので、毎回非常に勉強になっていました。それはプロダクトの質を保つためだけではなく、チームの一員としてこちらの成長も考えてくれているからだと感じました。特に、「今までそうやって開発してきたから」だけに留まることなく、そうすべき背景も含めて話してくださったのがとてもありがたかったです。

私の就業環境としてはフルリモートであったため、そのような環境でも気軽にヘルプを出せるかどうか心配な部分もあったのですが、こちらが緊張している中メンターの方から積極的にコミュニケーションを取ってくださったこともあり、結果としては杞憂に終わりました。

マイクロマネジメントがない分、普段の時間の使い方にも自主性が強く求められると感じました。特に私の場合、日中ずっとひとつのプロダクトを開発するということはこれまでに無かったので、タスクの進め方で迷子になってしまうこともありました。その対処として、個人的に特にうまく行った取り組みがポモドーロ・テクニックです。これによってかなり集中力を保つことができたと感じています。業務中ずっと監視されているということもないため、こういった手法を取りやすい環境も魅力的に感じました。

開発から得た学び

エキサイトブログはPVが非常に多く、また様々な機能を備えているサービスです。 そのようなサービスの開発に関わることには、大きな責任が伴います。 私が行ったタスクそのものは大きなものではないのですが、実際に実装に取り掛かってみると、予想外に気をつけるべきことが多いことに気付かされました。 その例を2つに絞って挙げます。

トラフィックに耐える設計

前述の通り、エキサイトブログには毎日たくさんのアクセスがやってきます。その数は事前に予測がつくものではなく、次の日にトラフィックが多くなるか減るかは誰にも予測がつきません。もし平均トラフィックぎりぎり想定でサーバー台数を用意していたとしたら、簡単にアクセス過多を起こしてブログがなかなか読み込まれなかったりするでしょう。

幸いにも、エキサイトブログでも利用しているAWSでは、オートスケールという機能によりこのような事態を防ぐことができます。サーバーインスタンスの数を現在のトラフィック量に応じて自動で増やしたり減らしたりしてくれるので、今ではこのような問題はあまり見られなくなりました。

しかし別の問題があります。従量制課金とはいえ、AWSにもお金がかかります。実際にAWSの課金額を見せていただいたのですが、その中でもRDS(データベース)は大きな割合を占めています。

特にこのデータベースの課金額、つまりアクセス負荷は、大元のトラフィック量だけでなくバックエンドの実装・SQLの書き方次第でも大きく変わります。

実際に、最初自分が書いてしまっていたPostgreSQLクエリ(の見本)を見てみます。

-- スキーマ
CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255),
    content TEXT,
    published_date TIMESTAMP
);
CREATE INDEX idx_published_date ON posts (published_date);

-- 2024年8月の投稿を取得
SELECT
    *
FROM
    posts
WHERE
    EXTRACT(YEAR FROM published_date) = 2024
    AND EXTRACT(MONTH FROM published_date) = 8;

このクエリには以下の問題があります。

  • WHERE句の中でカラムに関数を適用してしまっているため、インデックスが効かない。

インデックスが効かないと、行の検索に時間がかかります。データベースの課金額もだいたい処理時間に比例して高くなるので、単純に考えれば10倍遅いクエリを書いてしまったら課金額も10倍になります。

これは比較的わかりやすい例ですが、もっと複雑なクエリを書いたとき、インデックスが効かないケースを見落としてしまうかもしれません。もしくは、もしかしたら今のSQLエンジンはとても賢くて、この程度のミスは勝手に最適化して直してくれるかもしれません。

それを実際に確かめる方法が、実行計画を見ることです。実行計画は、クエリの実行に実際にどのくらい時間がかかるかを教えてくれます。先程のクエリの実行計画(実際の所要時間を含めたもの)は以下のようになります。

効率の悪いクエリの実行計画
詳細は省きますが、インデックスが用いられていないことがわかります。そしてExecution Timeの部分を見ると、実際のクエリ所要時間に64msほどかかっていることがわかります。 ここから、より効率の良い書き方でクエリを直すと、以下のようになります。

SELECT
    *
FROM
    posts
WHERE
    published_date >= '2024-08-01'
    AND published_date < '2024-09-01';

効率の良いクエリの実行計画
所要時間が4分の1ほどになっていることがわかります。こう直すだけで掛かるお金が大幅に減ります。やらない手はありませんね

私がインターンで書いたSQLクエリは、数ある中の一つに過ぎませんが、それでも悪いものを積み重ねると莫大な負債となります。あとから問題に気がつくのも、まとめて直すのもすごく大変なのは、想像がつくところです。今正しい設計を積み重ねていくことの大切さを感じました。

今回のインターンのコードレビューで実行計画を求められたことをきっかけに、 数値で計測し、その理由も理解しておくことの必要性を感じました。 また、これはトラフィック対処に限りませんが、他の機能の実装を見てみると自分の実装の問題に気がつくこともあり、コードリーディングの重要性も感じました。

確実に正しく動く設計

これまで様々なことを述べましたが、言うまでもなく、サービスが常に正しく動くことも重要です。 私が今まで行ってきたサービス開発はどれも小規模なもので、自分自身や周りの人のみがユーザーの、利用者数もデータの数も少ないものでした。しかし今回のプロダクト開発は、それらとはわけが違います。数分でもサービスが落ちたり、ましてやデータが消えるなんてことが起こったらユーザーの信頼を失ってしまいます。

今回のインターンでは、さすがに本番環境を直接壊せる権限までは与えられなかったのですが、参加4日目にして自分で書いたコードが本番環境にマージされるスピード感ということもあり、特に気を引き締めて業務を行いました。

コードレビューや自動テストなどによってもプロダクトの質はある程度担保されるものの、仕組みだけに頼らずメンバーひとりひとりがプロダクトそのものにオーナーシップをもって開発することが大事に感じました。

最後に

今回のインターンで関わってくださった皆様に、この場を借りてお礼申し上げます。私のスケジュールの都合上、就業期間を圧縮しての参加でしたが、お陰でより実務に近い時間を過ごせたと感じています。

参加理由として「エンジニアの一員として働く経験を積む」ということを挙げさせていただきましたが、メンターや他の方々が手間をかけてご指導くださったおかげで、確実にそれを上回る経験をさせていただきました。また、将来の相談といった話も聞いてくださり、これからどうしていくべきかをより深めることができました。

学生の皆さん、忖度なく成長させてくれるエキサイトの実務インターンに、ぜひ応募してみてください!!

JSを使用せずにCSSだけで「空白のセルがある場合」に限定したスタイリングをする方法

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

今回は、JSを使用せずにCSSだけで「空白のセルがある場合」に限定したスタイリングをする方法をご紹介します。

実現したいこと

冒頭、実現したい状態を整理しておきます。

「鈴木花子」さんの年齢が空白になっているテーブル

上の画像のテーブルでは、「鈴木花子」さんの行の年齢のセルが空白になっています。

このような場合、以下のように「鈴木花子」さんの行全体を塗りつぶし、空白のセルは特に濃く塗りつぶされるようなUIにしたいとします。

「鈴木花子」さんの行と空白である年齢のセルが塗りつぶされたテーブル

このような場合に、CSSの擬似クラスである:emptyと関数の:has()を使用することで、JSを使用せずにスタイリングをすることができます。

完成形

まずは完成形をお示しします。

See the Pen `data`属性と`:has()`を使用して空白のセルを持つ行のスタイルを変更する方法 by AyumuSaito (@ayumusaito-excite) on CodePen.

どうやるのか

子を持たない要素を表す:emptyと、引数に指定した条件を持つ要素に限ったスタイル適用ができる:has()を用いることで実現できます。

tr {
  &:has(td:empty) {
    background: #FFCECE; // 子要素をのない`td`タグを持つ場合に行全体の背景色を赤にする
  }
}

td: {
  &:empty {
    background: #FFA0A0; // 子要素がない場合にセルの背景色を特に濃い赤にする
  }
}

行要素であるtrタグに対して、「子要素のないtdタグを持つ場合」という条件を、:has()を用いて分岐をします。

加えて、今回は空白のセルは特に濃く塗りつぶされるようにしたいので、「子のないtdタグ」という条件を:emptyで分岐してスタイリングします。

これで完成です。

さいごに

今回は、JSを使用せずにCSSだけで空白のセルがある場合に限定したスタイリングをする方法をご紹介しました。

スタイリングの際の一助となれば幸いです。

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

【Dart】enumとsealedクラスのパターンマッチングについて

こんにちは。エキサイト株式会社でアプリエンジニアをしている岡島です。 今回はDartenumとsealedクラスについて勉強したことをまとめていこうと思います。

環境

Dart: 3.4.0

enumのパターンマッチング

基本的な使用例

enum Weather { sunny, rainy, cloudy }

String getWeatherAdvice(Weather weather) {
  return switch (weather) {
    Weather.sunny => "日焼け止めを塗ろう!",
    Weather.rainy => "傘を忘れずに持って行こう!",
    Weather.cloudy => "念の為傘を持って行こう",
  };
}

void main() {
  print(getWeatherAdvice(Weather.rainy)); // 出力:傘を忘れずに持って行こう!
}

enumは列挙型と呼ばれ、定数に意味を持たすことができ、まとめて管理することができます。

enumの網羅性チェック

enumの大きな利点は、コンパイラが網羅性チェックを行ってくれる点です。

例えば以下の例です。

enum Weather { sunny, rainy, cloudy, snowy } //snowyを追加

String getWeatherAdvice(Weather weather) {
  return switch (weather) {
    Weather.sunny => "日焼け止めを塗ろう!",
    Weather.rainy => "傘を忘れずに持って行こう!",
    Weather.cloudy => "念の為傘を持って行こう",
    // Weather.windyのケースが欠けている
  };
}

このようにWeatherで列挙した値がswitch文で抜けている場合コンパイラが検知してくれて以下のようなメッセージを出してくれます。

The type 'Weather' is not exhaustively matched by the switch cases since it doesn't match 'Weather.snowy'.

これにより、enumで扱っている値が抜けている場合や、新しい値の追加したときなどにバグを防ぐことができます。

sealedクラスのパターンマッチング

Dart 3から導入されたSealedクラスは、enumよりも柔軟にデータを扱うことができます。

基本的な使用例

sealed class Shape {}

class Circle extends Shape {
  Circle(this.radius);
  final double radius;
}

class Rectangle extends Shape {
  Rectangle(this.width, this.height);
  final double width;
  final double height;
}

class Triangle extends Shape {
  Triangle(this.base, this.height);
  final double base;
  final double height;
}

double calculateArea(Shape shape) {
  return switch (shape) {
    Circle(radius: final r) => 3.14 * r * r,
    Rectangle(width: final w, height: final h) => w * h,
    Triangle(base: final b, height: final h) => 0.5 * b * h,
  };
}

void main() {
  print(calculateArea(Circle(5))); // 出力: 78.5
  print(calculateArea(Rectangle(4, 5))); // 出力: 20.0
  print(calculateArea(Triangle(3, 4))); // 出力: 6.0
}

この例ではShapeというsealedクラスと、CircleRectangleTriangleというサブクラスを定義しています。それぞれの形の面積を計算する関数calculateAreaでは、Dartのsealedクラスのパターンマッチングを利用したswitch文を書いています。

サブクラスの構造に基づいたパターンマッチング

String describeShape(Shape shape) {
  return switch (shape) {
    Circle(radius: final r) => "A circle with radius $r",
    Rectangle(width: final w, height: final h) when w == h => "A square with side $w",
    Rectangle(width: final w, height: final h) => "A rectangle with width $w and height $h",
    Triangle(base: final b, height: final h) => "A triangle with base $b and height $h",
  };
}

このようにwhenなどの条件式を用いたswitch文も書くことができます。 この例では、Rectangleのケースで条件(when w == h)を使用して、正方形の場合を特別に扱っています。

詳しくはドキュメントの分岐についてをご参照ください。

dart.dev

sealedクラスの網羅性チェック

sealed class Shape {}
// ... 既存のクラス ...
class Pentagon extends Shape {
  Pentagon(this.side);
  final double side;
}

double calculateArea(Shape shape) {
  return switch (shape) {
    Circle(radius: final r) => 3.14 * r * r,
    Rectangle(width: final w, height: final h) => w * h,
    Triangle(base: final b, height: final h) => 0.5 * b * h,
    // Pentagonのケースが欠けている
  };
}

sealedでも、enumと同じようにコンパイラが網羅性のチェックを行ってくれます。この例では、Pentagonのケースが無いことについてコンパイラが検出してくれます。

最後に

このように、enumやsealedを利用し、パターンマッチングとswitch文を適切に使用することで、より安全でバグの少ないコードを書くことを意識していこうと思います。

展示会の備品を色々作ってみた件2-1

こんにちは!エキサイトお悩み相談室でデザイナーをしているサヅカです。 子供が2歳になり、イヤイヤ期で心が折れそうなママデザイナーです。

先日、お悩み相談室 for Biz(法人向けオンラインカウンセリングサービス)が「バックオフィスDXPO東京’25 夏」にブース出展しました。

dxpo.jp

今回は出展に向けていくつか備品・掲示物等を作成しましたので紹介させていただきます。

用意するもの

  • パンフレット(後輩が担当)
  • ブース用ポスター
  • ブースレイアウト用掲示
  • Tシャツ
  • 名刺

(`·ω·´)<任せてください!(やべ、結構ある)

一体何からやればいいのかしら…という次元だったので、とりあえずブースの広さや高さの確認から始めました。

出展マニュアルを確認しよう

※実際のマニュアルの画像をイラスト風に変えています。

装飾可能範囲がW5320…つまり、5メートル!

5メートルの印刷なんてデザインしたことがないのでビビりました。 しかし最終的に大人の事情で出展社名の両側(パラペット部分1,750x300)のみを作成することになりました(いつかチャレンジしたい)

ブース全体のレイアウトを考える

他社事例を参考にしつつイメージを膨らませていき、Illustratorにて10分の1の大きさでレイアウトしていきました。

今回かなり悩んだのは壁に貼るポスターの大きさです。A1かB1か…枚数はどのくらいにするのか。 商談用の椅子とテーブルを配置したり、男性女性のイラストを置いてどのくらいの高さに貼るのがベストか検討を重ねました。

実際の会場を見ていない状態で空間のデザインをするのはかなり難しいですね。空間デザイナーさんは必ず見てデザインしますよね(多分)

デザイン完成!

メインカラーの青はパンフレットのCMYKのカラーと統一して調整し、こんな感じでfixしました。 ポスターのサイズは最終的にA1で落ち着きました。

印刷会社探しや見積もりなどを並行して行いましたが、大型のパネル印刷を対応している印刷会社が意外となくて苦戦。 検討した結果、パネル印刷はプリオさん、その他はラクスルさんにお願いすることになりました。

仕上がり

実際の写真はこのような感じで、ほぼイメージ通りだったと思います!

展示会当日

ポスターから少し浮かせて取り付けた黄色い吹き出しは、社内印刷したものを段ボールに貼って切り取ったものです。切るのはかなり大変でしたがアクセントになりました。

ブース作りについての反省点

  • ポスターの文字はもっと大きくても良さそう
  • ポスターはA1ではなくB0でもよかったかもしれない
  • 角のブースだったのでパラペットの側面も用意するべきだった(←3Dでレイアウトすればよかった!)
  • 展示台の側面にもカラーボード貼ってもよかった

長くなってしまったので続きは2回に分けて書きたいと思います。

続きはこちら tech.excite.co.jp

【実験記】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
セマンティックバージョンについて