「第二回テクデザ総会」を開催しました!

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

2022年5月13日に、第二回目となる「テクデザ総会」を開催しました!

第二回テクデザ総会

ちなみに、第一回目の様子はこちらになります。

tech.excite.co.jp

テクデザ総会

テクデザ総会は、事業部間など、普段は接することがない距離感のエンジニア・デザイナーの間でのコミュニケーション活性化を目的として開催されています。

幸いにも前回が参加者から好評だったため、半年経って再度開催することとなりました。

今回は内容を第一回と一部変更しており、

  • 各事業部トピックス発表
  • 新卒発表
  • ブレイクアウトセッション
  • ディスカッション

という構成にしました。

各事業部トピックス発表

前回と同じく、各事業部の部長陣等に協力していただき、前回のテクデザ総会から今回までの間にあったトピックスを発表していただきました。 案外知らないこともたくさんあり、盛り上がりました。

新卒発表

前回との大きな違いの一つはここで、4月に入社した新卒エンジニアの皆さんに、技術研修で行った講義やハンズオン、研修として開発した検証端末の貸し出しツールの発表をしていただきました。 研修内容は、実際に講師やメンターとして関わっていない人はほとんど知る機会がなかったため、新卒の方の紹介も兼ねた良い時間になったと思います。

ブレイクアウトセッション

こちらは前回と同じく、Zoomのブレイクアウトルームの機能を使って参加者を四名程度のグループに分け、話し合ってもらいました。

全部で二回行い、一回目は10分程度で軽く自己紹介などをしていただき、二回目は「もっとコミュニケーションを活性化するにはどうすればよいか」という内容で20分議論していただきました。

ディスカッション

前回との大きな違いの二つ目がここで、二回目のブレイクアウトセッションである「もっとコミュニケーションを活性化するにはどうすればよいか」という議論について、全体から四チームを選出して、その代表の方々に実際にディスカッションを行ってもらいました。

各チームで出た案の内容は多種多様で、今後のコミュニケーション活性化に繋がりそうなとても良い機会になりました。

最後に

二回目となったこの総会も、ありがたいことに好評をいただきました。

もちろん単に実行するだけではなくこれを活かしていろいろな施策を行っていきたいですが、それと同時に、半年に一回のスパンで総会も続けていきたいところです。

【世界初開催!】Config Watch Party参加してきた!【中編】

こんにちは!2年目デザイナーのSaaS事業部のかじもとです!

2022年5月11日に開催されたfigma初のオフラインイベント「Config Watch Party」に同期デザイナーの山﨑さんと参加したので、そのレポートをお届けします😉

中編では、日本から登壇した4名のスピーカーのセッションについてさっくりご紹介します。

会場の雰囲気やグッズについては前編記事をご覧ください〜!

tech.excite.co.jp

①インハウスデザイナーが非デザイナーとスムーズに仕事をする秘訣

Watch Party最初のスピーカーは、楽天グループ株式会社でUXデザイナーをされている木原 朝美さんによるセッション。

スライドもFigmaのコミュニティにアップされていたのでぜひご覧ください!

www.figma.com

楽天フリマアプリのラクマを担当されているインハウスデザイナーでもあり、普段はプロジェクトのキックオフからリリース前のテストまで担当なさるそうです。

このワークフローの中で非デザイナーとのコミュニケーションがあり、これまでであれば、各フローに関わる人が個別のフォーマットでファイルを作成しているため、個別にコミュニケーションを取らざるを得ませんでした。
しかし、複数人でFigmaを運用したことで、早い段階でのファイル共有やフィードバックが可能になり、爆速コミュニケーションを取れるようになり驚いた、と体験談を語っていました🙌

さらにVariant機能を使うことで、Figma上にノンデザイナー向けのデザインツールを作成したことや、Figma実用ならではのおすすめ機能なども紹介いただき、弊社デザインでも取り入れたくなるTipsが多く素敵でした✨

プラグインによるFigmaのハックとワークフローの拡張

2人目スピーカーの谷 拓樹さんはUbie株式会社でデザインエンジニアをしており、欲しいと思ったプラグインを自ら作ってしまうほどのFigma愛😳
今回のセッションでもプラグインについて丁寧に話していただきました〜!
www.figma.com

私自身も業務でプラグインを使いますが、全く知らなかったものやおすすめのプラグインも多数ご紹介いただきました🥳

個人的に「ためになった〜!」と思ったTipsはコードの読み方。FigmaのPagesやアートボード・Frameそれぞれにもきちんと名前がつけられており、それをうまく使いこなせばハックした状態「ツールとして使うだけじゃないFigma」にもできちゃうんですね…🤭
この辺はエンジニアならでは…な視点ですね💃

Figmaのスライドにdemoの動画や、コードのサンプルなどのリンクがあるので、気になった方はぜひ見てみてください…!
(弊社エンジニアもぜひみて欲しい🥺そして教えていただきたい🥺🥺)

③開発者に愛されるFigmaのデザインの作り方

実装という視点でセッションいただいたのは、株式会社カケハシでフロントエンドとデザインを担当されている関 憲也さん。
普段はUIを作りそのままフロントまでされるとのことで、開発者目線で「デザインの意図を汲み取りやすく」「メンテナブルなデザイン」のススメを語っていただきました。

こちらもCommunityにスライドが共有されていたのでぜひご覧ください✨(日本語版も作っていただいているようです…神!)

www.figma.com

デザイナーとエンジニアの間で「意図」をうまく伝えられないと、思ってもいなかったものにつながってしまいます。私自身もFigma上でUIを作ってエンジニアに渡す工程を経験しましたが、意図が伝わらなかったり不備が多く困らせてしまった経験があります…
そういった意図の伝え漏れ・伝言ミスを防ぐためにも、意図を伝えるためのTipsをご紹介いただきました👏

