Figmaのバリアントプロパティでアイコンの色まで制御したい

こんにちは!SaaS・DX事業部デザイナーの鍜治本(@KAJIJI_design)です。
SaaSではtoB向けツールのUIを作成するにあたり、デザインシステムを設計したり、独自のコンポーネントを活用しています。
今回はその中の一つであるButtonにフォーカスして、発見したFigmaのTipsを共有します!🙌

デザインシステムって?

日本の行政機関で初めてデジタルに特化したデジタル庁では、デザインシステムについて次のような記載をしています。

構築と推進の目的
デザインと開発を効率化し、利用者の課題解決に集中する
大規模なサービスで素早く改善サイクルを回す
一貫性を担保し、行政サービスを使いやすくする
開発チームの円滑なコミュニケーション
デジタル庁 デザインシステムより引用 www.digital.go.jp

制作側、特にデザイナーにとっては「一貫性を担保」や「開発チームの円滑なコミュニケーション」がメリットになると思います。
SaaSは今までデザイナーがひとり体制だったので、ある程度伴走しながら設計していく運用でなんとかなっていました。今年からインターン生が増えたり、チームとしてより活発に回り始めたこともあり、誰が作ってもズレのない表現ができるデザインシステムの構築を進めています。

Buttonをバリアントで切り替える

KUROTEN ComponentsのButton Filled
これはSaaS・DX事業部で提供している「KUROTEN」というツールで使われているボタンの一部です。
Componentsの仕組み自体はGoogleMaterial Design3を参考にしていますが、マウスホバー・押下といった状態変化をバリアントのグループにしています。

プロパティのON/OFFでアイコンの表示/非表示を切り替えられる
テキストとアイコンを組み合わせられるプロパティにしているので、アイコンを任意で表示/非表示の切り替えができるようにしています。

別保管しているアイコンを呼び出して変更できる
さらに、インスタンスの入れ替えプロパティを使ってアイコン自体も入れ替えができるようにしています。今回はここのアイコン変更について悩んでいた事象を解決しました。

アイコンがデフォルトの色から変わってくれない


アイコン自体は #3F4B5B のカラーを指定しており、Filled ButtonのPrimary でアイコンを使用する場合には #FFFFFF に変更しています。
本当であれば、バリアントを入れ替えた時に色まで変わってくれると便利なのですが…

バツアイコンから別のアイコンに切り替えている様子
なぜかうまく反応せず、エクスポート(右斜め上矢印付きのもの)アイコンではデフォルトカラーが残っていたり、クエスチョンアイコン(?マークのもの)に至っては色が全く変わりません。

原因を探してみる

インスタンスの入れ替えで変わるもの・変わらないものがあったことから、アイコン側に原因があると仮説しました。そして、アイコン自体の構造を見直しながら切り替えたところ、2つの原因が見えてきました。

①パスやシェイプの名称が一致していない

アイコンをレイヤーから見てみると、それぞれのパスやシェイプが一致していない場合があります。今回で言うと、closeアイコンは「Vector」、external linkアイコンは「Union」になっていました。
アイコン作成時にAdobe Illustratorを使ったり、Figmaのシェイプで描画することで、最終的な名称のズレが生じてしまうようです。

closeアイコンは「Vector」に対しexternal linkアイコンは「Union」になっている
(手間ではありますが)名称を一致させることで、同じシェイプと認識され、インスタンス入れ替えも反応するようになりました。
closeアイコンとexternal linkアイコンを切り替えて反応している様子

②パス・シェイプが複数に分かれている

もう一つの原因で、シェイプがグループ化されていたりフレームになっているなど、1つのシェイプにまとまっていないことが挙げられます。

folderアイコンがGroup化され、2つのシェイプから成っている
画像のfolderアイコンは「Group 8」の中に2つのVectorが組み合わさってできているため、うまく反応しなかったようです。
Groupごと選択し「選択範囲の結合」すると、1つのUnionに
Groupを選択した状態で、画面上部の「ブーリアングループ」内の「選択範囲の結合」をすることで1つのUnionに変換できます。
ただし、このままではUnion内に2つのVectorが存在したままです。
Union内に2つのVectorがある状態
もう一度Unionを選択し、「ブーリアングループ」内の「選択範囲を結合」(⌘+E)をすると、1つにまとまったシェイプに変換できます。
Unionグループを選択して「選択範囲を結合」(⌘+E)
1つにまとまったシェイプかどうかの確認は、下記の2種類があります。
レイヤーとオブジェクトそれぞれでの確認方法

  • レイヤーであればUnionやVectorより下に階層ができていないかどうか
  • オブジェクトの編集モードにしたときに、余計な線の重なりがないか

中にはシェイプ内に余計な線が入っているものもあり、これも削除しておいた方が無難です。Illustratorと異なり線だけ消すことができないため、ベクターネットワークを作った上で削除するとスムーズです。

UnionやVector内に余計な線が含まれている
ベクターネットワークを作成してから、不要なポイントを削除

まとめ

今回はバリアントのプロパティ切り替えにおける、アイコンの色の制御についてわかったことをまとめました。

  1. アイコン作成時にシェイプの名称を一致させる
  2. 複数のシェイプで構成されないように、シェイプを1つにまとめる
  3. 余計な線が残らないようにする

自分流で見つけた手法なので、「こっちの方がわかりやすいよ!」などあれば是非教えてください…!

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

こんにちは、エキサイト株式会社でエンジニアをしております、吉川です。 少し前になりますが、7月7日に社内の交流会である「テクデザBeer Bash」の第5回を開催しましたので、そのレポートを書いていきます。

前回のレポートはこちら↓

tech.excite.co.jp

テクデザBeer Bashとは

テクデザBeer Bashは、社内のエンジニア・デザイナーの交流を深めるためのイベントです。

軽食・お酒やソフトドリンクを片手に部署問わず参加者同士で話したり、登壇者によるコンテンツ発表を一緒に聞いたりして関係性を深めてもらい、日々の業務でも気軽に話し合えるようになってもらうことを目的としています。 (*感染症対策をした上での開催となっています)

飲み物

食べ物

当日の様子

今回のコンテンツは

  • エキサイトHDページリニューアル
  • PLIZM

の2つを用意しました。どちらもここ最近の社内で注目された取り組みであり、改めて活動を社内に広めることができるようにしました。

乾杯

エキサイトHDページリニューアル

エキサイトHDページリニューアル
4月19日にエキサイトHDは東証スタンダード市場に上場いたしました。それに伴いHDページの大々的なリニューアルを行ったので、リニューアルに関わったデザイナーやビジネス職の方から、実際どういうフローで業務を行っていたのか紹介してもらいました。

事業部を超えた特別なプロジェクトということで、普段の業務とはまた違った別の苦労や貴重な経験があり、それを広く社内に伝えることができたのではないかと思います!

リニューアルされたHDページはこちら↓

www.excite-holdings.co.jp

PLIZM

PLIZM
4月11日にエキサイトHDの新しいメディア「PLIZM」がリリースされました!ブロックチェーンゲームのメディアであり、ゲーム情報も仮想通貨情報も1ヶ所に集約したメディアとなっています。

社内でも詳細を知っている人はそう多くないということで、開発・運営チームから、PLIZMとはどういうメディアなのか?またブロックチェーンゲーム業界はどのようなものなのか?などについて改めて周知することができたかと思います!

PLIZMのページはこちら↓

plizm.io

最後に

今回は前回開催からあまり間を置かず、2ヶ月ちょっとの期間で開催することができました。また参加者も引き続き30人ほどを集めることができました。去年から続けてきたこのイベントもようやく安定してきたかなという感触です。今後はこの盛り上がりを続けていけるように、もっとコンスタントに開催できるように頑張っていきます!また次回開催の際にはこちらのブログで紹介いたします!

GitHub Discussionsのテンプレートを作成してみた

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

今回は、GitHub Discussionsのテンプレートを作成方法について紹介します。

サンプルコードは以下で公開しています。

github.com

GitHub Discussionsとは

GitHub Discussionsは、GitHubリポジトリの一機能で、コミュニティの共同コミュニケーションフォーラムです。GitHub Discussionsを利用することで、コミュニティメンバーは最新情報を共有したり、質問をしたり、アンケートを取ることができます。

