ECSで複数のターゲットグループを紐付けるときの注意点

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

AWSをインフラとしている場合、アプリケーションを動かす環境としてECSは選択肢の大きな一つです。 それがWebアプリケーションであれば、ECSとALBのターゲットグループを紐付けることが多いでしょう。

実はECSは、2つ以上のターゲットグループと紐付けることができます。 ですが、1つのみ紐付ける場合と比べると注意点があります。

今回は、その注意点について説明していきます。

なおこちら、2021/11/22現在の情報であり、将来的には変わっている可能性があるのでご注意ください。

ECSとターゲットグループ

ECSは、AWSが提供しているコンテナ稼働のマネージドサービスです。 公式ページには、以下のように説明されています。

Amazon ECS は、フルマネージドコンテナオーケストレーションサービスであり、コンテナ化されたアプリケーションを簡単にデプロイ、管理、およびスケーリングできます。

マネージドであり管理が非常に容易なため、AWSをインフラとしていてアプリケーションをコンテナでデプロイしたい場合は、ECSを使うことが選択肢の大きな一つとなります。 また、提供したいアプリケーションがWebサービスである場合、ECSとALBのターゲットグループを紐付けることによりALB経由でアクセスを流すことができます。

多くの場合、ECSとターゲットグループは 1 : 1 で紐付けると思います。 ただ、例えばinternal(VPC内のみ)とexternal(internet経由)の両方のALBを使用する必要があるなど、2つ以上のターゲットグループをECSに紐付けたいこともありえます。

実はそうした場合、1つのECSに対して2つ以上のターゲットグループを紐付けることができます。

f:id:excite-takayuki-miura:20211122113324p:plain
ターゲットグループが1つの場合

f:id:excite-takayuki-miura:20211122114121p:plain
ターゲットグループが2つの場合

非常に便利な機能なのですが、1つ注意点があります。

2つ以上のターゲットグループを紐付ける注意点

ECSには「オートスケーリング」という機能があります。 これは文字通り、コンテナへのアクセス量や負荷に応じて自動的にコンテナ数を増減してくれる機能であり、例えば時間や情勢によってアクセス量が変わりうるWebサービスなどで非常に有用な機能です。

このオートスケーリングでは、基本的に3つの指標をもとにコンテナの増減数を決定します。

ECSServiceAverageCPUUtilization

平均のCPU使用率が指定した値を超えたらコンテナ数を増やす

ECSServiceAverageMemoryUtilization

平均のメモリ使用率が指定した値を超えたらコンテナ数を増やす

ALBRequestCountPerTarget

ALBからの1コンテナ平均リクエスト数が指定した値を超えたらコンテナ数を増やす

これらはそれぞれサービスの特性に応じて使い分ければよいのですが、実は2つ以上のターゲットグループに紐付けたコンテナでは、最後の ALBRequestCountPerTarget を使用するのは危険です。

f:id:excite-takayuki-miura:20211122114906p:plain
ターゲットグループが1つの場合

f:id:excite-takayuki-miura:20211122125321p:plain
ターゲットグループが2つの場合

選択自体はできますが、実はこちら、片方のターゲットグループしか計算されていません。

計算されている方のターゲットグループにアクセスがあった場合

f:id:excite-takayuki-miura:20211122131831p:plain
計算されている方のターゲットグループにアクセスがあった場合のグラフ

f:id:excite-takayuki-miura:20211122131924p:plain
計算されている方のターゲットグループにアクセスがあった場合のコンテナ数

計算されていない方のターゲットグループにアクセスがあった場合

f:id:excite-takayuki-miura:20211122150139p:plain
計算されていない方のターゲットグループにアクセスがあった場合のグラフ

f:id:excite-takayuki-miura:20211122150210p:plain
計算されていない方のターゲットグループにアクセスがあった場合のコンテナ数

このように、片方のターゲットグループしか計算されないので、基本的に2つ以上のターゲットグループを紐付けたECSでは、 ALBRequestCountPerTarget は使わないほうが良いでしょう。

最後に

こちら、実は昔はそもそも2つ以上のターゲットグループを紐付けたECSでは ALBRequestCountPerTarget を選択すること自体が不可能でした。 それが選べるようになったということは、徐々に改修がされていっているのかもしれません。

こちらは2021/11/22現在の情報ですが、将来的には適切に処理がなされるようになるかもしれないので、気になる方は注意してみると良いでしょう。

SpringBootで対話的インターフェースをSpring Shellで実装する