特に印象に残ったのは、「デザインとコードを同期させる」こと。そもそもFigmaはコードの概念と似た部分が多く、作り方に気をつければコードのようなレイヤーを組めてしまうのです…!さらに、コードの概念に沿った作りにすることで、デザイナーの考えていた意図をエンジニアが分かる共通の言語に変換できるので、ミスを減らせるようになります。

また、エンジニア界隈では有名(?)な、負の遺産清算する「負債デー」も教えていただきました。これは事業部でも取り入れたい所存…😭

④複数ブランドのための一つのデザインシステム

Watch Party最後のスピーカーは、GMOペパボ株式会社でシニアデザイナーの福嶋 瞭さん。SUZURIやminneといった複数のブランドを、共通のデザインシステムで管理しており、そのメリット・デメリットや、具体的な設計手法についてセッションくださいました!🎉

www.figma.com

ペパボさんでは、ブランドの世界観を表現する手法としてデザインシステムを活用しており、デザインシステムのトークンを差し替えることで各ブランドを表現しつつ、共通のデザインシステムを成り立たせているそうです。

「複数ブランド」と聞き、エキサイトも複数事業・複数サービスを抱えているため、共感の頷きが止まりません…😭
かなりシステマティックなので下準備が必要ですが、こんな解決方法もあるのか…!と勉強になりました。

また、共通基盤のデザインシステムを作る上で重要なこととして「意味を先に決めること」を挙げていました。見た目としては同じボタンを、色違いでYes/Noの2種類を作った場合、色の種類が少ないブランドでコードにすると同じ意味となってしまいます。それを回避するためにも、きちんと意味を定義しておくのがコツになるそうです。

セッションを聞いてみての感想

ここまで読んでみた方はお気づきかもしれませんが、スピーカーのうち3名は技術にまつわるお話をしていました…!デザインを組み立てたりコミュニケーションを深めるツールでありながら、開発者に渡すためのバトンにもなるFigma。強すぎる…👏👏

アップデートによりさらに進化していて、これからもプロジェクトの中で大活躍してくれそうですね!私自身もコードなど分からずでしたが、これを機にちょっとづつ勉強してみようと思います👩‍💻

終わりに

初のオフラインイベントで大ボリュームだったので、少し長くなってしまいました💦
後編では、ここで書ききれなかったWatch Partyの内容をお伝えします🥰

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

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

www.wantedly.com

【グラフィックデザイン】TDC展2022に行ってきた!

こんにちは!21卒の山﨑です!

今年4月に開催された「TDC展2022」に行ってきたので、そのレポをまとめました!

TDC展って何?

佐藤卓佐藤可士和など日本を代表するデザイナーが所属する「東京タイプディレクターズクラブ(通称TDC)」が主催のグラフィックデザインの国際賞「東京TDC賞2022」の成果を見る事ができる展覧会です。

tokyotypedirectorsclub.org

TDC展の様子

TDC展はggg(ギンザ・グラフィック・ギャラリー)で開催されています!

国内1788作品、海外1856作品、あわせて3644作品の応募から11の受賞作品、77のノミネート作品を含む514作品が入選し、今年秋に出版されるデザイン年鑑に掲載されます。

ネット越しに見るよりも実物を見たほうが、印刷の細かな違いを見ることができて最高です👏

「ベースを白・メインの赤の面積を広く使って青を差し入れに入れるとポップで爽やかな感じになるな〜」とか「漢字ってこんなかっこいい使い方できるんだ」など勉強になる事が沢山ありました😊

いい作品をインプットしないと業務のアウトプット領域が広がらないので、これからも展示会には積極的に足を運びたいと思います!

終わりに

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

興味があれば連絡いただければと思います🙇‍♀️

それではまた!

www.wantedly.com

【世界初開催!】Config Watch Party参加してきた!【前編】

こんにちは!21卒デザイナーの山﨑です!

今回は世界初Figmaコミュニティイベント「Config Watch Party」に21卒デザイナー・鍜治本と参加したので、開場のレポを前編・中編・後編に分けてお伝えします!

前編では会場の様子・グッズなどをざっくりご紹介します👏

日本から登壇したスピーカーセッションを紹介した中編はこちら!

tech.excite.co.jp

カンファレンスの様子

会場は品川の「WHAT CAFE」で開催されました!

開催日は平日だったのですが、開場前から多くの人が集まりFigmaの盛り上がりを改めて実感しました…

検温・消毒の後に自分の名札を書き、係の方からFigmaグッズを受け取り入場します!

室内は椅子のみとテーブル席で分かれていて、テーブル席ではPCで作業しやすくなっております👏(テーブル席の写真を撮り忘れていました)

Configを聴きながらメモをとったりしていたので、とてもありがたいです…

Config Watch Partyの間にLT会やFigmaの中の人に質問するコーナーもあり、とても充実+学びになった時間でした!

他社のFigmaデザインガイドライン運用なども聞く事ができ、エキサイトで今後Figmaどのように運用していくかの指針において、とても参考になりました!(詳細は中編・後編にて!)

新機能発表

Figmaの新機能発表もアツすぎます👏

↓今回発表された新機能はこちら↓

  1. ダークモード
  2. 改善されたオートレイアウト
  3. コンポーネントプロパティ
  4. スポットライト
  5. FigJamの新しいウィジェット(JIRA/ASANA/Github)
  6. 可変 (Variable)フォント
  7. スプリングアニメーション
  8. 個別のストローク

個人的にAuto layoutのオブジェクト重ねる機能と「可変 (Variable)フォント」でフォントの微調整が可能になったのが嬉しいです…😭

Figmaの中の人に聞く!」コーナー

オフライン会場では「Figmaの中の人」である山下さん・川延さんに質問できるコーナーが…!

今回質問と回答をいくつかピックアップしてみました👏