デフォルトの状態でも十分に活用できますが、独自でカテゴリを追加することもできます。 今回は、独自でカテゴリを追加した際のGitHub Discussionsのテンプレート作成方法について紹介します。

docs.github.com

GitHub Discussionsのテンプレート作成方法

(1) テンプレートを作成するDiscussionsのカテゴリ用意

  1. Discussions ページに遷移し、カテゴリの編集ボタンを押します。

  2. カテゴリを作成します。

  3. カテゴリを登録します。今回はカテゴリ名をArchitecture Decision Recordとします。

(2) テンプレートファイル作成

まず、/.github/DISCUSSION_TEMPLATE/ ディレクトリを用意します。

/.github/DISCUSSION_TEMPLATE/ ディレクトリ配下に、(1)で用意したカテゴリの名でテンプレートファイルを作成します。 今回の場合は、カテゴリ名がArchitecture Decision Recordなので、/.github/DISCUSSION_TEMPLATE/architecture-decision-record.ymlになります。

テンプレートファイルの書き方は、公式のドキュメントを参照ください。

ディスカッション カテゴリ フォームの構文 - GitHub Docs

今回の場合は以下のように作成しました。

labels: [Architecture Decision Record]
body:
  - type: dropdown
    attributes:
      label: ステータス
      description: この文書のステータスを選択してください。
      options:
        - "提案済み"
        - "承認済み"
        - "破棄"
    validations:
      required: true
  - type: textarea
    attributes:
      label: コンテキスト
      description: この決定を行った状況について書いてください。
    validations:
      required: true
  - type: textarea
    attributes:
      label: 決定
      description: 決定とその根拠について書いてください。
    validations:
      required: true
  - type: textarea
    attributes:
      label: 影響
      description: 決定による影響や、比較・検討した内容について書いてください。
    validations:
      required: true
  - type: textarea
    attributes:
      label: コンプライアンス
      description: この決定が順守されていることを確認方法について書いてください。
    validations:
      required: true
  - type: textarea
    attributes:
      label: 備考
      description: 参考文献等あれば書いてください。
    validations:
      required: false

3. デフォルトブランチにmerge

デフォルトブランチにmergeしたら作成完了です。

GitHub Discussionsのテンプレート使用方法

テンプレートを作成したら、GitHub Discussionsで該当のカテゴリを選択してディスカッションを作成します。 作成ページでは、テンプレートで用意した入力フォームが表示されます。

Discussion 作成ページ

まとめ

GitHub Discussionsのテンプレートを作成することで、ディスカッションの作成を効率化することができます。 独自のカテゴリを用意してGitHub Discussionsを使う際はテンプレートを用意してみてはどうでしょうか。

小話

今回の例で扱っている アーキテクチャ決定文書(Architecture Decision Record) をgitで保管することについては様々な意見があります。この話についてはまたの機会に書きたいと思います。

アーキテクチャ決定文書(Architecture Decision Record)の詳しい話は、「ソフトウェアアーキテクチャの基礎」の「19.3 アーキテクチャデシジョンレコード」に書いてあります。 興味のある方は読んでみてください。

【福岡オフライン開催】JJUG CCC 2023 Spring報告会 に参加してきました

こんばんは、中尾です。

【福岡オフライン開催】JJUG CCC 2023 Spring報告会に参加してきました。

javaq.connpass.com

場所はエンジニアカフェです。

時間 内容 登壇者
18:40 ~ 19:00 開場・受付開始
19:00 ~ 19:05 開始挨拶
19:05 ~ 19:25 JJUGについて、Java21の新機能 きしだ なおき
19:25 ~ 20:05 再演 Webアプリケーションを作りましょう @irof
20:10 ~ 20:20 LT ヌーラボ
20:20 ~ 21:00 懇親会
21:30 完全撤収

開始前にゾロゾロ人が入っていきます。さすがエンジニアカフェ。

まず、岸田さんの「JJUGについて、Java21の新機能」です。

Virtual Thread、やはり気になります。

はじめて生のきしださんに会えてお話しできて嬉しかったです。プロになるJava持っていって、サイン貰えばよかったな。。。

続いて、@irofさんの「再演 Webアプリケーションを作りましょう」です。

ハンズオン形式なので、SpringBootを使ったことない人でも安心でした。。

最後の方にテスト周りの話になって、プロジェクトによって、いろいろあるってことをおっしゃっていたので、そうだよなと本当に思いました。

speakerdeck.com

後、わからないことはドキュメントに書いてある!っということなので、ちゃんとドキュメントを読もうと思います。

irof.hateblo.jp

生の@irofさんとお話しできてよかったです。JJUGのTシャツを白いの買って、サイン書いて貰えばよかった。

次は登壇できるくらいのネタを持っていきます!

集合写真、私はどこにいるでしょう!?

GitHub Actionsでブランチ名を取得してコンテナのイメージタグ名にする

はじめに

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

普段の業務では、GitHub Actionsのworkflow_dispatchを使用してコンテナイメージをビルド、プッシュしています。 このとき、Gitのブランチ名をコンテナのイメージタグ名とするワークフローを使用しています。

これにより、コンテナレジストリやコンテナ実行環境で、どのブランチのコードが動作しているのかが簡単にわかるようになります。 本記事では、GitHub Actionsでブランチ名を取得して、コンテナのイメージタグ名にする方法を紹介します。

ワークフロー

下記に普段業務で使用しているワークフローの一部を示します。 このワークフローを実行すると、対象のブランチでコンテナイメージのビルドをして、それをコンテナレジストリにプッシュします。 また、コンテナレジストリには、Amazon ECRを使用しています。

name: build and push sample
run-name: ${{ github.workflow }} (${{ github.ref_name }})

on:
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1-node16
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Get tag
        id: get-tag
        run: |
          TAG=$(echo ${{ github.ref_name }} | sed -e 's/\//\-/g')
          echo "value=${TAG}" >> $GITHUB_OUTPUT

      - name: Get image name
        id: get-image-name
        run: |
          echo "value=${{ steps.login-ecr.outputs.registry }}/sample:${{ steps.get-tag.outputs.value }}" >> $GITHUB_OUTPUT

      - name: Build and push to Amazon ECR
        run: |
          docker build . \
              -f Dockerfile \
              -t ${{ steps.get-image-name.outputs.value }}

          docker push ${{ steps.get-image-name.outputs.value }}

タグ名を取得する

ブランチ名は、${{ github.ref_name }}で取得することができます。 しかし、以下の引用の通り、ブランチ名でよく使用される/は、イメージタグ名に含めることができません。

タグ名に含められるのは有効な ASCII 文字で、(アルファベット)小文字と大文字、数字、アンダースコア、ピリオド、ダッシュです。 タグ名はピリオドやダッシュで開始できません。そして最大で 128 文字です。

docs.docker.jp

業務では、ブランチ名に/を使用しています。 そのため、sedコマンドを使用してイメージタグ名に使用できる-に置換する処理を追加しています。

置換前後の例を以下の表に示します。

置換前 置換後
feature/add-book-title feature-add-book-title
feature/fix/book-title feature-fix-book-title

プッシュしたイメージ名を確認する

Amazon ECRにプッシュしたコンテナイメージの、イメージタグ名を確認します。 feature/...bugfix/...といったブランチ名の/が、-に置換されていることを確認できます。

おわりに

GitHub Actionsでブランチ名を取得して、コンテナのイメージタグ名にする方法を紹介しました。

本記事が誰かの役に立てれば幸いです。

採用アナウンス

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

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

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

neuet × LINE Fukuoka Engineer Meetupに参加してきました

こんばんは、最近登壇芸人になっている中尾です。

エキサイトブログの刷新の話しかしてないですが。

neuet × LINE Fukuoka Engineer Meetupに参加してきました。

場所はFGN(Fukuoka Growth Next)です。スタートアップ企業を支援する福岡市の施設です。有名な場所なのでエンジニアなら一度は伺ってほしいです。

line.connpass.com

タイムテーブルは以下になります。

  • セッション1 チャリチャリ neuet株式会社 蛭田さん

チャリチャリのセキュリティ対策の話とかしていただけました。

コスト削減の話もしていただけました。

どんどんチャリチャリを展開していってほしいです。

  • セッション2 LINEスキマニのOh Jinseokさん

LINEスキマニのアーキテクチャーの話をしていただきました。

Spring Boot と Kotlin を使っているようです。

