SpringBoot でSQLをテストする(Mybatis)

エキサイト株式会社 メディア事業部エンジニアの中です。

SpringBootでmybatisで発行したSQLのテストのやり方を記載します。

H2というin-memoryデータベースを用意し、schema.sql、data.sqlの初期データを挿入してから、SQLを発行し、正しくデータが取得できているかテストをします。

ユースケース

  • SQLのテストする

前提条件

  • MybatisTestを使う
  • H2を使う

gradleに以下を追加

        testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:2.1.3'
        testImplementation 'com.h2database:h2:1.4.200'

入力

application.yml

  • SQL server を使用するため、ModeをMSSQLServerに設定します。
  • 細かい設定は公式サイトから参照してください。

www.h2database.com

spring:
  datasource:
    initialization-mode: always
    driver-class-name: org.h2.Driver
    url: jdbc:h2:~/test;MODE=MSSQLServer
    username: sa
    password: sa
  h2:
    console:
      enabled: true
mybatis:
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    org.springframework.jdbc: debug
    com.exblog: debug

schema.sql

CREATE TABLE user
(
    user_id  bigint IDENTITY(1,1) PRIMARY KEY,
    name NVARCHAR(20) NOT NULL
);

data.sql

INSERT INTO user VALUES (1, 'nakao');

MapperTestApplication.java

  • h2を起動させるため、テスト用にSpring Applicationの設定を追加します
  • testファイルが置いてあるパッケージに配置してください。
package com.sqlserver.mapper;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MapperTestApplication {

}

UserCustomMapper

  • SQLを実行するinterfaceとftlです。
@Mapper
public interface UserCustomMapper {
    @Lang(FreeMarkerLanguageDriver.class)
    @Select("find_user.ftl")
    UserModel getUser(
            @Param("user_id") Long userId
    );
}

find_user.ftl

select
  *
from
  user
where
  user_id = <@p name="user_id"/>

入力例

テストコードです

@ExtendWith(SpringExtension.class)
@MybatisTest
class UserCustomMapperTest {
    @Autowired
    private UserCustomMapper userCustomMapper;

    @Test
    public void findByStateTest() {
        final UserModel user = userCustomMapper.getUser(1L);
        assertTrue(user.getUserId().equals(1L));
        assertTrue(user.getName().equals("nakao"));
    }
}

出力例 デバッグモードを使うと発行したSQLの詳細が出力されます。

2021-04-29 23:39:04.683 DEBUG 4206 --- [    Test worker] c.e.mapper.UserCustomMapper.getUser  : ==>  Preparing: select * from user where user_id = ?
2021-04-29 23:39:04.705 DEBUG 4206 --- [    Test worker] c.e.mapper.UserCustomMapper.getUser  : ==> Parameters: 1(Long)
2021-04-29 23:39:04.725 DEBUG 4206 --- [    Test worker] c.e.mapper.UserCustomMapper.getUser  : <==      Total: 1
BUILD SUCCESSFUL in 3s

SQLのテストは大変

in-memoryデータベースを用意しましたが、事前準備からテスト実施まで大変ですよね。。。 Readのテストなら事前データの投入ですみますが、CUDだったらもっと大変になります。 それはまた別の機会に記載させていただけたらと思います。

MybatisTestについて、もっと知りたい方は以下を参考にしてください。

mybatis.org

JSR303の@ValidとSpringBootの@Validatedの違い

エキサイト株式会社メディア開発の佐々木です。

Javaには、JSR303 Bean Validationという私の好きなValidation仕様があります。@Validをつければ、クラスのフィールドに@Empty@Min(1)のようなアノテーションをつけるだけで、値のバリデーションが可能です。Springにはこれを拡張した@Validatedがあります。この使い方について軽く触れます。

@Validはなに?

これはJavaの標準仕様で、クラスのフィールドにアノテーションをつけて、所定メソッドを実行するとバリデーションを行ってくれます。

@Data
class Form {
   @NotEmpty
   private String name;
 }


Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); // validatorのインスタンス生成
Form form = new Form(); // データクラスのインスタンス
Set<ConstraintViolation<Form>> validate = validator.validate(form); // バリデーションの実行

フィールドに付けられたアノテーションの情報を元にバリデーションをしてくれます。フィールドの仕様とバリデーションがセットになっているので視認性もよく使い勝手はいいです。ただ、使い勝手が良すぎるせいで、ある程度約束を守ってもらわないと設定がバラバラになってしまうという問題もあります。

@Validatedはなに?

カンタンにいうと、@Validを拡張したものが、SpringFramework内にあります。拡張の主なポイントはグループの指定です。

グループの指定とは?

とあるユースケースで、バリデーションを指定したいフィールドを絞りたいことがあるかと思います。1つのデータクラスにリクエストパラメータを入れるのですが、入力フォームが多段になっていたり、簡易フォームと詳細フォームでわかれていたりとあると思います。そういうユースケースに効果を発揮します。

実装

下記のような実装があります。simpleのエンドポイントは、 idnameは必須、detailの方のエンドポイントは、simpleのエンドポイントに加えて、agenickNameが必須になります。groupsという属性を定義することによって、バリデーションをどこまで行うかの識別を行っています。

@RestController
@RequestMapping("valid")
public class ValidController {

    @GetMapping("simple")
    public Form simple(@Validated(value = {SimpleForm.class}) Form form) {
        return form;
    }

    @GetMapping("detail")
    public Form detail(@Validated(value = {SimpleForm.class, DetailForm.class}) Form form) {
        return form;
    }

    @Data
    static class Form {
        @NotNull(groups = {SimpleForm.class})
        private Integer id;
        @NotBlank(groups = {SimpleForm.class})
        private String name;

        @NotNull(groups = {DetailForm.class})
        private Integer age;
        @NotBlank(groups = {DetailForm.class})
        private String nickName;

    }
}

simpleのエンドポイント

@Validated(value = {SimpleForm.class}) Form form が引数に定義されることによって、Formクラスのフィールドにgroups = {SimpleForm.class}アノテーションが付与されているところだけバリデーションが実行されます。

$ curl "http://localhost:8080/valid/simple?id=1&name=sample"

{"id":1,"name":"sample","age":null,"nickName":null}

detailのエンドポイント

simpleのエンドポイントをdetailに変えて、クエリパラメータはそのままに実行してみます。

$ curl "http://localhost:8080/valid/detail?id=1&name=sample"