【質問】今後個人プランは有料になる?

【回答】その予定はありません!

 

【質問】デザイントークンの置き換え機能は考えていますか?ブランドが複数あって同じコンポーネントを使いたい場合、プラグインを使えばできるけど、公式でできる?

【回答】考え中です!できるようになるかも…?

 

【質問】FigmaファイルのiPad対応はどうなる?

【回答】FigmaはReadのみだけど、Figjamは対応しているよ!なるべく早く頑張る予定だけど、仕組みが複雑なのでまだかかるかも…しばしお待ちください!

 

【質問】ブランチ機能って本当に必要なの?実際どのくらい使われてる?

【回答】ユーザーの意見的には多くて、会社カルチャーによりけりだけど使ってもらえてます!元々はマイクロソフトのような大きい企業からのリクエストで、デザインシステムの管理が複雑な場面で活用してもらうために搭載したよ。 ブランチ機能は、ブランチを作ったことによって自由に編集できる部分に強みがあると考えているよ

 

【質問】コンポーネントのプロパティ機能ってvariantとの使い分けはどうするの?

【回答】平行に使えるようになっていくし、両方使っていって欲しい機能だよ。 例えばボタンをコンポーネントにしたときに、variantだと何十種類も作っていたけど、アイコンの有無なんかをプロパティ機能で変えて使うイメージかな

 

【質問】Figmaのオフラインイベントはどうなりそう?

【回答】もっとやりたいしやろうと思う!今日のイベントもこんなに来てもらってるし、会社に持ち帰って議論します!

かわいいグッズや美味しそうなご飯も!

続いてはカンファレンスを彩るグッズ、ランチをご紹介します!

可愛すぎるConfigグッズ

今回「Config Watch Party」に当選した100名限定で配布されたConfigグッズはこちらです!

ピンバッチ・ステッカー・帽子など非常に豪華なラインナップでした!すごすぎる…😭

ご飯

飲み物やご飯もご用意していただきました!

スパイスチキン・エビ・ヴィーガンメニューと3種類ほどメニューがあり、多様性に配慮されたご飯でした👏

最後に

登壇者の発表、LT会など盛りだくさんなイベントでした!

詳しい内容については中編・後編でお伝えする予定です😊

↓中編はこちら↓

tech.excite.co.jp

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

興味があれば連絡いただければと思います🙇‍♀️

それではまた!

www.wantedly.com

DateTimeFormatでは、JSONで受け取った日付をうまく取得できない場合があるという話

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

Javaで日付形式の文字列を受け取る場合、 DateTimeFormat を使うことが多いと思います。 ですが実は、JSONで日付を受け取ろうとするとうまく行かない場合があります。

今回は、JSONで日付を受け取るにはどうするべきかを説明します。

JSON以外から、Javaで日付を受け取る方法

まずはGETリクエストで日付を受け取ることを考えてみます。

package sample;

import lombok.RequiredArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;

@RequiredArgsConstructor
@RestController
@RequestMapping("sample")
public class SampleController {

    @GetMapping
    public String sample(@ModelAttribute SampleRequestModel sampleRequestModel) {
        return sampleRequestModel.datetime.toString();
    }

    public record SampleRequestModel(
            @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime datetime
    ) { }
}

このコードのように DateTimeFormat を使い、

http://localhost/sample?datetime=2022-01-01 00:00:01

このアクセスをすれば、

2022-01-01T00:00:01

こんな感じのレスポンスが返ってくるため、問題なく文字列の日付を受け取れていることがわかります。

JSONから、Javaで日付を受け取る方法

一方で、例えばどこかのAPIへアクセスし、レスポンスとして受け取ったJSONに日付が混ざっていた場合を考えましょう。

package sample;

import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

import java.time.LocalDateTime;

@Data
public class SampleResponseModel {
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    LocalDateTime datetime;
}

こんなモデルで、

{
    "datetime": "2015-02-24 15:00:00"
}

このJSONを受け取ることを想定します。

先程のGETリクエストを考えると問題なさそうですが、実は実行すると

JSON parse error: Cannot deserialize value of type `java.time.LocalDateTime` from String \"2015-02-24 15:00:00\"

こんなエラーが起きてしまいます。

どうやら今回の日付のような、間にスペースが入っている日付は、JSONから受け取ろうとすると DateTimeFormat を使った場合エラーになってしまうようなのです。

ではどうするかというと、 JsonFormat を使えば問題ありません。

package sample;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.time.LocalDateTime;

@Data
public class SampleResponseModel {
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    LocalDateTime datetime;
}

これで、無事受け取れるようになります。

終わりに

受け取る形式によって、同じデータでも微妙に必要な処理が変わってくる場合があります。 注意しましょう。

【大ピンチ】FigmaのTeamを削除した時の復活方法

こんにちは。21卒デザイナーの山崎です。

今回は「FigmaのTeamを削除した時の復活方法」について書きたいと思います。

FigmaのTeamって何?

Figmaには「Team」「Project」「File」と大きく3つの階層があります。 基本Teamは各サービスごとに作られています。

Teamは重要なものなので、そのTeamのadmin権限を持っていないと削除できません。

削除方法

  1. Teamを右クリック
  2. 「Delete team」を選択
  3. 記入画面にTeam名を入力
  4. 削除

Teamsを削除した場合の復活方法

Teamの復活させ方は、Teamを削除するときの文言に記載されています。 「この操作を取り消すには、28日以内に電子メールに記載された指示に従ってください。」

Figmaからきたメールの赤枠の部分をクリックすると、削除したTeamsが復活します!

終わりに

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

興味があれば連絡いただければと思います🙇‍♀️

それではまた!

www.wantedly.com

Spring Bootにおける、おすすめRedis設定方法

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

以前こちらの記事で、Spring BootのRedisキャッシュで List.of などを使う方法について説明しました。