開発の流れなどいろいろ話をしてただけました。

  • 懇親会

懇親会は熱気あふれていました。学生から社会人までいろいろ語っていました。

勉強会の醍醐味ですね。

懇親会後の一発目です。

エキサイトブログのリビルド後の話をしました。データベースのサイズを小さくしたこと、なぜできたのか?SQLのパフォーマンスチューニングをしていることを話しました。

写真はこれだけしかないです。。。申し訳ない。。

  • LT3 転職で見えた新天地:ハードウェアの受託開発からtoCサービス運営企業への大転換 佐藤一幸さん

neuetに転職してまもない佐藤さんの発表です。

neuetの技術についていろいろお話していました。私の発表と違って真面目にお話をしていて素晴らしかったです。

  • パネルディスカッション

最後にパネルディスカッションです。蛭田さん、新田さん、Oh Jinseokさん、がエンジニアについてを語ってくれました(このあたりからあまり覚えていないwwすみません)。

  • 最後に

学生の方が話しかけてくれて、いろいろ事業について聞いてくれました。私もそれに答えて回答しました。良いエンジニアになってほしいです。

またこういう機会があれば、登壇しようと思います。

Spring Bootで自動生成するOpenAPIで、同名のクラスをcomponents.schemasとして登録する方法

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

以前、Spring BootからOpenAPIのドキュメントを自動生成する方法について説明しました。

tech.excite.co.jp

今回はその中で少し詰まった、「同名のクラスを components.schemas として登録する方法」について説明します。

components.schemaとは

components.schemas は、OpenAPIの仕様の一つで、使い回しが出来るよう纏められたモデルのドキュメントです。

swagger.io

Often, multiple API operations have some common parameters or return the same response structure. To avoid code duplication, you can place the common definitions in the global components section and reference them using $ref.

例えば、Spring Bootで以下のようなコードからOpenAPIドキュメントを自動生成してみます。

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

    @GetMapping
    public SampleResponseModel sample() {
        SampleResponseModel responseModel = new SampleResponseModel();
        responseModel.setValue("Hello world!");

        return responseModel;
    }

    @Data
    static class SampleResponseModel {
        private String value;
    }
}

すると、以下のようなYamlファイルが出力されます。

openapi: 3.0.1
info:
  title: OpenAPI definition
  version: v0
servers:
- url: http://localhost
  description: Generated server url
paths:
  /sample:
    get:
      tags:
      - sample-controller
      operationId: sample
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/SampleResponseModel'
components:
  schemas:
    SampleResponseModel:
      type: object
      properties:
        value:
          type: string

この2つを比較すると、以下のことがわかります。

  • Spring Bootコード側でレスポンスモデルとして定義していた SampleResponseModel が、OpenAPIドキュメントの中では components.schemas として定義されている
  • /sample エンドポイントのレスポンスの型が、直接指定されているのではなく、 $ref: '#/components/schemas/SampleResponseModel' のように components.schemas を指すようになっている

こうすることで、 SampleResponseModel をOpenAPIドキュメント内で使い回すことが出来るようになります。

例えば、以下のようなSpring Bootコードを考えてみます。

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

    @GetMapping
    public SampleResponseModel sample() {
        SampleResponseModel responseModel = new SampleResponseModel();
        responseModel.setValue("Hello world!");

        return responseModel;
    }

    // 同じモデルを返すエンドポイントを追加
    @GetMapping("2")
    public SampleResponseModel sample2() {
        SampleResponseModel responseModel = new SampleResponseModel();
        responseModel.setValue("Hello world!");

        return responseModel;
    }

    @Data
    static class SampleResponseModel {
        private String value;
    }
}

同じモデルを返すエンドポイントを追加してみました。

すると、OpenAPIドキュメントは以下のようになります。

openapi: 3.0.1
  title: OpenAPI definition
  version: v0
servers:
- url: http://localhost
  description: Generated server url
paths:
  /sample:
    get:
      tags:
      - sample-controller
      operationId: sample
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/SampleResponseModel'
  /sample/2:
    get:
      tags:
      - sample-controller
      operationId: sample2
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/SampleResponseModel'
components:
  schemas:
    SampleResponseModel:
      type: object
      properties:
        value:
          type: string

SampleResponseModel は二重に定義されず、 components.schemas として定義された SampleResponseModel が使い回される形になっているのがわかります。

このように、 components.schemas はドキュメントの可読性や文量を減らすためにとても有用な機能なのですが、Spring Boot上で自動生成する場合に問題が起きる場合があります。

その問題とは、「同じ名前の components.schemas を登録できないこと」です。

同じ名前のcomponents.schemasを登録できない

以下のようなコードを考えてみます。

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

    @GetMapping
    public SampleResponseModel sample() {
        SampleResponseModel responseModel = new SampleResponseModel();
        responseModel.setValue("Hello world!");

        return responseModel;
    }

    @Data
    static class SampleResponseModel {
        private String value;
    }
}
@RestController
@RequestMapping("sample2")
public class Sample2Controller {

    @GetMapping
    public SampleResponseModel sample() {
        SampleResponseModel responseModel = new SampleResponseModel();
        responseModel.setValue2("Hello world!");

        return responseModel;
    }

    @Data
    static class SampleResponseModel {
        // 2 の方は、フィールド名を変更
        private String value2;
    }
}

先程のエンドポイントを、メソッドではなくクラスごと分けてみました。

また、レスポンスモデルについて、名前は同じですがフィールド名を変えています。

これでOpenAPIドキュメントを自動生成すると、以下のようになります。

openapi: 3.0.1
  title: OpenAPI definition
  version: v0
servers:
- url: http://localhost
  description: Generated server url
paths:
  /sample:
    get:
      tags:
      - sample-controller
      operationId: sample
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/SampleResponseModel'
  /sample2:
    get:
      tags:
      - sample-2-controller
      operationId: sample_1
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/SampleResponseModel'
components:
  schemas:
    SampleResponseModel:
      type: object
      properties:
        value:
          type: string

なんと components.schemas には、 value2 というフィールドを持つモデルが登録されていません!

というのも、 components.schemas には同名のモデルを登録することはできないのです。

そのため、 value をフィールドに持つモデルのみが残り、 value2 の方は消えてしまったのでした。

ですがもちろん、ドキュメントとしてはこれは不完全です。 では、どうすれば良いのでしょうか?

同名のクラスをcomponents.schemasとして登録する方法

解決方法は2つあります。

モデル名が被らないようにする

1つ目は、そもそもSpring Boot側でモデル名が被らないようにすることです。

先程の value2 の方のコードを以下のように変えてみます。

@RestController
@RequestMapping("sample2")
public class Sample2Controller {

    @GetMapping
    public SampleResponseModel2 sample() {
        SampleResponseModel2 responseModel = new SampleResponseModel2();
        responseModel.setValue2("Hello world!");

        return responseModel;
    }

    @Data
    static class SampleResponseModel2 { // 名前を被らないように変更
        private String value2;
    }
}

すると、OpenAPIドキュメントは以下のようになります。

openapi: 3.0.1
  title: OpenAPI definition
  version: v0
servers:
- url: http://localhost
  description: Generated server url
paths:
  /sample:
    get:
      tags:
      - sample-controller
      operationId: sample
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/SampleResponseModel'
  /sample2:
    get:
      tags:
      - sample-2-controller
      operationId: sample_1
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/SampleResponseModel2'
components:
  schemas:
    SampleResponseModel:
      type: object
      properties:
        value:
          type: string
    SampleResponseModel2:
      type: object
      properties:
        value2:
          type: string

value2 をフィールドに持つ SampleResponseModel2 が生成され、 /sample2 エンドポイントの返り値の型として使われているのがわかります。

これで、無事想定通りのドキュメントになりました。

ただ、この方法で解決できるならいいのですが、Spring Boot側のコードのアーキテクチャやルール的に、同名のレスポンスモデルを作りたいという場合もあるでしょう。

その場合は、次の方法で解決できます。

自分でcomponents.schemasの名前をつける

デフォルトでは components.schemas への登録名はモデル名になりますが、アノテーションをつければ手動でも設定できます。

以下のようにします。

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

    @GetMapping
    public SampleResponseModel sample() {
        SampleResponseModel responseModel = new SampleResponseModel();
        responseModel.setValue("Hello world!");

        return responseModel;
    }

    @Data
    @Schema(name = "SampleResponseModel") // Schemaアノテーションを追加
    static class SampleResponseModel {
        private String value;
    }
}
@RestController
@RequestMapping("sample2")
public class Sample2Controller {