{"timestamp":"2021-04-29T15:17:56.903+00:00","status":400,"error":"Bad Request","trace":"org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 2 errors\nField error in object 'form' on field 'age': rejected value [null]; codes [NotNull.form.age,NotNull.age,NotNull.java.lang.Integer,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [form.age,age]; arguments []; default message [age]]; default message [null は許可されていません] ....

上記のようにエラーがバリデーションエラーが発生しています。これは先程のsimpleのエンドポイントとは異なるバリデーション設定がされています。@Validated(value = {SimpleForm.class, DetailForm.class}) Form formのバリデーション設定で、DetailForm.classが追加されています。こちらが追加されている為、 Formクラスに定義してあるとおり、agenickName の定義が必須になっています。

さいごに

@Validatedについてカンタンに説明させていただきました。気をつけねばならないのが、 groupsをつけると@Validは動作しなくなるというのがあります。ただ、@ValidatedはSpring依存になってしまうので、早いところ共通仕様をだしてほしいところであります

AWS RDSのダウンサイジングとオートスケーリング

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

今回はAWS上のRDSのダウンサイジングに加えAurora Auto Scalingを設定しコストダウンを測った話と、その際の注意点や挙動についてご紹介させていただきます。

また、今回はGUI上での設定についてお話させていただきます。

DBインスタンスの追加・削除

現行で既に動いてるRDSを触る場合、インスタンスサイズを変更すると変更中はそのインスタンスでリクエストを捌くことはできません。

そのため、一時的に大きめのインスタンスを先に作って作業すると良いです。

f:id:excite-at-ma:20210428152508p:plain 1段階〜2段階上のサイズにしておくと安心です。

クラスターの設定に合わせてリーダーを作ってくれるため、一時的なリーダーの名前を決めて設定します。

f:id:excite-at-ma:20210428153802p:plain 便利なことに、自動で異なるAZ配置に振り分けてくれます。

書き込みインスタンスを変える時は、事前にフェイルオーバーで読み取りインスタンスに降格させてインスタンスの変更をすると管理しやすいです。

f:id:excite-at-ma:20210428152957p:plain フェイルオーバーの優先順位は各インスタンスの追加設定から変更できます。

オートスケールの設定

オートスケールは、オートスケールを設定したいクラスターを選択し、[アクション]=>[レプリカのAuto Scallingの追加]で設定できます。

f:id:excite-at-ma:20210428154135p:plain

指定のターゲット値を基にインスタンスの台数が増減していきます。 CPU以外にも、DBの接続数を基にスケールイン・アウトを行うことができます。

今回のプロジェクトの場合、比較的早くアクセスが増えてしばらく高負荷の状態が続くケースが多かったです。

そのため、時間はデフォルトの300秒ではなくスケールイン60秒、スケールダウンは少し長めに300秒に設定しています

f:id:excite-at-ma:20210427164118p:plain 実際にオートスケールを設定すると上記のようにapplication-autoscalling-** といったインスタンスが増えます。

アクセスが増えたことにより、2台のインスタンスが立ち上がり大量の負荷で落ちないようになっています。

また、オートスケールの設定自体はクラスタの[ログとイベント]内の[AutoScalling ポリシー]からいつでも変更可能です。

f:id:excite-at-ma:20210428154716p:plain

障害時のフェイルオーバーに対応する

障害などでフェイルオーバーが起きた際、書き込みのインスタンスと読み込みのインスタンスが入れ替わってしまうことがあります。

f:id:excite-at-ma:20210427165908p:plain
入れ替わりの例
上記のように青が読み込み、赤が書き込みだったものが、ある時を境にフェイルオーバーが起きて入れ替わっています。

こうなると書き込みと読み込みのサイズに差があるとリクエストが捌き切れずにパンクしてしまう恐れがあります。

そのため、基本的にはmasterとreadのインスタンスサイズは同じにしておくのが望ましいです。

終わりに

今回のスケールダウンでRDSコストを当初の半分以下にまで抑えることができました。

ただしあまりにもギリギリに調整しすぎると、万が一の障害が起こった際や想定外のアクセスがきた場合に対処が難しくなるため、サイズにはある程度余裕は持たせましょう。

また、サービスにもよりますがオートスケールのターゲット値も50~60%程度が一番汎用的に使えそうです。

可用性を保ったまま低コストの運用を実現するため、サービスに合わせてうまく負荷分散できるようにチューニングしていくことが重要です。

AWSとGitHub Actionsでデプロイをカイゼン

エキサイトのみーです。

アプリケーションのリリースサイクルを速めるためにも、デプロイの改善は重要な要素の1つだと考えています。
今回は、アプリケーションのコンテナ化に際してデプロイをどのように改善させたのか、について紹介します。

これまで

オンプレにおける従来のデプロイは非常に面倒で、ミスしてくれと言わんばかりのものでした。箇条書きにすると、

  • GitHubでPull Requestして、
  • レビューして、
  • mainブランチにマージして、
  • 踏み台サーバにログインして、
  • デプロイサーバにログインして、
  • シェルスクリプト(rsync)を叩く

というような手順。酷いときは、mainブランチにマージしたけどデプロイを忘れる、なんてことも。
mainブランチと本番環境にズレが生じる、みたいなあり得ない状況も度々発生。
そもそもの作業が億劫なのでデプロイ頻度が下がっていく、となるのも当然の結果でした。

アプリケーションをコンテナ化してそれをデプロイするとなると、同じようなやり方では破綻してしまいます。
ということで、より簡単で、より安全なデプロイ方法を検討することになったわけです。

コンテナ化後

試行錯誤の結果、今では以下のような手順に落ち着いています。

  • GitHubでPull Requestして、
  • レビューして、
  • mainブランチにマージされたら、GitHub Actionsで自動デプロイ

これにより、デプロイ作業がGitHubだけでほぼ完結するようになりました。手数も減って、デプロイサーバの管理も不要になって、まさに一石二鳥。
さらにBlue/Greenデプロイも取り入れたことで、より安全にアプリケーションをデプロイできるようになりました。
ここまで来てしまうと、もう昔には戻れません。

構成

大まかな全体構成は以下のような図になっています。今回は左半分の話になりますね。
特別なことは何もしていません。コントロールプレーンはECS、データプレーンはFargate、イメージはECRで管理しています。

f:id:ex-mii:20210426182208p:plain

GitHub Actions

アクションの作成は難しくはないので、全て自作しても良いとは思います。
が、今回はAWSのサービスをフル活用しているので、AWSが公開しているアクションを有効活用することができました。

# GitHub Secretsに登録したアクセスキーなどをセット
- name: Configure AWS credentials
    id: configure-credentials
    uses: aws-actions/configure-aws-credentials@v1
    with:
        aws-access-key-id: ${{ secrets.PROD_AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.PROD_AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ env.AWS_REGION }}

# ECRにログイン
- name: Login to Amazon ECR
    id: login-ecr
    uses: aws-actions/amazon-ecr-login@v1

# イメージをビルドして、ECRへPush
- name: Build, tag, and push image to Amazon ECR
    id: build-image
    env:
        DOCKER_BUILDKIT: 1
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        IMAGE_TAG: ${{ github.sha }}
    run: |
        docker build -t $ECR_REGISTRY/${{ env.ECR_REPOSITORY }}:$IMAGE_TAG .
        docker push $ECR_REGISTRY/${{ env.ECR_REPOSITORY }}:$IMAGE_TAG
        echo "::set-output name=image::$ECR_REGISTRY/${{ env.ECR_REPOSITORY }}:$IMAGE_TAG"

# タスク定義に、先ほどPushしたイメージを反映
- name: Render Amazon ECS task definition
    id: render-web-container
    uses: aws-actions/amazon-ecs-render-task-definition@v1
    with:
        task-definition: task-definition.json
        container-name: app
        image: ${{ steps.build-image.outputs.image }}

# CodeDeployを使用してECSにデプロイ
- name: Deploy to Amazon ECS
    id: deploy
    uses: aws-actions/amazon-ecs-deploy-task-definition@v1
    with:
        task-definition: ${{ steps.render-web-container.outputs.task-definition }}
        service: ${{ env.ECS_SERVICE_NAME }}
        cluster: ${{ env.ECS_CLUSTER_NAME }} 
        wait-for-service-stability: false
        codedeploy-appspec: appspec.yaml
        codedeploy-application: ${{ env.CODEDEPLOY_APPLICATION }}
        codedeploy-deployment-group: ${{ env.CODEDEPLOY_DEPLOYMENT_GROUP }}

aws-actions/amazon-ecs-deploy-task-definitionは、Blue/Greenデプロイにも対応しています。事前にCodeDeployアプリケーションやデプロイメントグループを作成しておくだけで、簡単にBlue/Greenデプロイができるようになります。

このCodeDeployを使用したBlue/Greenデプロイが、私たちに最高にクールな体験を与えてくれているのですが、それはまた別の機会に。

残課題

いろいろと改善できたものの、まだまだ課題は山積みです。

GitHub Actionsで使用するIAMユーザ

GitHub ActionsのワークフローからAWSリソースへアクセスするため、事前にIAMユーザを作成してアクセスキーをGitHubのSecretsに登録しておく必要があります。

IAMユーザには必要な権限のみを付与しているものの、現在はアクセスキーのローテーションをしていないのでセキュリティ的には微妙です。
万が一に備えて、ローテーションの自動化等を検討しておくべきかと思われます。

Pull Requestへのコメントでデプロイ

開発中はもっと簡単にデプロイしたいものです。
そこで、テスト環境などはPull Requestにコメントすることでデプロイされるよう、GitHub Actionsのワークフローを設定しています。

on:
    # コメントが作成・編集されたときに発火
    issue_comment:
        types: [created, edited]

jobs:
    deploy:
        # Pull Request内のコメントが対象、且つ、コメントの先頭に「/test」と入力された場合のみ
        if: contains(github.event.comment.html_url, '/pull/') && startsWith(github.event.comment.body, '/test')

/testとコメントすればテスト環境へデプロイされちゃいます。すごくお手軽。
ただし、どのPull Requestからもデプロイできてしまうという問題も。チーム開発時は事前にルールを決めておくのが良さそうです。

CodePipelineは使わないの?

GitHubだけで完結したかった、ので使っていません。
また、テスト環境へのデプロイを手軽にやりたかったということもあり、GitHub Actionsのほうが適していると判断しました。

おわりに

特に目新しいことはしておらず、公式ドキュメントなどに記載されていることを愚直に実践しただけに過ぎません。
ですが、普通のことを普通に実行するだけで多くの恩恵を得られたのも事実です。カイゼンのポイントはそこかしこに眠っているはず。

クラウドへの移行は大変な作業ですが、クラウドのメリットを活かした構築をすることで、生産性の向上に大きく寄与できると思っています。
この記事が、クラウド移行を検討されている方にとって少しでも参考になれば幸いです。

MySQLにおける、複合Indexを貼る際の良い順番とは

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

MySQLにおいてIndexは、Selectクエリのパフォーマンス向上のために必要不可欠な要素です。 1Indexに対して1カラムを付けるということであればいいですが、複数カラムを付ける場合、付ける順番によってパフォーマンスが変わってきます。

ここでは、どのような順番で付けるのが良いかについて1つの案を提示します。

Indexとカーディナリティ

Indexを貼る際には、カーディナリティを考える必要があります。 例えば、以下のようなArticle(記事)テーブルがあるとします。

id code title category publish_date active
1 aaa 子育て記事A child 2021-01-01 00:00:00 1
2 bbb 子育て記事B child 2021-01-02 00:00:00 0
3 ccc 子育て記事C child 2021-01-03 00:00:00 0
4 aaa くらし記事A lifestyle 2021-01-02 00:00:00 1
5 bbb くらし記事B lifestyle 2021-01-05 00:00:00 1
6 aaa ビューティ記事A beauty 2021-01-03 00:00:00 0
7 bbb ビューティ記事B beauty 2021-01-04 00:00:00 1

また、以下の条件だとします。

  • id が主キーでAuto Increment
  • code (記事コード)と category (記事のカテゴリ)で複合ユニークなデータ
  • active は0/1のどちらかが入る

active カラムは0と1のどちらかしか無いため、よほど値に偏りがあったりしないとそこまでレコードを絞り込むことが出来ません。 そのような状態を、「カーディナリティが低い」と言います。

一方で id は主キーであり、すなわちユニークなデータなので、 id を指定すれば大きく絞り込むことが可能です。 そのような状態を、「カーディナリティが高い」と言います。

以上のことから、一般的にIndexはカーディナリティが高いカラムに貼るほうが効果が高いため、そのようなカラムに貼るべきです。

複合Indexとカーディナリティ

1Indexに1カラムの場合はもちろんですが、1Indexに複数カラムが付く場合も、カーディナリティが高い順番でIndexを付けるべきだと言われています。

たとえば今回の場合、 code (記事コード)と category (記事のカテゴリ)で複合Indexを作るとしましょう。 上記の例ではそこまで種類に違いはありませんが、これから記事数が増えていくにつれ、カテゴリ数はそこまで増えず、記事コード数は増えていくことが予想されます。 であれば、カーディナリティで言えば記事コードが高く、カテゴリが低いということになるので、順番としては

  1. code
  2. category

という順番で付けるのが妥当そうな気もします。

ですが実は、そう単純なものではありません。

複合Indexの場合、必ずしもカーディナリティ順でなくても良い

今回のテーブルの場合、 codecategory は複合ユニークです。 すなわち、 code だけでは一意にレコードを絞り込むことが出来ないため、 code だけを条件としてSelectクエリが投げられることはあまりないと考えられます。

一方で、記事をカテゴリ別に取得するというケースは十分に考えられます。 すなわち、 category でSelectすることは考えられるケースです。

そのような時、

  1. code
  2. category

の順番であればこのIndexが当たらない可能性があるため、場合によっては別途Indexを用意する必要がありますが、

  1. category
  2. code

の順番であればIndexが当たるため、新たにIndexを用意する必要がありません。

Indexは、作りすぎると逆にパフォーマンスが落ちてしまう原因にもなりえます。 今回はかなりシンプルなケースだったので category のIndexくらい作れば良いかもしれませんが、これがカラム数の多いテーブルになった場合などは問題になる可能性が考えられます。

もちろん、今回で言う active のレベルでカーディナリティが低いカラムを1番目に入れ始めるとまた話は別だったり、要求されるパフォーマンス次第かもしれませんが、 category くらいであればカーディナリティにそう目くじらを立てるほど厳しくする必要はないでしょう。 それよりも、不要なIndexの作成を防ぐ方を考えても問題ないのではないでしょうか。

まとめ

基本的に、複合カラムのIndexはカーディナリティが高い順につけるのが良いと言われていますが、上記のように必ずしもそれに従っていれば最適であるというわけではありません。 Indexに付く複数のカラムが複合ユニークかどうか、アプリケーションで実際にどのように使われる想定なのか、それらによって柔軟に順番を決めていけるとよりアプリケーションのパフォーマンスを向上させていくことができるでしょう。

「第5回 AI・人工知能 EXPO 春」に行ってきた

こんにちは、エンジニアの藤田です。

少し前になるのですが「第5回 AI・人工知能 EXPO 春」という展示会がビッグサイトで開催されてたので行ってきました。

今回の展示会は同会場、同時刻に「第2回 ブロックチェーン EXPO 春」、「第1回 量子コンピューティング EXPO 春」が開催されていて内容としては盛りだくさんな展示会となっていました。

行く目的は2つ。

  1. AIの流行りの把握とビジネス展開の調査

  2. 量子コンピューターの商用利用の現在地を知る

1についてはこの界隈は時の流れが早いので1年くらいで結構内容が変わってたりするので、断続的にウオッチしないといけないですね。 2についてはあまり情報がないのでとにかくなにかきっかけ作れればくらいの感じで望みました。とくにビジネス展開してる企業の話を聞きたく。あと動作原理を勉強中ということもあり個人的な興味強く。

まずAI系プロダクトについてざっくりまとめ。

エッジAI

増えてる。ラズパイ導入なども市民権を得てきているのか、コスト抑えつつクオリティ担保が可能になってきてる。

動画を扱うプロダクト

カメラでのリアルタイム監視系が数年前よりかなり増えている。やはり自然な流れとして動画系に移行しているのだなと。

ノーコード系

既存のソリューションをノーコードに振ることによって顧客ターゲットシフトや差別化の方向性が垣間見れた。

SaaS系とSI・受託に二極化

SaaS系はプロダクトがしっかりしていて、客単価高く、データ主体なので一度使うとチャーンしないといった王道にうまく乗れている企業が強いというのは他ジャンルのプロダクトと同じ。一方、やはりというか受託メインでやる企業が多いが、それではスケールしないという危機感あり。ただSI除いた受託系スタートアップのほうがユニークで前衛感あって、説明員の本気度も高く、話聞く分には情報量・質ともに得るものが多い(必然的に小さいブースを回ることが多くなる)

総じてこの分野は歴史がある分、目新しさはないが、毎年確実に底上げされてきてるといった感じ。 ファーウェイが一番大きなブース出してるあたり時代の流れを感じます(複数プロダクトの紹介で、内容も安定感あり)

次にブロックチェーンまとめ。 こちらは、正直あまり真剣にみませんでした。ブース区画も小さくてそんなに展示物が多くなかったこともあるかもです。 LINEさんがブース出してました。2年くらい前に発表されてたLINK(LINEの仮想通貨)でLINE経済圏作る構想があったことを思い出し、説明していただいた方に聞いてみたら、もうすでにLINEアプリ内で展開中とのこと、LINEをあまり積極的に使ってるほうじゃないのでこのあたりの情報に疎かったと反省。ただ、LINEのユーザー規模をもってしても独自通貨の流通は厳しいらしく、これからだとおっしゃってました(現時点で独立したアプリとなっていてLINEにがっつり組み込まれてる感がないので、おそらく仮想通貨プロダクトのLINE内での位置付けにも関係してるのかもなと思ったり) いづれにしてもLINEは壮大な実験場。

最後に量子コンピューティングまとめ。 こちらもブース区画はそれほど大きくなくて、展示物も目を引くような目玉プロダクトがないように見えた。 少なくとも日本ではまだまだビジネスとして成立させるのが難しい段階なんだなという実感。利用コストも相当なものだそうで、資本力のある企業中心にPoCを回してる。

ハードウエアレベルの量子コンピューターエミュレーターってのがいくつかあって、そういうジャンルがあるのだなと勉強になった。 私のメイン目的はソフトウエア中心にビジネス展開されてるBlueqatさんの話を聞くこと。量子コンピューター関連の独立系企業としては草分け的な存在と勝手に思っている。量子活用というと、古典コンピューターでは組み合わせ爆発による計算困難な創薬や配送経路問題などの解決ってイメージが強いけど、実際は最適化を目的としたさまざまなソリューションを展開していてたいへん興味深かった 。情報検索やレコメンデーション、生成系なども量子の範囲だとのこと。既存の機械学習アルゴリズムを量子に置きかえるという感じ。今より数段使える量子コンピューターが出てきてコストも下がってきたら、既存の知的リソースが流用可能ということで一気に導入が進むんじゃないかと想像する。それがいつになるのかって話ですが、このあと日を改めて伺った話だと日本は相当遅れてるみたい。確かに海外の情報探ってるともうあと3年くらいで来るんじゃないかと思うようになってたので、その肌感は合ってたかもしれない。好奇心とかの段階はすでに終わっていて、エンジニアとしての個人としても、日本の行く末に対しても潮目を逃すのではないかという漠然とした危機感と焦りが残る。

最後に。

こういった展示会には時々足を運んでます。まあ、一回で得られる情報なんてたかが知れてるとはいえ、一度に数百社のプロダクトが展示され俯瞰できるのは魅力で、その時の流行りがなんとなく見えるのと、スタートアップや社内の前衛的プロダクトで真剣勝負している方の生の声を聞くことができる貴重な機会だからです。一社一社アポとって話聞くなどかかなり無理なのでほんと貴重な機会です。コロナ禍でオンライン開催が増えているので直接会話できる機会が減るのはちょっと残念ですけどね。今回は感想程度のことしか書けてないけど、こういう地道な情報収集から得られた知見を体系化していって自社の価値創造に生かせていければなと強く思いますし、それが大きなミッションでもあります。

GithubのPullRequestのコメントにラベルを書くようにして生産性を少し改善する

エキサイト株式会社メディア開発の佐々木です。

メディア開発では、github.comを使ってソースコード管理を行っています。人数やプロジェクトが多くなってくるとPullRequestのコメントの書き方がバラバラになってくるので、生産性を確保する為に下記のようなラベルっぽいものを書くことを推奨する方針としています。

[MUST]… (Must)

必ず直してほしいところをコメントで記載します。どう直してほしいのか具体例をつけて書くことを推奨します。メンテしづらくなったり、パフォーマンスが著しく悪かったり、色々な観点がありますが、その観点がコミュニケーションでは大事なので、その点を書くようにします。

(例)
[MUST]
この実装はパフォーマンスがよくないとおもうので、 `xxxx` を使った書き方になおしてほしいです

GithubのSuggestion機能

明確な修正提案は、GithubのSuggestion機能を使ってもOK。

f:id:earu:20210425223421p:plain

複数行にまたがる場合は、cmd + 行数で範囲選択ができるので、それをやってからコメントすると作ることができます。

[IMO] (In my opinion)

自分ならこう直すみたいな提案型のコメントになります。具体的にメリット・デメリットも提示しながらコメントできると、チームの実装力が向上するかとおもいます。

(例)
[IMO]
この部分は、こういう書き方もできるとおもいます

[NITS] (nits pick)

細かい指摘等に対するコメントです。直しても直さなくてもいい程度の修正ですが、なるべく直すような方針にしています。

(例)
[NITS]
このコメントは不要な気がします

[ASK] (ask)

実装内容がわからないときとかに使います。

(例)
[ASK]
ここの実装の中身がわからないです

[GOOD] (good)

実装がよい場合にこちらを書いたり、絵文字でアクションすると良いコミュニケーションになので使用します。

(例)
[GOOD]
この書き方いいですね!

[HELP]

プルリクを発行する人が、いい解決法が見つからないときに、これを書いておきます。わからないところや解決したいことを書いておくとレビュアーが知見や解決方法の提案を書きやすくなります。

(例)
[HELP]
ここのいい解決方法が思いつかないです。やりたいことは xxx で、ちょっと冗長になってしまっているのを解消したいです。

対応できなかった指摘

指摘をもらったのに、対応できなかったものに関しては、忘れないようにgithub issueをたててもらうようにしています。コメントからissueをたてられるので、便利です。

f:id:earu:20210425231730p:plain

おわりに

冒頭にも書いたとおり、人数やプロジェクトも増えてくると指摘の仕方やコメントの仕方に関しても、みんなバラバラになりがちで、生産性があがりません。これを是正しようとしてフォーマット等を厳しくすると、これもまた指摘するのが面倒になったり、守られてないプロジェクトなどがでてきて、生産性に寄与しません。現在の部署では最低限のゆるめのフォーマットを使用してある程度はワークしているとおもいます。ガチガチにした方がいいとか いう意見もありますが、生産現場では、生産性の高さが勝負になってくると思います。これからもメンバーのスキルや考え方を見ながら、バランスを意識して開発効率をあげていければと思います。

IntelliJで共同編集 Code With Meを試してみました!

こんにちは、エキサイトの大津です。
以前から私が欲しいと思っていた機能がIntelliJであったので感動して投稿しました!

皆さんは、プログラミングで誰かに聞きたい場合や、教えるときにPCを持ってきて同じ画面を見てやりとりしていたことはありませんか?
そして相手のキーボードを打って『こうしたらいいんじゃない?』なんてやりとりをして、自分と違うキーボードや違う配列で戸惑い、自分の使っているPCで共同編集ができたらなんて楽なんだろうと。

最近、IntelliJのプラグインで『Code With Me』というものを知ってこれだ!となりました。

使い方も簡単で、まずはCode With Meをインストールします。

インストールすると、ツールバーに共有マークのような物が表示されますので、そこからURLを発行します。

f:id:excite-otsu:20210425194752p:plain
共有URLを発行する

あとはプロジェクトを共有したい人にURLをメッセージツールなどで共有します。
そしてアクセスしてきたときに承認してあげるだけです!

f:id:excite-otsu:20210425195047p:plain
共同編集中

わかりづらいですがカーソルが2つあるのが分かりますか? 同じファイルを編集するだけではなくそれぞれのファイルを編集しても構いません。

こちら、通話やビデオ通話機能、チャット機能や挙手などもついてます。

f:id:excite-otsu:20210425195301p:plain
ビデを通話中

環境に依存するかと思いますが通話の音質もビデオの遅延も少なかったです。
リモートワーク盛んなこのご時世、4月からの新卒入社の社員へのフォローやチーム開発での意識統一などにかなり役に立ちそうだと思いました!

※補足ですが8人程度で同一ファイルを編集したときは、補完機能などがうまく動かなかったのです。今後の発展に期待です。

では!

輪読会の開催が50回を超えました😊🎉

エキサイトのあはれんです。 2020年2月から社内の輪読会を開始して、2021年4月8日で開催回数が50回になりました。😊🎉 先週9冊目が読み終わり、次回から10冊目突入になります!🚀 今回は、社内で実施している輪読会について書きたいと思います。

輪読会の進め方

進めた方に関しては、輪読会の発起人である @_ohshige さんがすでにブログ記事で紹介していますので、それを引用させていただきます。

  1. 課題本を決める
  2. 1回の輪読会で取り扱える程度のまとまりの章で分割し、それぞれの担当を決める
  3. 輪読会当日までに全員がその範囲を読んでくる
  4. 輪読会当日は担当者がファシリテーターとなり良かった点や疑問点を提示して議論する
  5. 今の課題本が終わりに近づいたら次の課題本を決める

担当者が提示するものとして、章毎に以下のようなメモを用意しています。 担当者が、あらかじめエモポイント(良かった点)、気になること(疑問点)を書いてきて、 当日はメモと本を眺めながら議論し、その都度メモに追加していきます。

f:id:e125731:20210425174047p:plain
【輪読会】ユースケース駆動開発実践ガイド 8章のメモ

このメモは、議論内容のうっかり忘れ防止や、議論したことを振り返るときに役立っています。 その日の会を円滑に進めることが目的なので、見て分かるようにラフな内容になっています。 メモ等の前準備は省エネで押さえ、 その場の議論を大切にしている会なので、ここまで続けられたのかなと思っています。

私的!輪読会で良かったこと

私が輪読会をやって良かったと感じたことを書きたいと思います。 良かったことは以下の3つです。

  1. チームでの共通言語ができる
  2. 技術分野、経歴を超えた意見を聞ける
  3. 本を読み切れる

1. チームの共通言語ができる

お恥ずかしい話ですが、輪読会に参加するまでにドメイン駆動設計に関する知識が無く、 「ドメイン」「ユースケース」「リポジトリ」等の用語が分かっていませんでした。 輪読会でドメイン駆動設計に関する本を読み議論したことで理解でき、 ドメイン駆動設計を意識して開発するときは、より実装に近い議論をすることができるようになりました。 もし輪読会に参加していない世界線の私でしたら、ドメイン駆動設計の説明からになっていたので、 開発スピードを落としてまっていたかもしれません。

チームの技術知識レベルを底上げすることや、共通言語をつくる上で、 輪読会は効果的だと実感しています。

ユビキタス言語は、ドメイン駆動設計をする上でも大事ですもんね! (※ ユビキタス言語とは何か? ユビキタス言語とドメイン駆動設計に関連性については、「ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本」で紹介されています。)

2. 技術分野、経歴を超えた意見を聞ける

輪読会参加メンバーの専門技術(サーバーサイド、iOS、Android)、経歴(2〜8年目)がバラバラでしたので、 それぞれが経験してきた開発をもとにした意見を聞くことができ、 本の内容以上の知識を得ることができました。

3. 本を読み切れる

私は根っから締め切り駆動開発人間なので、締め切りがないとなかなか動くことができません...。 毎週「何章まで読んでくる」という締切を輪読会が提供してくれたおかげで、9冊の本を読めたと思います。 私と同じような締め切り駆動開発人間の方には、輪読会は特におすすめです。

おわりに

社内で開催している輪読会について書かせていただきました。 輪読会は、参加するメンバーや読む本によって方法は様々だと思います。 輪読会の一例として、これから開催する方のお役に少しでもなれたらと思います。

まだ読みたい本はたくさんありますので、これからも輪読会を続けていきたいと思います。🚀

参考 : 輪読会で読んだ本

  1. 『ドメイン駆動設計入門』
  2. 『オブジェクト指向でなぜつくるのか』
  3. 『現場で役立つシステム設計の原則』
  4. 『Clean Architecture』
  5. 『ドメイン駆動設計 モデリング/実装ガイド』
  6. 『事業をエンジニアリングする技術者たち』
  7. 『Design It !』
  8. 『レガシーコードからの脱却』
  9. 『ユースケース駆動開発実践ガイド』
  10. 『体系的に学ぶ 安全なWebアプリケーションの作り方』

MockHttpServletRequestでControllerのテストする(2)

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

Java Spring BootでControllerのJSONの送信と受信のテストをする方法を説明します。

ユースケース

  1. Controllerのテストをする。 2.受け口をPostにする。 3.Jsonパラメータを送信する 4.Jsonパラメータを受信する

題材

コード例

コントローラーです。

    @PostMapping("test")
    public String test(@RequestBody JsonBody jsonBody) {
        return jsonBody.getNaka();
    }

    @Data
    public static class JsonBody{
        private  String naka;
        private  String huga;
    }

入力例

Testクラスです。

    @Test
    public void testJsonParameters() {
        try {
            MockHttpServletRequestBuilder getRequest = MockMvcRequestBuilders.post("/test");
            final String contentAsString = this.mockMvc.perform(getRequest.with(request -> {
                final ObjectMapper objectMapper = new ObjectMapper();
                final Map<String, String> requestParameters = Map.of(
                        "naka","sho",
                        "huga","fuga"
                );
                try {
                    request.setContent(objectMapper.writeValueAsString(requestParameters).getBytes(StandardCharsets.UTF_8));
                    request.setContentType(MediaType.APPLICATION_JSON_VALUE);
                    return request;
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                    throw new RuntimeException();
                }
            }))
                    .andDo(MockMvcResultHandlers.print())
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    .andReturn().getResponse().getContentAsString();

            assertEquals("sho", contentAsString);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

出力例

テスト結果のコンソールです。

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /test
       Parameters = {}
          Headers = [Content-Type:"application/json"]
             Body = <no character encoding set>
    Session Attrs = {}

Handler:
             Type = com.controller.TestController
           Method = com.controller.TestController#test(JsonBody)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"text/plain;charset=ISO-8859-1", Content-Length:"3"]
     Content type = text/plain;charset=ISO-8859-1
             Body = sho
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
Disconnected from the target VM, address: 'localhost:56988', transport: 'socket'
BUILD SUCCESSFUL in 50s
13 actionable tasks: 1 executed, 12 up-to-date

解説

        final ObjectMapper objectMapper = new ObjectMapper();
        final Map<String, String> requestParameters = Map.of(
                "naka","sho",
                "huga","fuga"
        );
        try {
            request.setContent(objectMapper.writeValueAsString(requestParameters).getBytes(StandardCharsets.UTF_8));
            request.setContentType(MediaType.APPLICATION_JSON_VALUE);
            return request;
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new RuntimeException();
        }

Mapでjsonパラメータを作成し、コンテンツにbyteで設定します。 コンテンツタイプは明示的にapplication/jsonに設定します。

出力結果が正しく取れているので jsonでもテストができることがわかりました。

SpringBootでのリクエストの日付処理

エキサイト株式会社 メディア開発の佐々木です。

SpringBootでは、日付処理をある程度アノテーションで処理できるので共有します。

@RestController
@RequestMapping
public class DemoController {

    @GetMapping("/date_format")
    public Form dateFormat(Form form) {
        return form;
    }

    @Data
    static class Form {
        @DateTimeFormat(pattern = "yyyy-MM-dd")   // 入力時の期待フォーマット
        @JsonFormat(pattern = "yyyy/MM/dd")   // 出力時の期待フォーマット
        private LocalDate date;
    }
}

入力時に変換が必要であれば@DateTimeFormatを使用します。pattern = "yyyy-MM-dd" を定義してあげると、 2021-04-23みたいな日付の処理ができます。出力時に変換が必要であれば@JsonFormatを使います。 pattern = "yyyy/MM/dd"を定義してあげると、2021/04/23のように出力ができます。

試しに実行してみます。

$ curl http://localhost:8080/date_format?date=2020-12-12

{"date":"2020/12/12"}

期待どおりになりました。変換処理等も特に必要がないので手軽でいいです。

エキサイトでは、一緒に事業を運営していきたいエンジニアを募集しております。 インターン等も受け入れていますので、ぜひご連絡いただければと思います。

www.wantedly.com

カスタムバリデーション時のnullについて

エキサイト株式会社 メディア事業部エンジニアの中です。

カスタムバリデーションと@NotEmptyをつけてユニットテストする際、nullを設定すると NullPointerExceptionが発生することがあります。

理由は@NotEmptyと@CustomValidateの2回チェックが通るため、 CustomValidate側の方でnull対策をしておかないといけないからです。

ユースケース

データモデル

@Data
@Accessors(chain = true)
public class TestId {
    @NotEmpty
    @ExciteIdValidate
    private String id;
}

カスタムバリデーション

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {ExciteIdValidator.class})
public @interface ExciteIdValidate {

    String message() default "Idを正しく入力してください";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        ExciteIdValidate[] value();
    }
}
public class ExciteIdValidator implements ConstraintValidator<ExciteIdValidate, String> {

    private final static Pattern PATTERN_NG_WORD = Pattern.compile("ngword");

    @Override
    public void initialize(ExciteIdValidate exciteIdValidate) { }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {

        if(PATTERN_NG_WORD.matcher(value).matches()){
            return false;
        }

        return true;
    }
}

入力例

テストコードです

@ExtendWith(MockitoExtension.class)
class TestIdTest {

    private static Validator validator;

    @BeforeAll
    public static void BeforeAll() {
        validator = Validation
                        .buildDefaultValidatorFactory()
                        .getValidator();
    }

    @Test
    @Description("nullチェック")
    void ngNull() {
        final TestIdForm testId = new TestIdForm()
                .setId(null);
        assertFalse(validator.validate(testId).isEmpty());
    }
}

出力例 エラー部分抜粋しています。

Caused by: java.lang.NullPointerException
    at com.form.validate.TestIdValidator.isValid(TestIdValidator.java:27)
    at com.form.validate.TestIdValidator.isValid(TestIdValidator.java:7)
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateSingleConstraint(ConstraintTree.java:180)
    ... 99 more

改善

当たり前のことですが、nullを考慮して実装すれば問題なく通ります。 returnをfalseにしていますが、trueでも最終的な結果はエラーになります。 @NotEmptyでエラー判定されているからです。

public class ExciteIdValidator implements ConstraintValidator<ExciteIdValidate, String> {

    private final static Pattern PATTERN_NG_WORD = Pattern.compile("ngword");

    @Override
    public void initialize(ExciteIdValidate exciteIdValidate) { }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {

        if(value == null){
            return false;
        }
        if(PATTERN_NG_WORD.matcher(value).matches()){
            return false;
        }

        return true;
    }
}

Android の Constraint Layout を複数扱うレイアウトはレンダリングパフォーマンスを下げる

エキサイト株式会社メディア事業部エンジニアの武藤です。

Android の ConstraintLayout を使ってレイアウトを実装する際に、レンダリングパフォーマンスに問題があるケースがあったので紹介します。

問題が起きたケース

開発が進んでレイアウトが複雑になってきたときに、リストページのスクロールにカクつきが出てきました。

その際のレイアウトの簡略版が下記です。

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

    <FrameLayout>

        <androidx.core.widget.NestedScrollView>

            <androidx.constraintlayout.widget.ConstraintLayout>

                <RecyclerView />

            </androidx.constraintlayout.widget.ConstraintLayout>

        </androidx.core.widget.NestedScrollView>

        <androidx.constraintlayout.widget.ConstraintLayout>
            
            <ProgressBar />

        </androidx.constraintlayout.widget.ConstraintLayout>

        <androidx.constraintlayout.widget.ConstraintLayout>
                        
            <TextView
                android:id="@+id/text_error"
                ...
                />

            <Button
                android:id="@+id/button_reload"
                ...
                />
            
        </androidx.constraintlayout.widget.ConstraintLayout>

    </FrameLayout>

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

RecyclerView を使ったリストに、 API との通信中に表示する ProgressBar、通信エラー時にメッセージを出す TextView, 再読み込みをする Button を別々の ConstraintLayout の中で配置していました。

スペックの低い端末でわずかにカクついており、実装中には気づきにくいものでした。

改善したレイアウト

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

    <androidx.constraintlayout.widget.ConstraintLayout>

        <RecyclerView />

        <ProgressBar />

        <androidx.constraintlayout.widget.Group
            app:constraint_referenced_ids="text_error, button_reload" />

        <TextView
            android:id="@+id/text_error"
            ...
            />

        <Button
            android:id="@+id/button_reload"
            ...
            />

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

通信エラー時に表示する TextView, Button は、constraintlayout.widget.Group によってグループ化し、表示・非表示を切り替えるように変更しました。

最終的に ConstraintLayout を1つにまとめたところで改善に至りました。結果から原因の推察になりますが、ConstraintLayout を複数並べることがパフォーマンスに影響を及ぼしていたようです。

この件を調べてみると、いくつか同様の記事が見つかりました。 (もっと早く出会いたかった。。)

既存アプリのレイアウトをConstraintLayoutに書き換えた - ビー鉄のブログ

ConstraintLayoutをネストすると激重になる - Qiita

ConstraintLayout を扱う際は、入れ子や複数の羅列は避けてレイアウトを組むように注意すべきです。

なぜ気づけなかったのか?

実装時に気づけなかったことについて振り返ってみました。

まず起きているカクつき自体が、エミュレータや手元の実機ではわかりにくく環境依存の点があったと思います。実際に発覚したのはstageビルドで他の検証機を使った段階でしたので、どこの修正で影響を及ぼしたのかが追いにくい状況になっていました。

また、経験的にも多少のネストのネストでそこまでパフォーマンスに影響があるケースがなかったので、すぐにレイアウトの問題という発想には至りませんでした。一般的にパフォーマンス低下の要因には、レイアウト以外にも考えられ、複数の通信をする場合に同期的な実装になっていることやデータの処理側の可能性もあり、検討をつけにくくなっていました。

パフォーマンスへの影響には様々な要因が考えられ、問題の解決に時間がかかってしまいました。

レンダリングパフォーマンスを意識した実装

Androidレンダリングパフォーマンスを考えるにあたって、View の階層化は気をつけなければいけない問題の一つです。

これはAndroid の公式ドキュメントでも言及されており、開発者は注意しながらレイアウトの構築をする必要があります。

developer.android.com

レイアウトに特に時間がかかる最も一般的なケースは、View オブジェクトの階層が互いにネストされている場合です。ネストされた各レイアウト オブジェクトがレイアウト ステージにコストを追加します。階層がフラットであればあるほど、レイアウト ステージが完了するまでにかかる時間が短くなります。

公式ドキュメントではレイアウトのネストについて上記のように説明されており、ConstraintLayout に限らず、ネストを避けたレイアウトを組むことが重要です。

ConstraintLayout はネストを回避したレイアウト構築が可能なコンポーネントであり、View に制約を付与することで位置関係を定義します。

今回のケースでは ConstraintLayout を使いつつも、リストとエラー表示系を別々のレイアウトに配置していたことが原因でした。実際には単一の ConstraintLayout で実現でき、誤った使い方が招いた問題でした。

developer.android.com

GPUプロファイルを見てみる

レンダリングのパフォーマンスに問題があるときに、原因の究明を助ける方法が公式ドキュメントで紹介されています。

developer.android.com

今回はGPUプロファイルを使うことで、ヒントを得られました。

developer.android.com

設定方法を紹介します。

Android 端末の開発向けオプションを開いて 「HWUI レンダリングのプロファイル作成」から 「バーとして画面に表示」をクリックします。

HWUI レンダリングのプロファイルの作成
開発者向けオプションの「HWUI レンダリングのプロファイルの作成」の設定

画面上に色付きのグラフが表示されます。 ここでは、改善前のサンプルアプリでGPUプロファイルを見てみます。

GPUプロファイルを使った色付きグラフ
GPUプロファイルを使った色付きグラフ

色の意味は公式ドキュメントで説明されており、それを足がかりに原因を考えてみました。

developer.android.com

図では右側の黄緑と深緑部分が長くなっており、処理に時間を要していることが分かります。

黄緑は「Measurement / Layout(測定 / レイアウト)」を意味しており、ここが長い場合は、レイアウトに原因があることが考えられます。今回のケースでは、これをきっかけにレイアウトの見直しができました。

深緑は「その他」を意味しており、メインスレッドで重い処理が実行されている可能性が考えられます。こちらもリスト面のデータ通信が悪いのかなどの検討に繋がりました。

その他のパフォーマンス調査の方法

公式ドキュメントでは、GPUプロファイル以外にもパフォーマンスの調査方法が紹介されていますが、いくつか試してみてGPUプロファイルが手軽に試せたのが良かったです。

その他の方法についての所感です。

  • Systrace
    • 詳細な情報が確認できる反面、その情報がどういうものなのかを調べる必要があり、扱うのが難しく断念しました。。
  • lint
    • 冗長な実装がある場合にワーニングを表示してくれます。今回のケースではヒントとなる情報は得られませんでしたが、通常の開発時に一度使って無駄がないかを調べるのに有用と感じました。
  • Layout Inspector
    • 視覚的にレイアウトの階層を表示できて、ボトルネックの調査と言うよりは、レイアウト構築時にXMLやプレビューだけではわかりにくい階層を確認する場合には有用に感じました。

まとめ

Android の ConstraintLayout を扱う際の注意点とレンダリングパフォーマンス問題の解決の緒について説明しました。 Layout のネストの注意点は基本的ではありますが、私の開発経験では意識しなくとも問題になるケースがなかったので、解決にたどり着くまでに時間を要しました。 プロダクト開発では段階によって問題が複雑化し、問題の切り分けが難しくなってきます。 そういった問題に対して、GPU プロファイルを確認して原因を調査していくことで、改善につなげられました。

お手軽AWS情報収集のススメ

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

皆さんは、AWSの新情報の収集をしていますか? 必要だとは思いつつも、なかなか出来ていないという方も多いかと思います。 今回は、AWSの新情報をお手軽に手に入れる方法を紹介します。

AWSの新情報

AWSでは、毎日のように様々な新機能が発表されています。 その中には、大きいものから小さいもの、自分に関係するものから現状だとしないものまでありますが、すべての新情報を毎日チェックするのは大変です。 また、日本人など英語圏でない方にとっては、情報によっては最初は英語のみで発表される場合があることもあり、追いにくい一因にもなっているかと思います。 つまり逆に言えば、

  • 様々な新情報の中から特に大きめの話題をピックアップしてくれて
  • 日本語でまとめてくれている

ものがあれば新情報は追いやすくなるのではないでしょうか?

実は、そんな都合の良いブログがあります。

週間AWS

週間AWS は、まさしく上記の条件を満たしてくれるブログです。

f:id:excite-takayuki-miura:20210419134201p:plain
週間AWS

  • AWS公式で
  • 毎週、その週のAWS新情報の中からピックアップした情報を
  • 日本語で、簡単にまとめてくれている

ブログとなっています。

多くの場合、月曜日に先週一週間のトピックをまとめて更新してくれているので、週明けに見るという習慣をつけやすいかと思います。

まとめ

AWSでは、日々新しい機能が公開されています。 面倒な運用作業をマネージドなサービスとして代替してくれるようなものもあれば、適応すればコスト削減になるものもあり、可能な限り早く情報を手に入れることで様々な恩恵を受けることが出来ます。 また、週間AWSの情報を更に自身が携わるサービスに関係するものに絞り込んで、チームメンバーにシェアするのも良いかもしれません。

ぜひ週間AWSを活用していきましょう。

ReflectionTestUtilsで変数に値をセットする

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

Spring Bootでpropertyの値を取得する時、@Valueで取得すると思います。 @Valueは外部から値を注入しているので、UTをする時mockを設定しなければなりません。

ReflectionTestUtilsを使って設定する方法を説明します。

@Service
public class TestServiceImpl implements TestService {
    @Value("${spring.test.hoge}")
    private String hoge;

    @Override
    public String test() {
        return hoge;
    }
}

で取得できると思います。

ユースケース

  • @Valueでpropertyの値を取得している

入力例

テストファイルです。

class TestServiceImplTest {
    @InjectMocks
    private TestServiceImpl testService;

    @Test
    void test() {
        ReflectionTestUtils.setField(testService, "hoge", "naka", String.class);

        Assertions.assertEquals("naka", testService.test());
    }
}

出力例

テスト結果のコンソールです。

Task execution finished ':module:service:test --tests "com.service.TestServiceImplTest.test"'.

解説

ReflectionTestUtils.setField(testService, "hoge", "naka", String.class);
  • プロパティファイル名を埋め込みたいmockのtestService
  • プロパティファイル名を埋め込む変数名hoge
  • プロパティファイル名を埋め込む値naka
  • 埋め込む型String.class

簡単に埋め込むことができました。