tech.excite.co.jp

今回はそれも含んだ、Redisキャッシュのおすすめ設定方法を紹介します。

なお実は、弊社の中尾さんが以下のブログで同様におすすめ設定方法を紹介してくれています。

tech.excite.co.jp

今回は、これに更に詳しい説明を加えたり、一部設定を変えたものとなります。

Redisキャッシュのおすすめ設定方法

結論から言うと、以下のような設定になります。

キャッシュキー一覧

package sample;

import lombok.Getter;

import java.util.concurrent.TimeUnit;

public enum CacheKeyType {
    SAMPLE_1(SAMPLE_1_KEY, TimeUnit.MINUTES.toSeconds(1)),
    SAMPLE_2(SAMPLE_2_KEY, TimeUnit.MINUTES.toSeconds(1)),
    SAMPLE_3(SAMPLE_3_KEY, TimeUnit.MINUTES.toSeconds(1)),

    @Getter
    private final String key;

    @Getter
    private final Long ttl;

    CacheKeyType(String cacheName, Long ttl) {
        this.key = cacheName;
        this.ttl = ttl;
    }

    public static final String SAMPLE_1_KEY = "sample1Key";
    public static final String SAMPLE_2_KEY = "sample2Key";
    public static final String SAMPLE_3_KEY = "sample3Key";
}

使いやすいよう、キー名とTTLをまとめてEnumで管理しています。

Redis設定

package sample;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import io.lettuce.core.ReadFrom;
import sample.CacheKeyType;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisStaticMasterReplicaConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.Arrays;
import java.util.stream.Collectors;

@Configuration
@EnableCaching
@RequiredArgsConstructor
public class RedisConfig {
    private final RedisProperties redisProperties;

    /**
     * Redis用のプロパティ
     * ここでは、application.ymlからデータをとってくるようにしています
     * @param primary プライマリエンドポイントのプロパティ
     * @param reader リーダーエンドポイントのプロパティ
     * @param database データベース番号
     */
    @ConfigurationProperties(prefix = "spring.redis")
    public record RedisProperties(Endpoint primary, Endpoint reader, Integer database) {

        /**
         * Redisエンドポイント用のプロパティ
         * @param host ホスト名
         * @param port ポート番号
         */
        public record Endpoint(String host, Integer port) { }
    }

    /**
     * カスタマイズした GenericJackson2JsonRedisSerializer を取得する
     * - List.of 等の Unmodifiable collection をキャッシュで使用可能にする
     * - LocalDateTime 等を、各プロパティでの Serializer/Deserializer の指定なしにキャッシュで使用可能にする
     *
     * <p>
     * NOTE: GenericJackson2JsonRedisSerializer.registerNullValueSerializer(objectMapper, null) について
     * disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) の設定に必要だが、
     * DefaultTyping.EVERYTHING では一部処理を変更する必要があり、現状上記設定はしないのでここでは設定しない
     * spring-data-redis が ver.2.7 以降であれば設定されているようなので、アップデートすれば設定しても問題ない
     * </p>
     *
     * @see <a href="https://github.com/spring-projects/spring-data-redis/pull/2237">Switch Jackson default mapping default from NON_FINAL to EVERYTHING</a>
     *
     * @return カスタマイズした GenericJackson2JsonRedisSerializer
     */
    private GenericJackson2JsonRedisSerializer serializer() {
        ObjectMapper objectMapper = new ObjectMapper()
                .registerModule(new JavaTimeModule()) // これを設定することで、これ以外の設定なしに日付系データをキャッシュできるようになります
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // キャッシュ時の日付データのフォーマットを見やすくします。少なくとも LocalDateTime においては、なくても機能としては問題ありません
                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) // キャッシュ側に、Javaモデルでは存在しないプロパティがある(Javaモデル側のプロパティを減らしてデプロイしたなど)場合でもエラーが起きないようにします
                .activateDefaultTyping(
                        LaissezFaireSubTypeValidator.instance,
                        ObjectMapper.DefaultTyping.EVERYTHING, // finalなモデルでもキャッシュ時に型情報がRedisに保存されるようにします。これにより、 `List.of` 等もキャッシュに使用できるようになります
                        JsonTypeInfo.As.PROPERTY
                );

        // GenericJackson2JsonRedisSerializer.registerNullValueSerializer(objectMapper, null);

        return new GenericJackson2JsonRedisSerializer(objectMapper);
    }

    /**
     * Redis接続用設定のFactory
     * Primary / Secondary の接続設定をします
     * @return Lettuce接続用設定のFactory
     */
    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
                .readFrom(ReadFrom.REPLICA_PREFERRED)
                .build();

        RedisStaticMasterReplicaConfiguration serverConfig = new RedisStaticMasterReplicaConfiguration(
                redisProperties.primary().host(),
                redisProperties.primary().port()
        );
        serverConfig.addNode(redisProperties.reader().host(), redisProperties.reader().port());
        serverConfig.setDatabase(redisProperties.database());

        return new LettuceConnectionFactory(serverConfig, clientConfig);
    }

    /**
     * キャッシュ用のReactiveRedisTemplate設定を追加
     * キャッシュのキー・バリューのシリアライズ方法を指定します
     * @param factory ConnectionFactory
     * @return キャッシュに使用するReactiveRedisTemplate
     */
    @Bean
    public ReactiveRedisTemplate<String, Object> reactiveRedisTemplate(LettuceConnectionFactory factory) {
        StringRedisSerializer keySerializer = new StringRedisSerializer();
        RedisSerializationContext.RedisSerializationContextBuilder<String, Object> builder = RedisSerializationContext
                .newSerializationContext(keySerializer);
        RedisSerializationContext<String, Object> context = builder.value(this.serializer()).build(); // バリュー側で、カスタムしたシリアライザを使用するようにします

        return new ReactiveRedisTemplate<>(factory, context);
    }

    /**
     * Redisでのキャッシュ時、カスタムタイプを使って保存できるようにBuilder設定を追加
     * 先に設定したキャッシュのキー・TTL一覧を使ってキャッシュするようにします
     */
    @Bean
    public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
        return builder -> {
            var map = Arrays.stream(CacheKeyType.values())
                    .collect(
                            Collectors.toMap(
                                    CacheKeyType::getKey,
                                    e -> RedisCacheConfiguration
                                            .defaultCacheConfig()
                                            .prefixCacheNameWith("prefix:") // profile名などを持ってくると、環境ごとにprefixを付けられます
                                            .entryTtl(Duration.ofSeconds(e.getTtl()))
                                            .disableCachingNullValues()
                                            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string()))
                                            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(this.serializer())) // バリュー側で、カスタムしたシリアライザを使用するようにします
                            )
                    );
            builder.withInitialCacheConfigurations(map);
        };
    }
}