    @GetMapping
    public SampleResponseModel sample() {
        SampleResponseModel responseModel = new SampleResponseModel();
        responseModel.setValue2("Hello world!");

        return responseModel;
    }

    @Data
    @Schema(name = "SampleResponseModel2") // Schemaアノテーションを追加
    static class SampleResponseModel {
        private String value2;
    }
}

このように、レスポンスモデルに @Schema というアノテーションを、 name 付きで追加します。

すると、OpenAPIドキュメントは以下のようになります。

openapi: 3.0.1
  title: OpenAPI definition
  version: v0
servers:
- url: http://localhost
  description: Generated server url
paths:
  /sample:
    get:
      tags:
      - sample-controller
      operationId: sample
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/SampleResponseModel'
  /sample2:
    get:
      tags:
      - sample-2-controller
      operationId: sample_1
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/SampleResponseModel2'
components:
  schemas:
    SampleResponseModel:
      type: object
      properties:
        value:
          type: string
    SampleResponseModel2:
      type: object
      properties:
        value2:
          type: string

この方法により、モデル名ではなく name で指定した名前で登録できることがわかります!

つまり、コーディングルール的にレスポンスモデル名を同じにする必要がある場合でも、この方法は使用可能ということになります。

また、今回はサンプルのため SampleResponseModel2 としましたが、たとえばパッケージ名を含めるなどすれば、より適切な名前になるでしょう。

最後に

今回の「 components.schemas に同名のクラスは登録できない」のように、OpenAPIドキュメントを自動生成することになると、通常の開発に加えてOpenAPI側の仕様も考える必要が出てきます。

一見面倒が増えるだけに見えますが、それでも中長期的に考えれば、APIドキュメントが自動生成されるのは保守性や開発容易性のために大きな貢献となるはずです。

ぜひ皆さんも使ってみてください。

意外と(社内で)知られていないIntelliJのブックマーク機能

こんにちは。
いつものtaanatsuです。

今回はIntelliJ IDEAやPhp Stormのブックマーク機能を紹介します。

ブックマーク

ブックマークを行う

F3キー、または、行番号を右クリック→「Add Bookmark」を選ぶと、
行番号横にブックマーク記号が出現します。

ブックマークに移動する

⌘ + F3キーで、ブックマーク一覧が表示され、
クリックするとその場所にジャンプすることができます。

これは、ブックマークしたタブを閉じていても開いてくれるのでとても便利です!

Mnemonicブックマーク

自分がよく使う機能はこちらです。

ブックマークを行う

Ctrl + Shift + 数字キー(1〜0)で指定行をブックマークすることができます。

ブックマークの解除は、同じ行のところで、同じ操作を行えば消すことができます。
例:Ctrl + Shift + 3 でブックマークした行なら、Ctrl + Shift + 3を押すとブックマーク解除

ブックマークに移動する

Ctrl + 登録した数字キーでブックマークに移動します。

例:Ctrl + Shift + 3 でブックマークした行に行きたいときは、Ctrl + 3 で移動できます

これも、ブックマークしたタブを閉じていても開いてくれるのでとても便利です!

また、⌘ + F3キーで、ブックマーク一覧が表示されて、そこからジャンプすることもできます。


以上です。

ファイルを行き来してコードを見なければいけないときにとても便利ですので、使ってみてください!
それではまた次回!

SpotBugsの警告を抑止する

エキサイト株式会社メディア事業部エンジニアの佐々木です。メディア事業部ではSpringBoot/Javaで開発を行っており、静的解析ツールはSpotBugsを使用しています。ごくまれに、SpotBugsの指摘を抑止したいことがあります。Tipsとしてご紹介します。

前提

下記環境で動作確認済みです。