エキサイト株式会社エンジニアの佐々木です。SpringBootは、Webアプリケーションフレームワークというのが一般的な認識可と思いますが、結構なんでもできたります。今回はマイナーな、インタラクティブCUIアプリケーションを作る、SpringShellの機能についてご紹介します。(https://docs.spring.io/spring-shell/docs/2.0.1.RELEASE/reference/htmlsingle/)

はじめに

運用フェーズで極稀に操作することとかあるとおもいます(あまりしたくはないですが)。そういうときに、Shellが役に立つことがあります。

設定

build.gradle

依存関係を解消するのに、下記のように修正します。

依存関係の解決と、 ./gradlew bootRun で実行したときに標準入力を有効にするオプションを追加します。

dependencies {
    ....
    implementation 'org.springframework.shell:spring-shell-starter:2.0.1.RELEASE'   // これを追加する
    ....
}

bootRun {
    standardInput = System.in   // ./gradlew bootRun で実行したときに、標準入力を有効にする為
}

application.properties

下記を入れないと循環参照エラーで起動しないのを回避するのに追加します。

....
spring.main.allow-circular-references=true
....

コード

ここからはWebアプリケーションと同じようなエンドポイントのようなものを書いていきます。

package com.example.batch.controller;

import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;

@ShellComponent
public class DemoShellController {

    @ShellMethod(value = "足し算をする", key = "sampleAdd" , group = "calc")
    public Integer add(int a, @Max(10) int b, @ShellOption(value = "--optional_c" , defaultValue = "1") int c){
        return a + b + c;
    }
}

このような感じで記述できます。@ShellMedthodでShellのエンドポイントを定義できます。valueアトリビュートは必須設定ですが、その他は任意になります。コマンド名はデフォルトメソッド名ですが、keyをつけることにより変更できます。@MaxでValidationも付与でき、@ShellOptionで引数名変更やデフォルト値を入れることも可能です。デフォルト値を入れないと、必須パラメータ になるので、注意してください。

実行

動作結果としてはこのような感じになります。 補完が効くのも便利です。

shell:>sampleAdd 1 10
12
shell:>sampleAdd 1 11
The following constraints were not met:
        --b int : 10 以下の値にしてください (You passed '11')
shell:>sampleAdd 1 11 10
The following constraints were not met:
        --b int : 10 以下の値にしてください (You passed '11')
shell:>sampleAdd 1 10 10
21
shell:>

f:id:earu:20211122100917g:plain

まとめ

Shellアプリケーションが簡単に実装できたかとおもいます。Java + SpringBoot + Gradleではマルチプロジェクト、モジュラーモノリス構成が簡単に構築できますので、内部の実装はWebアプリケーションで開発した実装を使用することが可能です。SpringBoot依存にはなってしまいますが、サービスが大きくなってきたらマイクロサービスに切り出すでいいかと思っております。

今回は、インタラクティブ(対話的)なShellアプリケーションでしたが、同じソースコードバッチ処理も可能ですので、次回ご紹介いたします。

おわりに

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

カジュアル面談はこちらになります! meety.net

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

以上となります。

DNSの名前衝突に気を付けよう

こんにちは。インフラストラクチャーグループの宝田です。

DNS 運用で気を付けるべき名前衝突問題に出くわしたので、事象の発生経緯と今後の対策を記しておきます。DNS の名前衝突問題とは、組織が内部的に使う Top Level Domain(TLD)と インターネットで利用できる TLD が重複してしまうことにより、DNSの動作が期待するものとは違った動作になることを指します。

経緯

サービスで使用中のとある AWS アカウントで AWS ClientVPN(以降 VPN と記載します) を構築し利用していたところ、VPN を接続している時に Slack の通話ができないという現象が発生しました。他の通話アプリ、例えば Google Meet や Zoom 等は問題なく使用できるが Slack だけ通話機能が一切使えないという状態でした。
とりあえず VPN を接続した状態で Slack チャンネルで /call --test と打って通話機能のテストを試したところ、以下のように音声接続がうまくいっていないことが分かりました。

f:id:excite-takarada:20211118101710p:plain
コールテスト画面

Slack 通話のどのフェーズで失敗しているか把握したいので Slack のサポートに問い合わせてみると下記の方法で通話ログを取得できることを教えてもらいました。

  1. Slack アプリの上部のメニューバーで、「ヘルプ」>「トラブルシューティング」>「再起動してネットワークログを取得」を選択します(Windows 10 の場合は、左上の 3 本の横線をクリックして、「ヘルプ」>「トラブルシューティング」>「再起動してネットワークログを取得」を選択します)

  2. 再起動を求めるポップアップが表示されます。「Slack を再起動する」 をクリックします

  3. デスクトップアプリが自動的に再起動し、「ログの進行中です」というポップアップが表示されます

  4. 調査したい内容を再現します

  5. ロギングを 2 分間実行して、問題がキャプチャされたことを確認してから、ポップアップにある「ロギングを停止」をクリックします

  6. Slack のダウンロード先のフォルダに表示される .zip ファイルを解凍した中にある net.log がネットログになります

ただし、通話ログの見方は公開されていないようなので自ら解析するなら雰囲気で理解する必要があります。今回はサポートにお願いして通話ログの中身を解析してもらいました。
解析してもらったところ、Slack 通話で発生する signal.m1.an1.app.chime.awsnearest-media-region.l.chime.aws の名前解決が失敗していることが分かりました。余談にはなりますが、上記の URL からも分かる通り Slack は Amazon Chime SDK を使っているようです。

aws.amazon.com

ここまでくると、aws ドメインの名前解決が失敗する理由はすぐ分かりました。原因は、VPNDNS 設定でプライベートホストゾーンの名前解決を可能にしており、そのプライベートホストゾーンに aws というホストゾーンが存在しているからでした。つまり、 VPN を繋いだ状態で aws ドメインを名前解決するとプライベートホストゾーンを参照してしまい、そのプライベートホストゾーンには signal.m1.an1.app.chime.awsnearest-media-region.l.chime.aws はレコードとして当然登録されていないので名前解決が失敗していたということです。

上記の事象は、AWS が管理している aws ドメインを内部向けドメインに使用してしまったという名前衝突が根本的な原因となります。

対策

今回は弊社の内部向けで使っているドメインが他組織で管理しているドメインと重複してしまい、名前解決に不具合が起きた結果、外部のサービスが利用できなくなる現象がおきました。

名前衝突の対策としては JPNIC の記事にある通り、ドメインを取得してそのドメインを内部向け DNSドメインとして利用することにしました。これで他組織で管理されているドメインと重複することが原理的になくなります。

内部利用向けのTLDに関しては、インターネットで利用できるグローバルなドメイン名を使用する1

まとめ

DNS の名前衝突の事例について紹介しました。内部向けドメインは組織で取得したものを利用しましょう。
最後に補足として、DNS のサーチリスト2と呼ばれるドメイン名の補完機能を使うと、ローカルネットワークのドメイン名を名前解決するつもりがインターネット上の TLD に対して名前解決してしまうという今回の事例とは反対の現象も起こり得ますので、サーチリストも極力使わないということも合わせて押さえておくといいと思います。

JJUG CCC 2021 Fallに弊社社員が登壇します!

弊社社員がJJUG CCC 2021 Fallで登壇いたします。 お時間のある方はぜひご覧ください!

登壇情報

登壇者: s-nakao@エキサイト株式会社 (@openjdk17) | Twitter

日時: 2021/11/21 10:00〜

場所:Track D

タイトル:エキサイトブログ刷新に向けて序章 - APIを一つに

プロポーザルURL: fortee.jp

JJUG CCC 2021 Fall

開催日時:2021/11/21

開催場所:オンライン開催

公式サイト:

jjug.doorkeeper.jp

公式TwitterJJUG (@JJUG) | Twitter

JavaのRestTemplateでタイムアウトを設定する方法

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

APIにアクセスする際、一定時間までにレスポンスが返ってこなかったらエラーとして処理したい、というのはよくある要望かと思います。 そのために使用するタイムアウト設定について、JavaのRestTemplateで設定する方法を説明します。

タイムアウト

APIなどにネットワーク経由でアクセスする場合、必ずしもレスポンスがすぐに返ってくるとは限りません。 API自体に不具合があったり、アクセスが急増していたり、ネットワークに問題があったりなど、様々な理由でレスポンスが返ってくるまでに時間がかかってしまう可能性があります。

バッチなど、多少時間がかかっても問題がないアプリケーションであれば大丈夫かもしれませんが、Webページ用のサーバであればそうも言っていられないこともあるでしょう。 そのような場合、「指定した一定時間でレスポンスが返ってこなかったらエラーとして処理する」というタイムアウト設定をするのが一般的です。

RestTemplateにおけるタイムアウト設定

RestTemplateでは、以下のようにすればタイムアウト設定ができます。

build.gradle

// バージョンは適宜変更してください
implementation 'org.apache.httpcomponents:httpclient:4.5.13'

タイムアウト設定

import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.util.Arrays;

@Configuration
public class RestConfig {

    @Bean
    public RestTemplate restTemplate() {
        var clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(HttpClientBuilder.create().build());

        // ここでタイムアウトを設定

        // 接続が確立するまでのタイムアウト
        clientHttpRequestFactory.setConnectTimeout(500);

        // コネクションマネージャーからの接続要求のタイムアウト
        clientHttpRequestFactory.setConnectionRequestTimeout(500);

        // ソケットのタイムアウト(パケット間のタイムアウト)
        clientHttpRequestFactory.setReadTimeout(1000);

        return new RestTemplate(clientHttpRequestFactory);
    }
}

終わりに

タイムアウトを設定していないと、思わぬところでWebページの応答速度が大きくなってしまう可能性があります。 Webページの要件ごとにタイムアウト時間は異なってくると思うので、適切な設定をしましょう。

外部公開勉強会を開催しました!

技術的な知見を共有しあうことを目的に、外部公開勉強会を開催しました。

第1回のテーマは「前へ進むための新しい取り組み」でした。

長年サービス運営をしてきたエキサイト社員とiXIT社員が、どのようにして技術的負債に向き合いサービスを成長させていったのか、その成功と失敗について話していただきました。

今後も外部公開勉強会を開催していきますので、見に来ていただけると幸いです。

excite.connpass.com

発表資料

クラウドネイティブ化への第一歩

speakerdeck.com

僕たちがすべきことはリファクタリングなのか

www.slideshare.net

約10年続くスマホゲームアプリのDB負荷と向き合う話

www.slideshare.net

エキサイトブログ刷新に向けて 序章 - APIを一つに

www.slideshare.net

JacksonでXMLをJavaオブジェクトに変換する

はじめに

エキサイト株式会社 21卒 バックエンドエンジニアの山縣です。 既存サービスのリビルドするにあたり、外部APIを呼び出してXMLを取得してJavaオブジェトに変換する処理を書きました。 Jacksonを使用したXMLJavaオブジェクト変換についての記事は少なかったため本記事にて紹介します。

導入

本記事で使用するサンプルのXMLとそれに対応するJavaクラスです。

サンプルで使用したXML

<Response>
    <ItemList>
        <Item id="a123" name="orange"/>
        <Item id="b234" name="lemon"/>
        <Item id="c345" name="apple"/>
    </ItemList>
    
    <ResultList>
        <Result num="1">オレンジ</Result>
        <Result num="2">レモン</Result>
        <Result num="3">リンゴ</Result>
    </ResultList>
</Response>

XMLに対応するJavaのクラス

package com.example.demo.model;

import java.util.List;

import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;
import lombok.Data;

@Data
@JacksonXmlRootElement(localName = "Response")
public class Basket {
    @JacksonXmlProperty(localName = "ItemList")
    @JacksonXmlElementWrapper(useWrapping = false)
    private List<Item> items;

    @JacksonXmlProperty(localName = "ResultList")
    @JacksonXmlElementWrapper(useWrapping = false)
    private List<Result> results;

    @Data
    public static class Item {
        @JacksonXmlProperty(isAttribute = true)
        private String id;
        @JacksonXmlProperty(isAttribute = true)
        private String name;
    }

    @Data
    public static class Result {
        @JacksonXmlProperty(isAttribute = true)
        private Integer num;
        @JacksonXmlText
        private String text;
    }
}

XMLのルートの下からマッピングする

XMLのルートの下からマッピングするには、@JacksonXmlRootElementを付与する必要があります。

@JacksonXmlRootElement(localName = "Response")
public class Basket { ... }

XMLのリストをマッピングする

XMLのリストをマッピングするには、下記2つのアノテーションが必要です。

@JacksonXmlProperty(localName = "ResultList")
@JacksonXmlElementWrapper(useWrapping = false)
private List<Result> results;

@JacksonXmlElementWrapperは、useWrappingの他にlocalNameも設定できます。 このとき、@JacksonXmlPropertyを消して、@JacksonXmlElementWrapper(useWrapping = false, localName = "ResultList") と書くと動作するように見えますが、UnrecognizedPropertyExceptionとなってしまいマッピングすることができませんでした。 そのため、上記のように2つのアノテーションを記述しなくてはなりません。

RestTemplateでXMLJavaオブジェクトの変換をする

最後に、下記のように記述することで、外部APIから取得したXMLJavaオブジェクトの変換を実現することができます。

final ResponseEntity<Basket> response = restTemplate
    .exchange(
        "http://example.com",
        HttpMethod.GET,
        httpEntity,
        Basket.class
);

おわりに

Jacksonを使用したXMLJavaオブジェクト変換についてまとめました。 JSONJavaオブジェクトの記事は数多くありますが、XMLJavaオブジェクトの記事はあまり見当たらなかったので、 XMLの変換で困っている人の助けになれば幸いです!

エキサイトは「DroidKaigi2021」に協賛しました!

エキサイトは「DroidKaigi2021」にSUPPORTERSとして協賛しました。今回は惜しくも弊社から登壇者はいませんでしたが、グループ会社であるRadiotalkから牧山さんが登壇しました。 来年は弊社から登壇者が出るように技術力を切磋琢磨し、向上させていきたいです。

登壇内容

登壇者:牧山

タイトル:Androidエンジニアが1人という不安と向き合う

内容:Androidエンジニア不足が叫ばれる昨今、1つのAndroidアプリに対して1人のエンジニアという状況が増えてきているように感じています。このような状況下では、スキル面・キャリア面での不安を持つ方も多いのではないでしょうか。私もそうでした。私のAndroidエンジニアキャリアもほとんどがAndroidエンジニアが1人での開発を経験しています。  本セッションでは、私の経験を踏まえ、どのようにAndroidエンジニアとしての技術を学び、どのように不安と向き合っているのかを紹介します。

  • Androidエンジニアとしての技術の学び方

 ・キャッチアップの方法

 ・ 優れたソースコードを読む

  • 不安と向き合うために

 ・スコープを広げてソフトウェア開発を学ぶ

 ・サービスへの理解を深めソフトウェア開発に還元する

 ・サービスの未来を見据えた設計/開発

 ・エンジニアリングでサービスを前にすすめる

アーカイブURL:https://www.youtube.com/watch?v=5M0xJLdAcAE

DroidKaigi2021

公式サイト:https://droidkaigi.jp/2021/

公式チャンネル:https://www.youtube.com/c/DroidKaigi

Javaでリトライをする方法

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

アプリケーションコードを書く上で、特定の処理をリトライさせたい場合があります。 今回は、Java / Spring Bootでリトライする方法について説明していきます。

リトライ

アプリケーションコードを書く上で、どうしても失敗する可能性が消しきれない処理というものが存在します。 代表的なものとして「インターネットを介したAPIリクエスト」があり、以下のようにリクエスト元である我々ではどうしようもないエラーが発生し得ます。

  • リクエスト先APIに何かしら不具合が発生した
  • 通信をするためのネットワーク周りで何かしら障害が発生した

特にインターネットはベストエフォートなので、そもそも「100%常にリクエストが成功する」ことは保証していません。 そのため、上記のような場合でも我々が管理するアプリケーションは正しく動作することを考える必要があります。

これを解決する方法の一つが「リトライ」です。 すなわち、1回失敗しても「失敗した」判定はせず、規定回数に達するか成功するまで何度も処理を実行する、というものです。

Java / Spring Bootでのリトライ方法

このリトライ処理は、Java / Spring Boot環境では spring-retry というライブラリを使うことで簡単に実装できます。

build.gradle

必要なライブラリを設定します。 spring-retry だけでなく、 spring-boot-starter-aop も必要なので注意してください。

// バージョンは適宜変更してください
implementation 'org.springframework.retry:spring-retry:1.3.1'

// 設定にはこちらも必要になります
implementation 'org.springframework.boot:spring-boot-starter-aop'

設定ファイル

spring-retry の設定ファイルです。 ここで spring-retry を使えるようにします。

@Configuration
@EnableRetry
public class RetryConfig {}

リトライしたいメソッド

準備が整ったら、後はリトライしたいメソッドに @Retryable というアノテーションをつければリトライするようになります。 この時、 value に投げられたらリトライする例外クラスを、 backoff にリトライする際の挙動を書きます。

今回のサンプルコードでは、

  • SampleExceptionが投げられたらリトライする
  • リトライする際は、500ms空けて実行する
  • それ以外はデフォルトの動作

となっていますが、設定次第で挙動をカスタムできます。

@Retryable(value = {SampleException.class}, backoff = @Backoff(delay = 500))
public String sample() {
    return "Sample";
}

最後に

リトライは、アプリケーションが大きくなっていくと必要になる場面が増えてきます。 もちろん何でもかんでもリトライすればいいわけではないですが、適切に使用することでアプリケーションの信頼性は向上するはずです。

ぜひ使ってみてください。

KUROTEN.の技術スタック

エキサイト株式会社でKUROTEN.の開発している森脇です。

今回はKUROTEN.の技術スタックについてご紹介いたします。

KUROTEN.とは

KUROTEN.(クロテン)は経営管理業務のDXを行い、スピード経営を実現するプラットフォームです。必要なデータを一元管理、経営判断が可能な管理会計のあるべき姿を構築し、経営の意思決定をサポートするSaaSのサービスです。

lp.kuroten.jp

KUROTEN.開発の歴史

KUROTEN.は 2020年3月くらいから開発を開始し2021年6月にβ版のリリースをいたしました、その間いろいろ仕様も変わりシステム構成も変わり、なかなかチャレンジングな開発を行ってきました。

ようやくリリースし構成も決まってきたので、ご紹介したいと思います。

サーバーサイドの技術スタック

項目 ソリューション
言語 Go
フレームワーク Iris
ORマッパー gorp
テストフレームワーク testing
データベース Aurora
ドキュメント生成 swaggo
アーキテクチャ クリーンアーキテクチャ

Go

エキサイトではあまり使っていませんでしたが、サービスの特性上、静的型付け言語がよかったので選定しています、 エンジニア的にも業務で使ってみたかったところも大きいです。

Iris

サーバーサイドは紆余曲折あって、最終的にIrisを選定して使っています。

最初はLambdaで動かしていました。エンドポイントは100を超え、メンテナンス性が悪くパフォーマンスもよくなかったので、ECSで動かすことにしました。
ECSで動かすならフレームワークが必要ということで、選んだのがIrisでした、echoやginも候補でしたが、最速ということもありIrisを選んでいます。

ORマッパー

複雑なSQLを書いているため、ORマッパーの恩恵はあまり受けていません。 select文を書いた時に名前でバインドしたかったので、gorpを選んでいます、今はgormでもできるので、そっちが一般的ですね

swaggo

ドキュメントの生成と、postmanに食わせるswagger.jsonを吐き出してくれるとても便利なやつです。
導入前はpostman用のjsonを手動で書き、apiの追加・変更があるたびに修正しないと行けなかったのですごく大変でした

クリーンアーキテクチャ

当初はLambdaで作っていたこともあり、あまりアーキテクチャを意識した作りになっていませんでした。
Irisに移行した時にmvcにしましたが、機能が増えていくことで複雑化し、メンテナンス性や循環参照の問題も出てきたため、クリーンアーキテクチャの導入を行いました、コードもみやすく役割がはっきりと分かれているので、レビューもしやすくなりました、現在はまだ移行中です

フロントエンドの技術スタック

項目 ソリューション
言語 javascript
フレームワーク nuxtjs
デザインフレームワーク vuetify
テストフレームワーク jest

javascript

typescriptにすればよかったんですが、javascriptで書いてます。vue3に上げるタイミングでtypescriptに移行しようと思っています。

jest

当初はavaで行っていましたが、今はjestに変更しています、カバレッジも100を目指して日々テストコードを書いています。

バッチの技術スタック

項目 ソリューション
言語 python
ORマッパー sqlalchemy
テスト pytest

python

特にフレームワークも使わずに好きに書いています、Lambdaで動かすので、関数を呼ぶシンプルなコードになっています

インフラ

主にAWSを使っています、使っているサービスは一般的なものを中心です。

AWS 説明
ECS frontとapiが動いています
RDS Aurora MySQL
S3 一時的なデータ保存場所で使用しています
AppSync 非同期通信を行うために使用しています
Lambda 主にバッチを動かしています
StepFunction LambdaをStep毎に実行するために使用しています
QuickSight サービス分析で使用しています
WAF セキュリティ関連
Codepipline 検証環境や本番へのデプロイで使用しています。
CodeBuild
CodeDeploy
CloudWatch 監視
terraform インフラコードは全てterraformで書いています
cloudformation Lambda関連はsamを使っているので、cloudformationも使っているます

その他

項目 ソリューション
認証 Auth0
spreadjs

最後に

KUROTEN.はいろんな技術を使って構築されています。
今後も新しい技術をどんどん使い、良いサービス提供したいと思います。

SpringBootで画像を受け取りそのまま表示する方法

エキサイト株式会社エンジニアの佐々木です。小ネタですが、画像を受け取ってそのまま表示する方法のメモです。

はじめに

SpringBootで画像を受け取って表示する簡単なサンプルです。フォームでファイルをアップロードし、それをそのまま表示するだけのデモアプリになります。

HTML

テンプレートエンジンでThymeleafを使用します。普通のHTMLとして記述できます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <form>
      <input type="file" name="file"/>
      <button type="submit" formenctype="multipart/form-data" formmethod="post" formaction="/upload">MultipartFile送信</button>
  </form>
</body>
</html>

サーバ

サーバの方は、コントローラに実装を書いていきます。今回は、Thymeleafを使用するので、 @Controller を使います。

@Controller
@RequestMapping
@RequiredArgsConstructor
public class RootController {

    @GetMapping
    public String index(){
        return "index";
    }

   @ResponseBody   // テンプレートは使用せずに、メソッドで返却したものBodyとして扱う指定
   @PostMapping(value = "upload" , consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Object upload(@RequestPart("file") MultipartFile filePart) throws IOException {   // ファイルの場合は、 `@RequestPart` を使うことを注意
        File file = File.createTempFile(UUID.randomUUID().toString(), filePart.getOriginalFilename());   // ファイル名を取得し、Fileインスタンスを生成
        filePart.transferTo(file.toPath());
        byte[] bytes = Files.readAllBytes(file.toPath()); //ファイルの読み込み

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.parseMediaType(Files.probeContentType(file.toPath())));  // ファイル名からMimeTypeを取得する
        httpHeaders.setContentLength(bytes.length);

        return new HttpEntity<>(bytes, httpHeaders); //レスポンス
    }

}

ファイルアップロードのパラメータに関しては、 @RequestPart を使用します。フォームで送信されたファイルをFiles.readAllBytes()で読み込みます。あとは、レスポンスに入れるだけなので、HttpEntityにデータを入れれば画像の表示ができます。

まとめ

Javaはファイルの扱いが結構面倒な言語なんで、スクリプト言語に比べると面倒に感じるかもしれません。(byte配列への変換とか)。最近ではFilesクラスPathsクラスが便利になってきているので、それほど辛くはないと思います。(以前はxxxxReaderBufferとか色々あって辛かった)

最後に

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

カジュアル面談はこちらになります! meety.net

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

JavaのRestTemplateでgzipを受け取る方法

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

APIなどからのレスポンスは、サイズが小さいに越したことはありません。

レスポンスサイズを小さくする方法の1つに、レスポンスをgzipで圧縮するという方法があります。 API側でレスポンスをgzip圧縮して送信し、受け取り側で解凍して使用するというやり方です。

ですが、実はJavaのRestTemplateは、デフォルトではgzip圧縮したレスポンスを使うことができません。

ここでは、RestTemplateを使ってgzip圧縮されたレスポンスを使用できるようにする方法を説明します。

レスポンスのサイズ

APIなどから受け取るレスポンスのサイズは、小さいに越したことはありません。 小さいと、

  • レスポンス全体の転送完了までの速度が上がる
  • 転送量が減るので、転送量ごとに料金がかかる場合、料金を削減することができる

などの利点があります。

転送量を減らすための方法はいくつかありますが、その一つに「データを圧縮してレスポンスを送信する」という方法があります。 例えば、Nginxでも設定によってレスポンスデータをgzip圧縮することができます。

ただし、RestTemplateを使っている場合、問題があります。 実は、デフォルトのRestTemplateではgzip圧縮されたレスポンスをそのまま使用することができないのです。 では、どうすれば良いのでしょうか。

RestTemplateでgzip圧縮されたレスポンスを使用する方法

gzip圧縮されたレスポンスを使用するには、RestTemplateの定義時に ClientHttpRequestFactory というものを設定する必要があります。

通常RestTemplateを定義する際は

var restTemplate = new RestTemplate();

とすると思いますが、これを

// バージョンは適宜変更してください
implementation 'org.apache.httpcomponents:httpclient:4.5.13'
var clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(HttpClientBuilder.create().build());
var restTemplate = new RestTemplate(clientHttpRequestFactory);

のように、 ClientHttpRequestFactory を設定します。 後は、リクエスト時にヘッダーに Accept-Encodinggzipを設定すれば、(API側がgzip圧縮に対応していれば)gzip圧縮されたレスポンスを受け取り、自動で解凍して使ってくれるようになります。

最後に

デフォルトでgzip解凍をしてくれないというのは少し罠みたいなところもあり、詰まってしまった方もいるのではないでしょうか。 この記事が参考になれば幸いです。

口下手デザイナーがLT発表までにした準備

f:id:KAJIJI_Design:20211027121307p:plain

初めまして!
SaaS事業部でデザイナーをしている、21卒のかじもとです!

社内・社外で発表する機会はあるけど「発表なんてやったことない!無理無理!」と遠慮される方が多いと思います。

私もかなりの口下手ですが、学生時代はデザイン系がゆえプレゼンが多く、膝を震わせながら大勢の前で発表してきました。

何度か積み重ねて口下手流のプレゼンを乗り切る対策をつかんできたので、発表の準備やスライド制作のコツをシェアします!
「これまで発表なんてしたことない…」「人前で話すのはプレッシャーだよ!」と悩む人の手助けになれたら嬉しいです。

発表前に準備するもの

私がプレゼン当日までに必ずやっていることがあります。それは、

  • 発表資料を必ず準備する
  • プレゼンの読み上げ原稿を準備する
  • 発表前に自分のプレゼンを聞き直す

この3つです。一つずつ解説していきます。

発表資料を必ず準備する

その場で実物画面を見せたり、過去の資料を再利用する方もいるかもしれませんが、私は毎回テーマに合わせて資料を準備します。

伝えたいことを整理して不要な情報を削ぎ、見合ったグラフィックに置き換えて発表する内容専門のスライドを用意しています。

そうすることで、発表の完成度を高めることにもつながりますし、作っていくうちに言葉が洗練されていくので、自分のためにも・聞き手のためにも分かりやすいスライドになります🙆‍♀️

「そんなこと言われてもうまくまとめられないよ!」という人向けに、近々スライド制作のコツも書くので、ぜひ参考にしてみてください〜!

プレゼンの読み上げ原稿を準備する

スライドを準備をすれば、原稿もほぼほぼできたようなものです。スライドに合わせて言葉で解説する文章を作るので、イメージとしては紙芝居に近いかもしれませんね。一言一句読み上げられるものや、大まかにメモしたものなど、この作り方は個人の好みでいいと思います。

大切なのは伝えたい内容をきちんと伝えることですので、あまり悩みすぎず自分らしい言葉でまとめられれば大丈夫です👌

また、原稿を作ったら文字量を確認してみてください。下に発表時間とおすすめの文字量を記しました。

スライド制作の文字量

  • 3分プレゼン:900文字
  • 5分プレゼン:1500文字
  • 10分プレゼン:3000文字

発表時間が長くなるほど多少のズレは生じますが、適切なトークスピードで話せる文字量は1分あたり300文字です。この制約を設けることで喋りすぎ・喋らなすぎを防げますよ🙌

発表前に自分のプレゼンを聞き直す

これがいちばんハードル高く感じるかもしれません…自分の声なんて聞きたくない、発表まで時間がない…なんて方が多いはず。

ですが、これをやる・やらないで大きく変わります。準備してきた資料や原稿を客観的に見ることで、制作中気づけなかったミスや、わかりにくい表現が見えてきます。

さらに発表時間が決まっている場合は、内容のボリュームや話す速さを確かめるためにもぜひ聞き直してみてください⏰

私もこれまで、「当日大勢の前で失敗するよりも前もって練習して自信をつける方がマシ…」という思いで、夜な夜な録音して聞き返す作業をしてきました…

可能であれば、自分の声+スライドの遷移を同時に見られるように画面収録をしておくとより分かりやすくなります。

 

出来上がったスライドがこちら

slideshareに上がっているスライドに原稿も書いてあるので、気になった方はぜひダウンロードしてご覧ください〜!

終わりに

オンラインで画面越しに人と繋がる機会が多くなり、人前で話すプレッシャーもだいぶ軽減できるとてもいい時期だと思います。

近々、スライド作りのコツについても記事を書くので、ぜひ皆さんもプレゼンにチャレンジしてみてください〜!

RestTemplateで意図せずURLエンコードさせないための注意点

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

URLには、特殊な意味を持っていたり使えなかったりする文字があり、それらを使いたい場合はURLエンコードを掛ける必要があります。 そのため、とある条件下ではJavaのRestTemplateは自動的にURLエンコードを掛けてくれます。

ですが、場合によってはエンコードされてしまうと問題がある場合もあります。

今回は、RestTemplateで意図せずURLエンコードさせないための注意点を紹介します。

URLエンコードをすると問題があるパターン

URLには、特殊な意味があったり使えない文字があります。 例えば日本語は、使えない文字の1つです。 日本語を使いたい場合は、以下のように変換する必要があります。

// これを
https://サンプル

// こうする
https://%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB

これでURLとして使えるようになるのですが、実はこの % はURLで特殊な意味を持つ文字となっています。 そのため、うっかりもう一回URLエンコードを掛けてしまうと、以下のように更に変換されてしまいます。

// これが
https://%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB

// こうなる
https://%25E3%2582%25B5%25E3%2583%25B3%25E3%2583%2597%25E3%2583%25AB

この状態だと受け取り側も2回デコードを掛ける必要が出てきてしまうため、避ける必要があります。

RestTemplateとエンコード

ところで、JavaでURLリクエストをする際は、RestTemplateを使うという方が多いと思います。 例えば、以下のように使うことができます。

// 1. URLエンコードを掛けた上でURL文字列を作成
String uriString = UriComponentsBuilder.fromUriString("https://sample")
    .queryParam("sample_query", "サンプル")
    .build()
    .encode()
    .toUriString();

// 2. 作ったURL文字列を使ってGETリクエストを送信
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(
    uriString,
    HttpMethod.GET,
    null,
    String.class
);

ただし、これだと実は問題があります。 まず、 1. URLエンコードを掛けた上でURL文字列を作成 をした結果、以下のような文字列が発行されます。

// 1. URLエンコードを掛けた上でURL文字列を作成
String uriString = UriComponentsBuilder.fromUriString("https://sample")
    .queryParam("sample_query", "サンプル")
    .build()
    .encode()
    .toUriString();

// uriString = https://sample?sample_query=%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB

これは正しくURLエンコードされた文字列になります。 しかし、実際に 2. 作ったURL文字列を使ってGETリクエストを送信 にて送信されるURLは以下になります。

// 2. 作ったURL文字列を使ってGETリクエストを送信
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(
    uriString,
    HttpMethod.GET,
    null,
    String.class
);

// 送信されたURL : https://sample?sample_query=%25E3%2582%25B5%25E3%2583%25B3%25E3%2583%2597%25E3%2583%25AB

見ての通り、二重エンコードされています。 実は文字列を渡して restTemplate.exchange を実行すると、自動的にURLエンコードが掛けられます。 もちろん有用な場合もありますが、知らずに使っていると上記のように意図しないエンコード結果になってしまうでしょう。

RestTemplateで自動的にエンコードさせない方法

では、どうすれば自動エンコードさせないようにできるのでしょうか。 実は、文字列ではなくURI型で渡せばエンコードされずにリクエストが実行されます。

// 1. URLエンコードを掛けた上でURIを作成
URI uri = UriComponentsBuilder.fromUriString("https://sample")
    .queryParam("sample_query", "サンプル")
    .build()
    .encode()
    .toUri();

// 2. 作ったURIを使ってGETリクエストを送信
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(
    uri,
    HttpMethod.GET,
    null,
    String.class
);

このようにすれば、作成したURIそのままでリクエストが実行されます。

最後に

意図しないURLエンコードは、当然ですが意図しないリクエストにつながる恐れがあります。 基本的にはRestTemplateにはURI型を渡すようにし、エンコードをかける際は UriComponentsBuilder で明示的に実行するのが良いでしょう。

XTechPodcastのアイコンをデザインしてみた話。

f:id:excite_ny:20211026103245p:plain

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

業務中に役員以上がいるMessengerグループに追加され、こんなメッセージが届きました。

「XTechのPodcastのアイコン作ってくれない?」

突然のオーダーに超焦りながら、内容とアイコンのイメージをヒアリングを行いました。

  • 内容:XTech役員・手嶋とスタートアップ業界の経営者や投資家との対談を週に1度程度配信

  • 配信媒体PodcastSpotify

  • タイトル:XTVオフレコ対談(アイコンのどこかに入れる)

  • アイコンイメージ:XTechパーカーやTシャツ同様、かっこいい感じ

ちなみにXTechのパーカーのデザインはこんな感じです!(以前はXTechのTシャツとパーカーを11案を出してました)

f:id:excite_ny:20211020184646p:plain

ビジネス系・Tech系・サイバー系など…いろいろ考えた結果…8案ほど提案しました!

f:id:excite_ny:20211020183416p:plain

f:id:excite_ny:20211020183432p:plain

個人的には配信者の顔がある方がリスナーの安心感が強まると思い、顔写真多めの案になりました!

この8案を提出し決定したのは…こちらです!

f:id:excite_ny:20211020184954j:plain

めっちゃ画像でかくなってしまってすみません!

個人的に顔写真ありを推していたのもあり、このアイコンが決定したのは意外でした。

・毎回配信者本人が登場しない可能性がある(別の社内の方と対談する場合がある)

・スタートアップ オフレコ対談はXTechのラジオでなので個人を指定する写真は使用しない

以上の理由で、顔写真ありは採用されなかったのかなと思いました。

このアイコンは、パーカーのイメージに合わせたサイバーな雰囲気で製作しました。

「スタートアップ オフレコ対談」第一回目はなんとメルカリ共同創業者の方に聞く創業秘話です!

私自身知らなかった話を聞けて、すごく勉強になったので是非聞いてみてください🙇‍♀️

podcasts.apple.com