設定です。 細かくはコメントで書いてあります。

使用方法

public class SampleImpl implements Sample {

    @Override
    @Cacheable(cacheNames = CacheKeyType.SAMPLE_1_KEY)
    public String getSample() {
        // ....
    }

使用したいメソッドに @Cacheable を付け、キャッシュ名として先に作成していたキー名を指定します。 これにより、そのキー名と抱き合わせられているTTLでキャッシュしてくれます。

終わりに

キャッシュ周りはよく使うものなので、良い感じの設定にしておきたいものです。

今の所この設定では、(キーが増えると管理が大変になること以外は)特に不便は無いので、よければ参考にしてもらえれば幸いです。

第7回定期勉強会「パネルディスカッション:サービスの運用保守」

第7回定期勉強会「パネルディスカッション:サービスの運用保守」

こんにちは、エキサイトのあはれんです。 4月の勉強会は、技術共有を目的にサービスの運用保守についてのパネルディスカッションを開催いたしました。 各部署1名ずつ代表エンジニアがパネラーとして参加していただき「サービスの運用保守」をテーマに話していただきました。

なお過去開催分は以下になりますので、よければ御覧ください。

tech.excite.co.jp

パネルディスカッションの内容

Q1. 利用しているコミュニケーションツールは?

Q1. 利用しているコミュニケーションツールは?

全社的にSlackを利用していることから、ほとんどの部署でSlackを利用していました。 一部の部署では、雑談部屋を作りやすいという理由でtandemの利用もありました。

Q2. テストコードを書いていますか?

Q2. テストコードを書いていますか?

すべての部署で、テストコードはあったほうが良いという意見ではあったのですが、 既存システムがテスト導入しづらい構造だったり、工数が取れない問題でなかなか難しいというのが現実のようです。

どの部署もテストを書くのを諦めてるわけではなく、ユニットテストの勉強会を開催したり、新規開発の際は最初からテスト導入する方向で取り組んでいるようです。

Q3. CI/CDでやっていることは?

Q3. CI/CDでやっていることは?

CD(Continuous Delivery)に関しては、ほとんどの部署で実施できていましたが、 CI(Continuous Integration)に関しては、テストの導入が進んでないため、まだのところが多かったです。

Q4. データは活用できていますか?

Q4. データは活用できていますか?

データの活用に関しては、エンジニア、プロデューサーともに活用できている部署、将来的に活用できるようにしていきたい部署など様々でした。

Google アナリティクスを利用している部署が多く、GA4への移行についての話が盛り上がりました。

5. 定期的にリファクタリングしていますか?

Q5. 定期的にリファクタリングしていますか?

すべての部署で、リファクタリングはしたほうが良いという意見ではあったのですが、 テストが無く動作確認が難しいこと、工数の問題で、なかなか進まないのが現実のようです。

人によってはリファクタリングと機能追加が混じってしまいレビューしづらくなってしまう問題があるので、リファクタリングをする際のルール化の必要性について話されました。

最後に

長年運用されているサービスが多く、理想通りにできていない現実がありましたが、 今回のような交流会を通して、各部署で切磋琢磨しながら理想に近づけるようにしたいと思いました。

今後とも様々な勉強会を開催していくのでよろしくおねがいします。

「Excite × iXIT TechCon」運営チームがBEST Project Awardを受賞しました!

こんにちは 👋

エキサイト株式会社の伊藤(🐦@motokiito2)です!

おおしげ(🐦@_ohshige)さん率いる我々TechCon運営チームは、社内の技術活性を目的に、約7ヶ月間の準備を経て、過去最大級の技術者向けの社内カンファレンス「Excite × iXIT TechCon」を開催しました。

「Excite × iXIT TechCon」の開催については、下記のエントリを御覧ください。

tech.excite.co.jp

そしてこの度、XTechグループ総会で、TechCon運営チームの活動が評価され「Best Project Award」を受賞いたしました! 🎉

受賞にあたって

長い期間取り組んできた「Excite × iXIT TechCon」に対して、カンファレンスに参加してくれた技術者だけでなく、XTechグループ全体からもスポットが当たったということで、とてもうれしく思っています。

また、今回の受賞は「有志で結成された組織」としての受賞というこで、とても大きな意味があることだと思っています。

「Best Project Award」は、例年では活躍する部署や事業チーム単位で選ばれており、有志で結成された組織が表彰されるのは初めてのことでした。

これを機に、今まで挑戦できなかった様々なことも、有志が集まり実現する機会が増えるのではないでしょうか。

今後について

今回の受賞によって、技術者の組織文化を醸成させるための取り組みがより進めやすくなったと思います。

今後も、社内カンファレンスの開催に限らず、文化醸成のための様々な活動に取り組んでいきたいと思っています。

カンファレンスのデザインを担当してくれた新卒デザイナーのかじもとさん奮闘記はこちら 👇👇

tech.excite.co.jp

「Excite × iXIT TechCon」の開催のウラ側を知りたい方、弊社に興味がある方はこちらも御覧ください 👇👇

www.wantedly.com

Spring BootのRedisキャッシュで、List.ofを使えるようにする方法

f:id:excite-takayuki-miura:20220418112217p:plain

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

Spring Boot には様々な機能が用意されており、その一つに Redis へのキャッシュ機能があります。 非常に便利なのですが、少なくとも Spring Bootver.2.6.6 では、 List.of (正確には、 List.of などによって返却されるイミュータブルコレクション)等の一部の型でキャッシュが使えないという問題があります。

今回は、 List.of を使えるようにする方法について説明していきます。

Spring BootとRedisキャッシュ

Spring Boot では、spring-data-redisというライブラリを使って、任意のデータを Redis にキャッシュすることができます。 便利なのですが、少なくとも Spring Boot ver.2.6.6 の段階では、 List.of をキャッシュすると以下のようなエラーが出てしまいます。

Could not read JSON: Unexpected token (END_ARRAY), expected VALUE_STRING: need JSON String that contains type id (for subtype of java.lang.Object)\n at [Source: (byte[])\"[]\"; line: 1, column: 2]; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (END_ARRAY), expected VALUE_STRING: need JSON String that contains type id (for subtype of java.lang.Object)\n at [Source: (byte[])\"[]\"; line: 1, column: 2]

これは、 Redis へのキャッシュ方法が原因となっています。

Redis キャッシュの流れとして、

  • キャッシュを作成する際は、Java用のデータをJSON化して Redis に保存
    • この際、Java側の型情報も一緒に保存
  • キャッシュを取得する際は、 Redis のデータをJSONからJava用のデータに変換して使用
    • この際、保存されている型情報も参照する

となっています。

List.of などの final なデータはJSON化はできるのですが、JSONからデータを戻す際に型情報の変換がうまく行かずエラーが起きてしまうようです。

List.ofをキャッシュする方法

これを解決するためには、JSONの変換周りをカスタマイズする必要があります。

Spring Boot では、 Redis キャッシュの値のシリアライザを開発者側で指定することができるのですが、それを以下のようにすれば、 List.of をキャッシュできるようになります。

    private GenericJackson2JsonRedisSerializer serializer() {
        // *1
        ObjectMapper objectMapper = new ObjectMapper()
                .activateDefaultTyping(
                        LaissezFaireSubTypeValidator.instance,
                        ObjectMapper.DefaultTyping.EVERYTHING,
                        JsonTypeInfo.As.PROPERTY
                );

        // *2
        // GenericJackson2JsonRedisSerializer.registerNullValueSerializer(objectMapper, null);

        return new GenericJackson2JsonRedisSerializer(objectMapper);
    }

少し解説していくと、

*1

基本的にはデフォルト設定と同じですが、第二引数である DefaultTyping.EVERYTHING が変わっています。 これを第二引数に入れることで、 final なものであっても型情報を保存するようになります。

*2

disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) の設定に必要ですが、 DefaultTyping.EVERYTHING で使用するには GenericJackson2JsonRedisSerializer 内の一部処理を変更する必要があります。 spring-data-redisver.2.7 以降であれば処理が変更されているようなので、アップデートすれば設定しても問題ないと思われます。

最後に

List.of は、非常に便利な機能です。 使えるのであればこれを使わない手は無いので、ぜひ今回の設定をしていただき、ガンガン List.of を使っていきましょう!

なお、spring-data-redisver.2.7 以降であればデフォルトでDefaultTyping.EVERYTHINGとなっているようなので、将来的にはこの設定は不要になるかもしれません。 現在のデフォルト設定も見てみると良いでしょう。

Spring Bootで、JDBC設定にTLSプロトコルの指定が不要になった話

f:id:excite-takayuki-miura:20220411145717p:plain

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

今回は、Spring Bootのアップデートによって、DB設定がちょっと簡単になった話をします。

Spring BootとDB設定

Spring BootでDBと接続したい場合、 application.yml ファイルに以下のように設定します。

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/sample_db
    username: sample_user
    password: sample_password
    driver-class-name: com.mysql.cj.jdbc.Driver

基本的にはこれで良いはずなのですが、実は状況次第では、これではDBと接続できないことがありました。

DB設定とTLSバージョン

この「状況」というのは、端的に言えば各種バージョンのことです。

私のときは、以下のバージョンでの開発で発生しました。

- Java 11.0.13
- Spring Boot 2.4.3

原因は、DBとの接続時のTLSバージョンです。

TLS1.2 でないと接続できないにもかかわらず、デフォルトでは TLS1.1 で接続しに行ってしまうため、接続エラーが起きていたのでした。

これを解決するためには、DBのURLに以下のパラメータをつければOKです。

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/sample_db?enabledTLSProtocols=TLSv1.2
    username: sample_user
    password: sample_password
    driver-class-name: com.mysql.cj.jdbc.Driver

明示的に指定することで TLS1.2 で接続してくれるようになり、問題がなくなります。

新バージョンでは指定が不要になった

この指定ですが、実は新しいバージョンでは指定が不要になりました!

少なくとも以下のバージョンでは、指定しなくても接続できることを確認しています。

- Java 17.0.2
- Spring Boot 2.6.6

最後に

ちょっとしたことですが、ハマると少し解決までに時間がかかるところだったので、とてもありがたいです。

新しいJava・Spring Bootを最初から使っている人にはあまり関係ないかもですが、現時点でTLSバージョン指定が必要なものを使っている人や、バージョンを上げたがまだTLSバージョン指定を残している人の参考になれば幸いです。

PHPerKaigi 2022にエキサイトのエンジニアが登壇します。

2022年4月9日(土)〜4月11日(月)の日程で開催されるPHPerKaigi 2022にて、エキサイト所属のエンジニアが登壇いたします。

登壇情報については、以下をご覧ください。

登壇情報

何でもキレイにiterationする方法を考える in PHP

2022/04/10 18:30〜 Track A LT(5分)

fortee.jp

php の基本の foreach。
しっかり正しく記述できていますか?
foreach は簡単そうに見えて柔軟すぎて、結局難しかったりします。

こんな悩みはありませんか?
・foreach の中に if 文がたくさん詰まってしまう。
・Promise を foreach で回すのが難しく感じる
 ・同時実行上限
 ・終了時上限の付与
・Generator が苦手
 ・遅延処理が書けるって聞くけどよくわからない
 ・結局 foreach で終了までを全部詰め直すから読みにくくなってしまう

それらを解決する概念が下記の3つです。
・map処理
・並列処理
・遅延処理

この概念をいい感じに foreach で説明できたらいいなって思います。

PHPerKaigi 2022について

開催日:2022年4月9日(土)〜4月11日(月)

phperkaigi.jp

それでは皆さま、PHPerKaigi 2022を一緒に楽しんでいきましょう!

2回目以降のCheckstyle実行結果を見る方法

f:id:excite-takayuki-miura:20220405130322p:plain

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

皆さんは、Checkstyleを使っていますか?

Javaのコードが指定したコード規約に即しているかをチェックしてくれるツールで、これによってコードの品質を保つことができます。

Gradleからタスクを実行したりして違反が無いかを確認できるのですが、実際に使ってみて、初見だと少しハマりそうな部分があったので、その部分について書いていこうと思います。

Checkstyleとは

Checkstyleは、Javaがコード規約に即しているかをチェックしてくれるツールです。

公式では以下のように説明されています。

Checkstyle is a development tool to help programmers write Java code that adheres to a coding standard. It automates the process of checking Java code to spare humans of this boring (but important) task. This makes it ideal for projects that want to enforce a coding standard.

人の目でわざわざチェックしていくのが面倒で、しかもプログラマチックに解決できるコード規約周りのチェックをしてくれるので、簡単にコードの品質を保つことができるようになります。

Gradleであれば、以下のように設定できます。

build.gradle

plugins {
    // ...
    id 'checkstyle'
    // ...
}

// 今回はマルチプロジェクト構成なので、 `subprojects` に細かい設定を追記
subprojects {
    apply plugin: 'checkstyle'

    checkstyle {
        configFile = rootProject.file("path/to/xxx.xml") // ここに、使用したいコード規約の設定をXML形式で作る
        toolVersion = "10.0"
        sourceSets = [project.sourceSets.main]
    }

    // ...
}

この設定をすれば checkstyleMain というGradleタスクが使えるようになるので、後はそれを実行すればチェックしてくれるようになります。

f:id:excite-takayuki-miura:20220404170517p:plain

とても簡単で非常に便利なのですが、全くコードを変更せずにもう一回実行すると、以下のような表示になってしまいます。

f:id:excite-takayuki-miura:20220404170638p:plain

先程まで出ていたはずのWarningが全く出てこなくなってしまいました。 これはどういうことなのでしょうか?

2回目以降のCheckstyle実行

実はGradleには、入出力の結果が全く同じ場合、タスクを実行せずにスキップするという機能があります。 2回目の実行時に UP-TO-DATE という文字列が出ていますが、これはスキップされたことを表しているのです。

つまり、一度 checkstyleMain を実行してしまうと、コードを変更しない限りもう一度コマンドライン上からWarningを確認することができないのです。 当然ですが、1回目しか確認できないのは大きな問題です。

ではどうすれば良いかというと、実は checkstyleMain を実行すると、その結果をHTMLファイルとして以下のパスに出力してくれます。

build/reports/checkstyle/main.html

ブラウザで開くと以下のようになっているので、これを見れば良いというわけです。

f:id:excite-takayuki-miura:20220404171246p:plain

f:id:excite-takayuki-miura:20220404171332p:plain

ページ内リンク等も適切に貼られているので、慣れてしまえば特にストレスなく確認できるでしょう。

まとめ

知ってしまえば簡単なことですが、初めてCheckstyleを触ると「さっきまで出ていたはずのWarningがなくなった…?」と混乱してしまうこともあると思います。

ぜひこの情報を参考にしてもらえれば幸いです。

ConstructorPropertiesを使ったAPIリクエストモデルでは、ParameterObjectは使えないという話

f:id:excite-takayuki-miura:20220401172710p:plain

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

JavaのSpring Bootでは、OpenAPIのライブラリを使用することでAPIのリクエストモデルを簡単にドキュメント化することができます。

ただ、リクエスト用のモデルで ConstructorProperties を使っていると、うまいことドキュメント化ができないことがあります。

今回は、 ConstructorProperties を使ったAPIリクエストモデルでは、 ParameterObject は使えないという話をしていきます。

Spring BootとOpenAPI

Spring Bootでは、OpenAPIのライブラリを使用することで、以下のようにAPIを簡単にドキュメント化できます。

build.gradle

plugins {
    // ...
    id "org.openapi.generator" version "5.4.0"
    id "org.springdoc.openapi-gradle-plugin" version "1.3.3"
    // ...
}

実コード

package sample;

import lombok.RequiredArgsConstructor;
import lombok.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
@RequestMapping("sample")
public class SampleController {