openjdk version "17.0.7" 2023-04-18 LTS
OpenJDK Runtime Environment Corretto-17.0.7.7.1 (build 17.0.7+7-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.7.7.1 (build 17.0.7+7-LTS, mixed mode, sharing)
plugins {
    id "com.github.spotbugs" version "5.0.6"
}

問題となるコード

Enumの中で、定数且つイミュータブルなListを返却するメソッドでSpotBugsが警告をしてくれます。

public enum AreaType {
    HOKKAIDO, TOHOKU, KANTO, CHUBU, KANSAI, CHUGOKU, SHIKOKU, KYUSHU;
    private static final List<AreaType> AREA_TYPE_LIST = List.of(AreaType.values());

    
    public List<AreaType> getAreaTypeList(){
        return AREA_TYPE_LIST;
    }
}


----- 下記 SpotBugsの警告内容
Malicious code vulnerability (内部表現を暴露するかもしれない配列を返すメソッド)
EI_EXPOSE_REP (可変オブジェクトへの参照を返すことによって内部表現を暴露するかもしれないメソッド)
---

このように警告がでてしまいます。弊社ではテストが失敗するとGithubのプルリクエストのマージができませんので、対処する必要があります。

修正方法

上記のコードの対応方法は、公式によると、

EI: 可変オブジェクトへの参照を返すことによって内部表現を暴露するかもしれないメソッド (EI_EXPOSE_REP)¶ オブジェクトのフィールドに格納された可変オブジェクトの参照を返すと,オブジェクトの内部表現を暴露します。 インスタンスが信頼できないコードによってアクセスされるなら,可変オブジェクトのチェックされていない変更がセキュリティや他の重要なプロパティを危うくするでしょう。 何か違うことをする必要があります。オブジェクトの新しいコピーを返すことは,多くの状況でより良いアプローチです。 (引用元)https://spotbugs.readthedocs.io/ja/latest/bugDescriptions.html#ei-ei-expose-rep

まとめると、変更可能性がある可変オブジェクトをそのまま返却しないようにするということなのですが、今回は、 final修飾子 がついているのと、 List.of()を使用して、イミュータブルなリストオブジェクトになっており、SpotBugsの指摘には該当しないと判断します。@SuppressFBWarningsを使用することで警告を抑止することが可能となります。

    @SuppressFBWarnings(value = {"EI_EXPOSE_REP"}, justification = "定数且つ不変リストなため")
    public List<AreaType> getAreaTypeList(){
        return AREA_TYPE_LIST;
    }

警告されている箇所に@SuppressFBWarningsをつけることでSpotBugsの指摘がなくなりました。

まとめ

SpotBugsの指摘も、場合によっては抑止が必要となるケースがありますので、@SuppressFBWarningsの設定方法をご紹介させていただきました。無理にコードを変更せずに、この設定で対応可能なケースもありますので、ご参考になればと思います。

最後に

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

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

terraform state mvでStateファイル(tfstate)のリソース状態をマージする

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

Terraformを運用していると、ディレクトリ構造を整理したいケースがあります。 コード上での変更は容易ですが、それだけではStateファイル(tfstate)に差分が生まれてしまい、予期しないリソースの削除を招く危険があります。

今回は、TerraformにおけるStateファイルを安全にマージする方法について説明します。

Terraformのディレクトリ構成を変更するケース

例えば、S3のバケット作成の定義を下記のようなディレクトリ構造で管理しているとします。下記では、作成するバケットごとにmain.tfを分けており、それぞれのディレクトリでterraformコマンドを実行します。

s3
├── image_bucket
│   └── main.tf
└── log_bucket
    └── main.tf

この管理方法ではバケットが増えたときに煩雑になるため、上位階層のs3でimage_bucketとlog_bucketをマージしたいと思います。

s3
└── main.tf // image_bucket と log_bucketをマージ

単純にディレクトリ構成を変更するだけでは、Stateファイルとの差分が生まれてしまい、実体のリソースに対して意図しない破棄が起きてしまいます。 そのため、既にリソースが作成された後にディレクトリ構成やリソース定義を変更する場合、実体のリソースに変更が起きないようにStateファイルの修正が必要です。

Stateファイルのマージ

公式のヘルプセンターにStateファイルのマージ方法が記載されています。今回はこの方法に倣って行いました。

support.hashicorp.com

この手順では、Stateファイルのリソース状態を移動する terraform state mv コマンドを使っています。マージ元からマージ先にリソース状態を移動します。

マージ前の状態

先述した、ディレクトリを変更したいケースを元に手順を説明します。まずはマージ前の状態について説明します。

ディレクトリ構造です。

s3
├── image_bucket
│   └── main.tf
└── log_bucket
    └── main.tf

image_bucket/main.tfです。

terraform {
  required_version = "~> 1.3.4"

  backend "s3" {
    bucket                  = "sample-bucket"
    key                     = "s3/image_bucket/terraform.tfstate"
    region                  = "ap-northeast-1"
    shared_credentials_file = "~/.aws/credentials"
    profile                 = "sample-dev"
  }
}

provider "aws" {
  region                   = "ap-northeast-1"
  shared_credentials_files = ["~/.aws/credentials"]
  profile                  = "sample-dev"
}

terraform {
  required_version = "~> 1.3.4"

  required_providers {
    aws = {
      version = "~> 5.1.0"
      source  = "hashicorp/aws"
    }
  }
}

locals {
  bucket_name = "image-bucket"
}

resource "aws_s3_bucket" "image_bucket" {
  bucket = local.bucket_name
}

下記を実行して、Stateファイルを確認します。

$ terraform state list
aws_s3_bucket.image_bucket

aws_s3_bucket.image_bucket のリソースがあります。

同様に、log_bucket/main.tfには下記を変更した内容を設定します。

locals {
  bucket_name = "log-bucket"
}

resource "aws_s3_bucket" "log_bucket" {
  bucket = local.bucket_name
}

log_bucketのStateファイルも確認します。

$ terraform state list
aws_s3_bucket.log_bucket

マージ手順

マージ元のStateファイルの取得

マージ元のimage_bucketとlog_bucketのStateファイルをリモートバックエンドから取得して、ファイルに書き出します。書き出したファイルは、別途バックアップを作ります。

$ terraform state pull > image_bucket.tfstate
$ cp image_bucket.tfstate image_bucket.tfstate.txt

マージ先のStateファイルの作成

マージ先となる s3/ に空のStateファイルを作ります。

s3/main.tfです。

terraform {
  required_version = "~> 1.3.4"

  backend "s3" {
    bucket                  = "sample-bucket"
    key                     = "s3/terraform.tfstate"
    region                  = "ap-northeast-1"
    shared_credentials_file = "/root/.aws/credentials"
    profile                 = "sample-dev"
  }
}

provider "aws" {
  region                   = "ap-northeast-1"
  shared_credentials_files = ["/root/.aws/credentials"]
  profile                  = "sample-dev"
}

terraform {
  required_version = "~> 1.3.4"

  required_providers {
    aws = {
      version = "~> 5.1.0"
      source  = "hashicorp/aws"
    }
  }
}

下記 コマンドを実行し、マージ先のdestination.tfstateを用意します。

$ terraform init
$ terraform apply
$ terraform state pull > destination.tfstate

バックエンドをローカルに切り替える

ローカルのdestination.tfstateに対してマージ作業を行うので、バックエンドをローカルに切り替えます。

s3/の階層に、下記のoverride.tfファイルを作成します。

terraform {
  backend "local" {
  }
}

下記を実行して、バックエンドを変更を反映します。

terraform init --reconfigure

tfstateの移動 (マージ)

image_bucket.tfstate, log_bucket.tfstateに下記のリソースがあります。

image_bucket/image_bucket.tfstate

aws_s3_bucket.image_bucket

log_bucket/log_bucket.tfstate

aws_s3_bucket.log_bucket

これらを terraform state mv コマンドでマージ先に移動します。

$ terraform state mv -state=./image_bucket/image_bucket.tfstate -state-out=destination.tfstate aws_s3_bucket.image_bucket aws_s3_bucket.image_bucket

~~~~省略~~~~

Move "aws_s3_bucket.image_bucket" to "aws_s3_bucket.image_bucket"
Successfully moved 1 object(s).

これでリソースが移動されました。これをlog_bucketのtfstateに対しても行います。

そして、destination.tfstateのリソース状態に合わせて、s3/main.tfに下記を追記します。

locals {
  image_bucket_name = "image-bucket"
  log_bucket_name = "log-bucket"
}

resource "aws_s3_bucket" "image_bucket" {
  bucket = local.image_bucket_name
}

resource "aws_s3_bucket" "log_bucket" {
  bucket = local.log_bucket_name
}

terraform plan コマンドで、Stateファイルとコードに差分がないことを確認します。

$ terraform plan -state=destination.tfstate;

~~~~省略~~~~

aws_s3_bucket.image_bucket: Refreshing state... [id=image-bucket]
aws_s3_bucket.log_bucket: Refreshing state... [id=log-bucket]

No changes. Your infrastructure matches the configuration.

リソースの実体とtfstateに差分がないことが確認できました。

リモートのtfstateに反映

全てのリソースの移動完了後、マージ先のtfstateの"serial"要素の数値をインクリメントします。インラインコメントの部分を修正します。(本来JSONフォーマットにコメントは書けませんが、便宜上のものです。)

{
  "version": 4,
  "terraform_version": "1.3.4",
  "serial": 2, // 1 -> 2に更新する
  "lineage": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "aws_s3_bucket",
      "name": "image_bucket",

~~~~省略~~~~

バックエンドをリモートに変更して、pushすれば完了です。

$ rm override.tf
$ terraform init -recofigure
$ terraform state push destination.tfstate

終わりに

Terraform におけるディレクトリ構成を変更する場合のStateファイルのマージ方法について説明しました。

大まかな流れとしては、元のStateファイルの内容を元に、マージ先の新しいStateファイルにリソース定義を移していく作業を行っています。 バックアップを取ってから行うと安全です。

参考になれば幸いです。

参考

support.hashicorp.com

qiita.com

OpenAPIを使って、Spring Boot製のAPIからアプリ用のDartコードを自動生成する

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

皆さんは、 OpenAPI をご存知でしょうか?

OpenAPIとはAPIのドキュメントを書くときの仕様のことで、使われているプログラミング言語フレームワークに依存しないため、ドキュメントの標準的な仕様として使われています。

このOpenAPI仕様で作ったドキュメントなのですが、実は OpenAPI Generator というツールを使うことで、そのドキュメントに書かれたAPIに対応するコードを自動生成できます。

今回は、OpenAPIとOpenAPI Generatorを使って、Spring Boot製のAPIからアプリ用のDartコードを自動生成する方法を紹介します。

はじめに

OpenAPIとは

OpenAPIとは、端的に言うと「APIのドキュメントの標準化された書き方」です。

www.openapis.org

The OpenAPI Specification is a specification language for HTTP APIs that provides a standardized means to define your API to others.

そのAPIを開発するのに使われたプログラミング言語フレームワークに関係のない、標準化されたドキュメント仕様であるため、様々なところで使われています。

そしてOpenAPI Generatorというツールを使うことで、このOpenAPI仕様で作られたドキュメントから、対応したコードを自動生成することができます。

OpenAPI Generatorとは

OpenAPI Generatorは、OpenAPI仕様で書かれたドキュメントをもとに、様々な言語のコードを自動生成できるツールです。

openapi-generator.tech

自動生成されるコードは、

  • ドキュメントに書かれたAPIにリクエストを送り、レスポンスを受け取る「クライアントコード」
  • ドキュメントに書かれたAPI自体を作る「サーバーコード」
  • 等々…

など様々なタイプのコードを作ることが出来る上、

など自動生成できる言語もかなり多いため、おおよその要望に対応できるはずです。

openapi-generator.tech

つまり、

  1. APIを作成
  2. 何らかの方法で、OpenAPI仕様でAPIのドキュメントを作成
  3. OpenAPI仕様のドキュメントにOpenAPI Generatorを使って、対応するコードを自動生成する

が出来れば、「APIからコードを自動生成する」が達成できるということです。

ではここからは、具体的にどうすればよいかを紹介します。

実装

今回は、「Java / Spring Bootで作られたAPIをもとに、そのAPIにアクセスするDartのクライアントコードを自動生成する」ことを目標にします。

なおDartで作ったコードは、Flutter製アプリで使用する想定です。

OpenAPI仕様のドキュメントの自動生成

まずはAPIから、OpenAPI仕様のドキュメントを作る方法です。

Spring Bootであれば、実際に作られたAPIからドキュメントを自動生成することが可能です。

まずは、OpenAPI仕様のドキュメントを自動生成するためにライブラリを追加します。

今回は build.gradle を使用します。

dependencies {
    implementation 'org.springdoc:springdoc-openapi-ui:1.7.0'
}

次に、APIを作っていきます。

今回は非常にシンプルに以下のようにしてみました。

package sample;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

    @GetMapping
    public String sample() {
        return "Hello world!";
    }
}

これで準備は完了です。

実行して、以下のURLにアクセスしてみてください。 (ただしドメインは、自身の実行環境に合わせてください。)

http://localhost/v3/api-docs.yaml

すると、以下のようなYAMLファイルが取得できます。

openapi: 3.0.1
info:
  title: OpenAPI definition
  version: v0
servers:
- url: http://localhost
  description: Generated server url
paths:
  /sample:
    get:
      tags:
      - sample-controller
      operationId: sample
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                type: string
components: {}

なんとこれだけで、OpenAPI仕様のドキュメントが完成しました!

ちなみに、更にドキュメントの精度を上げたい場合は、以下のようにアノテーションを使って情報を補完すると良いでしょう。

package sample;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("sample")
@Tag(name = "サンプルAPI")
public class SampleController {
    @GetMapping
    @Operation(summary = "サンプルエンドポイント")
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "正常に処理が終了した場合",
                    content = @Content(mediaType = "application/text", schema = @Schema(implementation = String.class))
            )
    })
    public String sample() {
        return "Hello world!";
    }
}