    @GetMapping
    public String info(@ModelAttribute SampleRequestModel sampleRequestModel) {
        return "ok";
    }
    
    @Value
    public static class SampleRequestModel {
        /**
         * サンプル文字列1
         */
        String sampleString1;

        /**
         * サンプル文字列2
         */
        String sampleString2;
    }
}

このコードによって、自動的に以下のようなドキュメントが生成されます。

f:id:excite-takayuki-miura:20220401170913p:plain

更に、以下のように @ParameterObject を付けると、より可読性が高まります。

    // ...

    @GetMapping
    public String info(@ModelAttribute @ParameterObject SampleRequestModel sampleRequestModel) {
        return "ok";
    }

    // ...

f:id:excite-takayuki-miura:20220401171131p:plain

このドキュメントはブラウザ上で見られるのですが、その画面から直接APIへのリクエストもできるため、とても便利なものとなっています。

しかし、実はリクエストモデル上で ConstructorProperties を使っていると、うまくドキュメント化されないという問題があります。

ConstructorPropertiesを使ったAPIリクエストモデルでは、ParameterObjectは使えない

リクエストモデルを、以下のようにしてみましょう。

    // ...

    @Value
    public static class SampleRequestModel {
        /**
         * サンプル文字列1
         */
        String sampleString1;

        /**
         * サンプル文字列2
         */
        String sampleString2;

        @ConstructorProperties({"test_string_1", "test_string_2"})
        public SampleRequestModel(String testString1, String testString2) {
            this.sampleString1 = testString1;
            this.sampleString2 = testString2;
        }
    }

sampleString1 はクエリパラメータ上では test_string_1 として、 sampleString2 はクエリパラメータ上では test_string_2 として受け取るので、ドキュメント上でも test_string_1test_string_2 のみが表示されていることが、こちらが意図する挙動となります。

ではまず、@ParameterObject を付けない状態でドキュメント化するとどうなるかというと、以下のようになります。

f:id:excite-takayuki-miura:20220401171629p:plain

なんとこのように、 ConstructorProperties の値とモデルのプロパティの値の両方が出てしまうのです。

更に @ParameterObject を付けると、以下のようになってしまいます。

f:id:excite-takayuki-miura:20220401171744p:plain

モデルのプロパティの値しか出ず、 ConstructorProperties の値は出てきません。

まとめ

残念ながら、現状では ConstructorProperties を使ったAPIリクエストモデルでは、 ParameterObject は使えないようです。

ConstructorProperties を使わずにリクエストモデルを作成するか、いつか対応されることを願いながら気長に待つのが良いかと思います。

2ヶ月間の就業型インターンで学んだこと

f:id:e_yamauchi_hiroki:20220330122301p:plain

はじめに

こんにちは、エキサイト株式会社でインターンをさせていただいている山内です。 今回は約2か月間、就業型インターンで何をさせてもらい、何を学んだかについてご紹介します。

自己紹介

趣味は麻雀や野球などで、家に雀卓があったりします。 あと、HipHopが好きだったりもします。

技術的なことでは、個人開発やハッカソンの経験がある程度で、実務経験などは全くありませんでした。

やったこと

では、本題です。 今回のインターンでは、メディア事業部でレシピメディアである「E・レシピ」のAPIの、PHPからJavaのSpring Bootへのリビルドをさせていただきました。

Javaはこれまで触ったことはありませんでしたが、はじめにペアプロのような形で基礎的なことや開発指針について教わることができ、スムーズに開発に参加させてもらえたと思います。

学んだこと

今回、このインターンで学んだことはとてもたくさんありました。 その中でも、「良いコードを書く」ということと、「アーキテクチャ」についてお話しします。

良いコードを書く

まず、「良いコードを書く」ということについてです。 私は今回のインターンで学ぶまでは、良いコードを書くと言っても具体的に何をすればいいのかわかっていませんでした。

そんな中、インターン中ではメンターの方に限らず、たくさんのエンジニアの方がお話してくれたり、コードレビューをしてくれたりしました。 そうしていくうちに、どういうことを考えてコードを書いているのか、何に気を付ければいいのかということが少しずつわかってきました。

アーキテクチャ

次に「アーキテクチャ」についてです。 業務レベルでサービスを運用していくにはアーキテクチャがどれだけ大切か、逆に、アーキテクチャを考えなければどうしんどくなるのか、ということについてとても勉強になりました。 例えば、きちんとアーキテクチャを考えることで、機能の新規追加や修正をする際に、他の場所への影響を最小限で抑えられるということなどです。

実例

良いコードを書くということや、アーキテクチャについて勉強になったと思う一例である、ファン登録機能に関するプルリクでの議論についてご紹介します。

まず前提として、E・レシピにはユーザが料理家をファン登録する機能があり、1ユーザーが登録できるファンの数には上限があります。 そのため、実現したい機能の流れは以下のようになります。