こうすれば、ドキュメントも以下のように詳細になります。

openapi: 3.0.1
info:
  title: OpenAPI definition
  version: v0
servers:
- url: http://localhost
  description: Generated server url
paths:
  /sample:
    get:
      tags:
      - サンプルAPI
      summary: サンプルエンドポイント
      operationId: sample
      responses:
        "200":
          description: 正常に処理が終了した場合
          content:
            application/text:
              schema:
                type: string
components: {}

OpenAPI Generatorを使ってDartコードを自動生成する

次に、今作ったドキュメントをもとに、OpenAPI Generatorを使ってコードを自動生成します。

OpenAPI Generatorを使う方法は色々とありますが、今回はシンプルにDockerを使っていきます。

openapi-generator.tech

Dockerを使う場合、必要なのは以下の2つのみです。

  • Dockerを実行できる環境
  • OpenAPI仕様で作ったドキュメント

Dockerが実行できる環境で、ドキュメントが存在するディレクトリにて以下を実行してください。

docker run --rm \
  -v "./:/local" \
  openapitools/openapi-generator-cli:v6.6.0 \
  generate \
  -i /local/openapi.yml \
  -g dart-dio \
  -o /local/generated/dart

なんと、これで終わりです!

具体的には以下のようになっています。

docker run --rm \
  -v "./:/local" \ # 実行するときのディレクトリを、コンテナ内の /local というディレクトリと同期する
  openapitools/openapi-generator-cli:v6.6.0 \ # 指定バージョンのOpenAPI GeneratorのCLIのイメージを取得
  generate \ # 自動生成を開始する
  -i /local/openapi.yml \ # 生成元のAPIドキュメント。ここで指定されているパスは、ホスト環境ではなくコンテナ環境のものであることに注意
  -g dart-dio \ # 生成するコードの種類。今回は、Dartのコード(内部でdioというHTTPクライアントを使用)で作成する
  -o /local/generated/dart # 生成したコードを置く先。ここで指定されているパスは、ホスト環境ではなくコンテナ環境のものであることに注意

実行すると、 ./generated/dart ディレクトリが作られ、そこに自動生成されたコードが存在しているはずです。

これで自動生成は完了です。 お疲れ様でした!

最後に

今回は、Flutter製アプリで使うことを想定して、Dartでの自動生成を行いました。

これは、今回これを試そうと思ったきっかけが「アプリエンジニアの負担を減らす」ことだったからです。

世界的に、バックエンドエンジニアに対してアプリエンジニアは数が少ない傾向にあります。

つまり、「アプリの開発速度を上げる」ためには、「アプリエンジニアの負担を減らす」ことが重要なのです。

今回のコード自動生成を使えば、APIとの接続部分だけとはいえ、アプリエンジニアの負担を減らすことが出来るはずです。

ここでは Dart + dio にてコードを自動生成しましたが、それ以外にも生成に対応するコードの種類はたくさんあります。

ぜひ皆さんも試してみてください!

Embulkを使って、アクセスログを集計してデータベースに挿入する

エキサイト株式会社の中尾です。

Embulkを使って、S3のアクセスログを集計してデータベースに挿入する方法を説明します。

例えばアクセスログから時間帯別のアクセスログの結果をDBに保存したりすることがあると思います。

前提条件は以下の通りです。

  • AWS Athenaを使ってクエリが実行できる
  • nginxのアクセスログがS3に保存されている

AWS Athenaのクエリ

例えば、時間帯別のアクセスログを取得するクエリです。

SELECT
  date_trunc('hour', from_iso8601_timestamp(time) AT TIME ZONE 'UTC') AS hour,
  COUNT(*) AS access_count
FROM
  your_nginx_access_log_table
WHERE
  time between '2023-06-25 00:00:00' and '2023-06-25 23:59.59'
GROUP BY
  hour
ORDER BY
  hour

実行例です。時間ごとに集計されています。 こちらをデータベースに入れます。

hour access_count
2023-06-25 00:00:00 125
2023-06-25 01:00:00 98
2023-06-25 02:00:00 76
2023-06-25 03:00:00 104
2023-06-25 04:00:00 82
2023-06-25 05:00:00 135
2023-06-25 06:00:00 172
2023-06-25 07:00:00 204
2023-06-25 08:00:00 290
2023-06-25 09:00:00 350

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

まずEmbulkのinputプラグインにembulk-input-athenaをいれます。 outputプラグインには例としてembulk-output-postgresqlを入れます。

embulk gem install embulk-input-athena
embulk gem install embulk-output-postgresql

github.com

Embulkの設定

Embulkに使うyamlは以下の通りです。 ポイントは以下です。

  • WITH句を使うとコードが見やすい
  • Liquid Template Languageが使えるので、アクセスキーやデータの取得期間を動的に変えられる
in:
  type: athena
  database: default
  athena_url: {{ env.ATHENA_URL }}
  s3_staging_dir: {{ env.S3_STAGING_DIR }}
  access_key: {{ env.ACCESS_KEY }}
  secret_key: {{ env.SECRET_KEY }}
  query: |
    WITH access_log_hour_count AS (
      -- 時間帯別アクセス数
      SELECT
        date_trunc('hour', from_iso8601_timestamp(time) AT TIME ZONE 'UTC') AS hour,
        COUNT(*) AS access_count
      FROM
        your_nginx_access_log_table
      WHERE
        time between {{ env.START_DATE }} and {{ env.END_DATE }}
      GROUP BY
        hour
      ORDER BY
        hour
    )

    SELECT
        hour,
        access_count
    FROM
        access_log_hour_count

  columns:
    - {name: hour, type: timestamp, format: '%Y-%m-%d %H:%i:%s'}
    - {name: count, type: long}
  null_to_zero: true
#out:
#  type: stdout
out:
  type: postgresql
  host: {{ env.DB_HOST }}
  database: {{ env.DB_DATABASE }}
  user: {{ env.DB_USER }}
  password: {{ env.DB_PASSWORD }}
  table: access_log_hour_count
  mode: truncate_insert
  default_timezone: 'Asia/Tokyo'
  column_options:
    hour: {value_type: string, timestamp_format: '%Y-%m-%d %H:%i:%s'}

実行する例

### 環境変数は適宜入れてください
export START_DATE=
export END_DATE=
export DB_USER=
export DB_DATABASE=
export DB_PASSWORD=
export DB_HOST=
export ATHENA_URL=
export S3_STAGING_DIR=
export ACCESS_KEY=
export SECRET_KEY=

### 環境変数を読み込み実行します。
embulk run access_log_hour_count.yml.liquid

最後に

これでアクセスログがデータベースに入りました。 自前でバッチを作るよりも見やすく、そして管理しやすいですね。 よかったら参考になればと思います。

Figmaの新機能「Variables」でデザインシステムを作る際に使えそうなカラーの管理手法を試してみた

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

はじめに

今回は、先日公開されたFigmaのアップデートで追加された「Variables」を使用して、デザインシステムを作る際に使えそうなカラーの管理手法を3つ試してみましたのでご紹介します。 超新米デザイナーですので、誤りがありましたらご指摘頂けますと幸いです。

なお、Variablesは2023年6月時点でBeta版として提供されている機能であり、今後変更になる可能性があります。

Variablesとは?

Figmaの公式発表では以下のように紹介されています。

Variables in Figma design store reusable values that can be applied to all kinds of design properties and prototyping actions. They help save time and effort when building designs, managing design systems, and creating complex prototyping flows.

日本語で要約すると以下のようになります。

  • 様々な種類のデザインプロパティやプロトタイプアクションに適用する再利用可能な値を格納できる
  • この機能はデザイン作成やデザインシステム管理、複雑なプロトタイピングフローの作成において時間と労力の節約に役立つ

何ができるのか

具体的に何ができるのでしょうか?公式発表では、

  • デザイントークンを作成できる
  • 予めスペーシングを定義しておくことでフレームの切り替えで瞬時に余白が更新されるようにできる
  • 異なる言語間でのテキストの流れをプレビューできる

などの活用例が紹介されています。

カラー管理周りの機能を試してみる

多岐にわたる活用法があるようですが、今回はデザインシステムを作る際に重用しそうなカラーの管理手法に絞って、「カラーの変数化」「セマンティックトークンの作成」「ライトモードとダークモードで自動的にトークンを切り替えさせる設定」の順にご紹介します。

カラーの変数化

まずは単純にカラーの変数化をしてみます。この作業は従来のStylesでも実現可能です。

カラーの変数化もデザイントークンの一種であるプリミティブトークン(カラーコードに値を与えたトークン)と解釈することもできます

Variablesの設定を開く

右サイドバーにあるLocal variables
Variablesは右サイドバーにある「Local variables」から設定することができます。

変数を作成する

Local variablesの設定
設定画面が表示されたら「Create variable」をクリックします。今回はカラーの管理をしたいので、「Color」を選択します。

変数を定義する

カラーの追加
テーブルが表示されるので、Nameにカラーの名称、Valueにカラーコードを加筆していきます。今回は試しに3つのレベルのグレーを設定します。

変数をグルーピングする

グループの作成
同じ種別でバリエーションが存在する場合は、グルーピングすることで管理をしやすくします。定義した変数を選択してから右クリックし、「New group with selection」を選択するとグループの作成ができます。

グループの名称を決定
今回はグレーの中に3つのカラーバリエーションがあるので、グループ名を「Gray」とします。

これで、Gray/200 Gray/400 Gray/600の3つの変数を定義することができました。ここまでの作業は、先述の通りStylesでも同じことができます。

セマンティックトークンの作成

ここからがVariables特有の機能になります。 従来のStylesではStyles同士の紐づけができませんでしたが、Variablesでは定義した要素同士を紐付けることができます。 これにより、カラー変数とセマンティックトークンの紐づけ(使い分け)がFigma内でできるようになりました。

そもそもデザイントークンとは?

デザイントークンとは、デザイン要素の一貫性や統一性を担保するためにデザインシステムで使用されるものです。カラー以外にもフォントなどのタイポグラフィ要素、MarginやPaddingなどの余白要素なども定義されます。

デザイントークンはデザイン要素の役割を明確にすることに大きな意味を持ち、デザインの属人化を防ぎ、誰でもそのプロダクトらしさを導き出せる再現性を持たせるという大きな役割を担うと私は考えています。そしてなりより、デザイナー、エンジニアの枠を超えて組織内での共通言語となり、開発におけるチーム内のコミュニケーションを円滑にすると考えています。

今回ご紹介するセマンティックトークンはデザイントークンの一種であり、汎用的な定義ではなく特定の要素に対して定義されるトークです。

カラー変数とセマンティックトーク
例えば、Black / 900というカラー変数と--text-primaryというデザイントークンを紐付けることにより、「基本のテキストはBlack / 900を使う」というルールが明確になります。

コレクションを作成する

では、実際にセマンティクトークンを定義してみます。

新規コレクションの作成
先程作成したカラーの変数化とは別のものになるので、セマンティックトークン用のコレクションを作成します。 右上のコレクション名をクリックし、「Create collection」を選択します。(コレクション名はTokensとします)

個別のトークンを定義する

デザイントークンを定義
カラー変数の定義と同じくテーブルに定義をしていきます。その際、Valueの値で先に定義しておいたカラー変数との紐づけを行います。

カラー選択ができるように

これで変数化したカラーと紐づけられたセマンティックトークンを使用することができるようになりました。

カラー選択
今までのStylesと同じ要領で呼び出すことができます。

ライトモードとダークモードで自動的にトークンを切り替えさせる設定(おまけ)

トークンの定義次第では、ライトモードとダークモード2種類のデザインが存在する場合に、自動的に適用する内容を切り替えさせることもできます。 今回はデフォルトのカラーテーマがライトモードであるという前提で、ダークモードの場合に切り替えができるように設定する方法をご紹介します。

ダークモード用の内容を定義する

デザイントークンの定義
先程作成したセマンティックトークンのテーブルに列を追加(右上の+ボタンで追加できます)し、ダークモードのカラーを定義します。

適用させる

ライトモードとダークモードの2つのフレーム(適用前)
定義したセトークンを使用して、ライトモードとダークモードそれぞれの背景色を適用したフレームの中でボタンを作ります。当然、この時点ではどちらもデフォルトのライトモード用のトークンの内容が適用されています。

ダークモード用のフレームのLayerプロパティ
ダークモード用のフレームを選択し、Layerプロパティの設定で「Change variable mode」→「Tokens」→「Dark」と選択します。

ライトモードとダークモードの2つのフレーム(適用後)
すると、ダークモード用のフレーム内のボタンの各要素のカラーがDarkの列で定義した内容に差し替えられます。

さいごに

今回はFigmaのアップデートで登場した「Variables」の中でも、デザインシステムを作る際に使えそうなカラーの管理手法3つを試してみました。 今般のアップデートではVariables以外にも様々な機能が追加されているようですので、色々と試してみたいと思います。

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

参考文献

Figma「Guide to variables in Figma」(2023年6月26日閲覧) https://help.figma.com/hc/en-us/articles/15339657135383-Guide-to-variables-in-Figma

使えなくなりつつあるFCMの「以前の HTTP プロトコル」から、小さな変更だけでとりあえずWebPushを送る方法

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

以前この記事で、非推奨なFCMの使い方である「以前の HTTP プロトコル」ではWebPushが送信できなくなっている、というお話をしました。

tech.excite.co.jp

今回は、調査していく中で判明した、とりあえず取ることが出来る一時対応について説明します。

「以前の HTTP プロトコル」でWebPushが送信できなくなった

こちらの記事でも紹介しましたが、FirebaseのPush通知サービスであるFCMにおいて、非推奨な「以前の HTTP プロトコル」で送信したWebPushがエラーを返すようになっています。

tech.excite.co.jp

対応として現在の推奨方法である「FCM HTTP v1 API」に切り替える必要がありますが、その際に以下の調査が必要になるかもしれません。

  • 現在のWebPushの機能を保ったままのWebPush送信・受信方法の調査
  • 現在のユーザを引き継ぐ形で切り替えることが出来るのかの調査

これらの調査や実装に時間がかかれば、その間WebPushがほとんど送信できないということになってしまいます。

移行が完了するまでの一時的な対応でもいいので、とりあえず大きな変更なくWebPushを復活させる方法は無いのでしょうか?

「以前の HTTP プロトコル」から、小さな変更だけでとりあえずWebPushを送る方法

実は、多少の変更は必要になりますが、そこまで大きな変更なく「とりあえずWebPushを送信させる」ことは(少なくとも2023/06現在は)可能です。

FCMのドキュメントに、「以前の HTTP プロトコル」から「FCM HTTP v1 API」への移行方法が載っています。

firebase.google.com

これに従い、以下のように変更します。

送信先URLを「FCM HTTP v1 API」のものに変更する

送信先URLを変更します。

なお、新しい送信先URLには、利用するFirebaseアカウントのプロジェクト名を使用します。

プロジェクト名が sample-project であった場合は、以下のように変更します。

変更前: https://fcm.googleapis.com/fcm/send

変更後: https://fcm.googleapis.com/v1/projects/sample-project/messages:send

認証方法を変更する