  • ファン登録をする際に既にファン登録している数が上限数かどうかを判定
    • 上限数以下
      • ファン登録
    • 上限数
      • エラーを返す

ここで、「ファン登録上限数を超えているかの判定をどこでするのか」ということについて議論がありました。 私ははじめ、この判定をユースケースで行っていました。 しかし、本当にユースケースに書くのが適切なのか、モデルなどで書くほうがいいのではないか、などの意見交換がありました。

話し合っていく中で、アーキテクチャのレイヤーごとの役割分担の考え方や、「良いコードを書く」というところでも話した、何を考え、何に気を付けるのか、ということに対してとても理解が深まりました。

最終的には「ファンの上限」という仕様はサービス共通のため、サービス層に書くということになりました。 このやりとりの中で、結果というよりもその過程の「考えて話し合う」というところがとても勉強になり、考えてコードを書くことの重要性を感じました。

まとめ

今回のインターンでは業務レベルでの開発を経験させていただき、たくさんのことを学ぶことができました。 しかし、考え方を理解しても実際にそれをコードに落とし込むことは難しく、まだまだ実践できていない部分が多いと感じました。 今回学んだことがゴールではなく、きちんと使えるようになるためにこれからも意識していきたいと思っています。

最後に

メンターの方々をはじめ、エンジニア、人事の方々のおかげで楽しく、たくさんのことを学べるインターンになりました。

ありがとうございました!