WebPush送信時、今まではFirebaseアカウントが発行していたサーバーキーを使っていたと思いますが、これをFirebaseが発行するアクセストークンに切り替えます。

以前のサーバーキーは一度発行されれば固定値として使えていましたが、新しいアクセストークンはセキュリティ強化のため、一定時間で期限切れとなるようになっています。

そのため、動的に生成することが必要です。

基本的にはドキュメントに載っている手順で生成できますが、Javaでアクセストークンを発行する方法については、ドキュメントどおりに行ってもエラーになってしまうので注意が必要です。

ドキュメントでは、以下のようになっています。

private static String getAccessToken() throws IOException {
  GoogleCredentials googleCredentials = GoogleCredentials
          .fromStream(new FileInputStream("service-account.json"))
          .createScoped(Arrays.asList(SCOPES));
  googleCredentials.refreshAccessToken();
  return googleCredentials.getAccessToken().getTokenValue();
}

ですがこれだとエラーになってしまうので、以下のようにしましょう。

private static String getAccessToken() throws IOException {
  GoogleCredentials googleCredentials = GoogleCredentials
          .fromStream(new FileInputStream("service-account.json"))
          .createScoped(Arrays.asList(SCOPES));
  googleCredentials.refresh(); // ここを refresh() にしないとエラーになる
  return googleCredentials.getAccessToken().getTokenValue();
}

また、認証ヘッダの形式も変更します。

変更前: Authorization: key={サーバーキー}

変更後: Authorization: Bearer {アクセストークン}

これで、認証も問題なく出来るようになります。

ペイロードは送れない

ドキュメントでは最後に「送信リクエストのペイロードを更新する」という項目があり、一見書いてあるとおりに変更すればペイロードを送信できるように見えます。

しかし、少なくとも私が調査した限り、どうやってもペイロードを含めて送ることができませんでした。

ただし逆に言えば、 ペイロード無しなら上記の方法でWebPushを送ることが出来る ということです。

とりあえずこれで、ブラウザに「WebPushが来た」ということだけは知らせることが出来るようになりました。

ブラウザから通知データを取得してもらう

もちろん、「WebPushが来た」ことだけわかっても、タイトルやリダイレクト先等が分からなければブラウザが通知を表示することができません。

この方法で一時対応をする場合は、ブラウザがWebPushを受け取ったら、サービスワーカーからAPI等にリクエストしてもらい、通知データを取得してもらうようにするのが良いでしょう。

とりあえず上記の方法を取れば、一時対応ではありますがWebPushを復活させることができます。

最後に

とりあえずWebPushは復活できますが、そもそもこの無理矢理な迂回方法もいつまで使えるかわかりません。

あくまで一時対応・時間稼ぎであることを前提に、早急に正式な「FCM HTTP v1 API」に移行しましょう。

AWS Dev Day 2023 Tokyo 2日目の参加レポート

こんにちは、エキサイト新卒2年目、趣味は自宅鯖(オンプレ)のNOGU(@NOGU_D626🐤)です。
最近はAmazonでNUCがタイムセールで大値下がりしていたので即購入して、Proxmox VEインストールし遊んでいます。

AWS Dev Day 2023 Tokyo」2日目に突撃訪問してきたので各セッションについてお話ししていきたいと思います!

会場の様子などに関してはバックエンドエンジニアの山縣が1日目のレポートにまとめているのでそちらをご覧ください。

tech.excite.co.jp

セッションについて

今回自分が聞いてきたセッションは以下になります。

また各セッションでは録音・録画などは禁止されていますが、写真などの静止画については許可されておりその際に撮影した写真を掲載しています。

セッション名 時間
ゼネラルセッション 6月23日(金) 10:00 - 11:45
レガシーシステム、モダナイズへの道筋 6月23日(金) 12:45 - 13:30
Amazon S3Amazon Cognito・AWS Lambdaのアンチパターンで学ぶセキュリティ・バイ・デザイン 6月23日(金) 13:50 - 14:35
英語で読もう!AWSドキュメント頻出英単語集1900!と、その作り方 6月23日(金) 15:05 - 15:25
Amazon EC2 Mac インスタンスを用いたモバイルアプリ向けの CI/CD でストレスフリーな開発を実現する 6月23日(金) 16:10 - 16:30
セキュリティ/ガバナンス系サービスを使った安全なSandbox環境の作り方 6月23日(金) 17:15 - 18:00

各セッションについてざっくりとまとめていきます。

ゼネラルセッション

オーストラリアの Canva や Channel Nine などの企業で、開発者、ビジネスアナリスト、マネージャーを歴任し、現在は AWS アジア太平洋地域の Developer Relations の責任者であるKris Howard氏とJAWS仙台(東北)支部よりAPN Ambassadorであり、株式会社ビッグツリーテクノロジーコンサルティングマネージャーである熊谷 有輝子さんによる。

Be Confident. To make your Engineering Life Better (開発者としての居場所を考えるを題材)とし、居場所を見つけられない要因として考えられるアンコンシャスバイアス、インポスター症候群などを例に、実体験を取り上げながら居場所を見つける方法を議論していました。

このセッションでの印象は熊谷 有輝子さんの実体験を元にしたスライドがとても興味深かったです。

レガシーシステム、モダナイズへの道筋

このセッションはレガシーシステムを抱えられていて、内製化でレガシーシステムと向き合っていくことに興味を持っている人に向けたセッションでした。レガシーシステムのよくある課題をあげていき、あるべき姿の整理、モダナイズを行なった後のさまざまな課題などについて話されていました。 このセッションでの小さくリリースするというフレーズが記憶に残っています。 小さくリリースすることで成功体験を積んでいき、チーム全体のレベルをあげていくことは非常に大切であると自分も思います。

Amazon S3Amazon Cognito・AWS Lambdaのアンチパターンで学ぶセキュリティ・バイ・デザイン

このセッションでは、S3ファイルアップロードにおけるXSSやCognitoを使用したアプリケーションの認証認可の不備、Lambdaを使用したイベント駆動型アーキテクチャにおける認証情報への攻撃など、一般的に実装しやすいセキュリティの問題点について話していました。 興味のある方は登壇者の方がスライドを実際にアップロードしているのでそれも併せてご覧ください。 www.docswell.com

英語で読もう!AWSドキュメント頻出英単語集1900!と、その作り方

このセッションでは、AWSの公式ドキュメントを英語で読むことの重要性について話されています。日本語のドキュメントも充実していますが、一部のサービスでは日本語訳がないか、不正確な翻訳が存在することもあります。 そういった際に英語のドキュメントをスムーズに読めるようになりたいというニーズから。 AWS公式ドキュメントで頻出する英単語を元に頻出英単語帳を作るまでのアーキテクチャや使用した技術の解説などをしていました。

このセッションでの最も記憶に残っているのが頻出英単語帳の例文でrun実行するという単語の例文が You can run your applications on AWS EC2 instances. AWS EC2インスタンスでアプリケーションが実行できます。という例文からわかるようにしっかりと例文がAWSに関連していて面白いという印象が残っています。 実際に作成された 頻出英単語集1900 についてもwealthnaviさんのTechブログで紹介されているので実際に英語勉強を始めてみてはいかがでしょうか! tech.wealthnavi.com

Amazon EC2 Mac インスタンスを用いたモバイルアプリ向けの CI/CD でストレスフリーな開発を実現する

このセッションでは、Amazon EC2 MacOSインスタンスを用いてモバイルアプリ向けの CI/CD 環境を構築し、パイプラインの実行速度を改善し、パイプライン実行待ちのストレスから解放されることによる開発の質の向上と共に、Amazon EC2 MacOSとローカル環境でのMacと比較してのメリット・デメリット等について話されていました。

セキュリティ/ガバナンス系サービスを使った安全なSandbox環境の作り方

このセッションでは、セキュリティやガバナンス系のサービスを活用し、想定外のコストを防ぎ、安全かつ容易に使える実験環境の作成方法を紹介しつつ、実際に運用方法について話されていました。 また後半ではAssociateレベルの認定取得を応援するキャンペーンの概要についても話されていました。

これで2日目のAWS Dev Day 2023 Tokyoについての体験レポートは終わります。

(ちょっと待った!!!!!)

少しだけ!!採用アナウンスさせてください。

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

カジュアル面談からもOKなので!!お気軽にご連絡頂ければ幸いです。

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