オンプレからAWSリソースへ安全にアクセスする

エキサイトの宮西です。
こちらは エキサイトホールディングス Advent Calendar 2021 22日目の記事です。

qiita.com

はじめに

オンプレとクラウドのハイブリッド環境でシステムを運用していると、外部アプリケーションからAWSリソースを操作することが多々あります。脳死で「IAMユーザを作って、クレデンシャル情報を生成して・・・」となりがちだったので、どのような認証手段があるのかを整理しました。

署名バージョン4

IAMユーザのアクセスキー(クレデンシャル情報)を使った認証です。AWS SDKを使えばアプリケーション側も手っ取り早く実装できるので、使っている方も多いはずです。

Signature Version 4 signing process - AWS General Reference

が、有効期限の長いクレデンシャル情報を使うことになるので、キー漏洩による不正利用などが問題になりがちです。そのため、クレデンシャル情報のローテーションなどを考える必要があったりします。

セキュリティは軽視できませんし、なるべくならクレデンシャル情報の保存は避けたいところです。

SAMLOpenID Connector

使い回せるような半永続的なクレデンシャル情報ではなく、一時的に都度作成されるものであればリスクを低減できそうです。そこでSAMLOpenID Connector(OIDC)の出番です。GitHub ActionsがOIDCをサポートしたのは記憶に新しいですね。おかげでIAMユーザを作成せずとも、GitHub ActionsのワークフローからAWSリソースを操作できるようになったわけです。

Configuring OpenID Connect in Amazon Web Services - GitHub Docs

大体はこれで解決できそうですが、IdPがSAMLやOIDCに対応していなかったり、そもそもIdPが存在しない場合には新たに構築が必要になります。当然のことながら、SAMLやOIDCの認証プロセスの実装や運用も必要なので、一から作っていくのは骨が折れそうです。

MutualTLS

その他にも、MutualTLS(mTLS)が使えると思います。相互TLS認証とかクライアント認証などとも呼ばれていますね。AWSではAPI GatewayAWS IoT CoreなどでmTLSを設定できます。古くからある認証手段ですので、アプリケーション側も容易に実装できます。

Configuring mutual TLS authentication for an HTTP API - Amazon API Gateway

Client authentication - AWS IoT Core

ただし、証明書のライフサイクル管理が必須、という課題は残ります。また、API GWでは証明書が失効したかどうかについては検証しない*1ため、別途Lambdaオーソライザーなどを挟む必要があります。

おわりに

どれもメリット・デメリットがあります。

手法 メリット デメリット
署名バージョン4 手軽に実装できる クレデンシャル情報が漏洩したときのリスクが大きい
SAML / OIDC 一時的なクレデンシャル情報を用いるのでセキュリティ的に安心 SAMLアサーションやアクセストークンのライフサイクル管理、IdPの管理などが必須
MutualTLS レガシーなシステムでも実装が比較的容易 証明書のライフサイクル管理が手間になる

今回に限った話ではないですが、アプリケーションとの相性やビジネス的な優先度などから使い分けていけると良いかなと思います。

エキサイトではエンジニアを募集しています。募集情報は以下から。

www.wantedly.com

JacksonのObjectMapperがかなり優秀だった話

はじめに

はじめまして。エキサイトでインターンをさせていただいている岡﨑です。 アドベントカレンダーの22日目を担当させていただいています。 今日はJacksonのObjectMapperがとてもとても便利だった話をさせていただきます。

Object Mapperとは

それでははじめに言葉の定義からです。 Object Mapperとはそもそも何なのか、この機会に調べてみました。 まずは公式ドキュメントからです。

ObjectMapperは、基本的なPOJO(Plain Old Java Objects)との間、または汎用JSONツリーモデル(JsonNode)との間でJSONを読み書きするための機能と、変換を実行するための関連機能を提供します。また、さまざまなスタイルのJSONコンテンツを処理し、ポリモーフィズムやオブジェクトIDなどのより高度なオブジェクトの概念をサポートするように高度にカスタマイズできます。 公式ドキュメントより

簡単に噛み砕くと、 JacksonのObjectMapperはJavaのObjectとJson間の変換を簡単にしてくれるクラスです。

どれくらい簡単になるの?

それでは実際にやってみましょう。 JSONだとこんな感じです。

{
    "sample_id": 1,
    "sub_list1": [
        {
            "sub_id": 11,
            "sub2_list": [
                {
                    "sub2_id": 111,
                    "answer": "test"
                },
                {
                    "sub2_id": 121
                }
            ]
        },
        {
            "sub_id": 12,
            "sub2_list": [
                {
                    "sub2_id": 211,
                    "answer": "test2"
                },
                {
                    "sub2_id": 212,
                    "answer": "test3"
                }
            ]
        }
    ]
}

これを受け取り、モデルに変換していきたいと思います。

@Slf4j
@Value
public class SampleForm {
    @NotNull
    Sample samle;

     @Value
    public static class Sub2 {
        @NotNull
        Integer subId2;

        @NotNull
        String answer;

        @JsonCreator
        public Sub2 (
            JsonProperty("sub_id2") Integer subId2; 
            @JsonProperty("answer") String answer; 
        ) {
            this.subId2 = subId2;
            this.answer = Optional.ofNullable(answer).orElse("");
        }
    }

    @Value
    public static class Sub1 {
        @NotNull
        Integer subId;

        @NotNull
        List<Sub2> sub2List;

        @JsonCreator
        public Sub1 (
            JsonProperty("sub_id") Integer subId; 
            @JsonProperty("sub2_list") List<Sub2> sub2List; 
        ) {
            this.subId = subId;
            this.sub2List = sub2List;
        }
    }

    @Value
    public static class Sample {
        @NotNull
        Integer sampleId;

        @NotNull
        List<Sub1> subList1;

        @JsonCreator
        public Sample (
            @JsonProperty("sample_id") Integer sampleId,
            @JsonProperty("sub_list1") List<Sub1> subList1
        ) {
            this.sampleId = sampleId; 
            this.subList1 = subList1;
        }
    }

    @ConstructorProperties({
        "sample"
    })
    public SampleForm(
        Sample sample
    ) {
        this.sample = sample;
    }
}

こんな感じでできたらいいなと思いますよね。 しかし、これでは動きません。JSONのデータからそのままモデルに変換することはできないから当然です。 この場合はSampleがnullになって落ちます。

それでは、次のコードを書き換えていきましょう。

    @ConstructorProperties({
        "sample"
    })
    public SampleForm(
        Sample sample
    ) {
        this.sample = sample;
    }

ここがモデルで受け取ることはできないので、Sampleクラスで受け取っているところをStringで受けとってあげます。 そして、Sampleクラスに変換します。

さて、本題です。 ここで長々とコードを書かずに、数行で全て変換してくれるObjectMapperの登場です。

    @ConstructorProperties({
        "sample"
    })
    public SampleForm(
        String sample
    ) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            this.sample = mapper.readValue(sample, new TypeReference<>() {});
        } catch(Excepting exceptin) {
            new Exception("モデルに変換できませんでした");
        }
    }

ObjectMapper mapper = new ObjectMapper();

this.sample = mapper.readValue(sample, new TypeReference<>() {});

この二行を使うだけで、意図している通りにモデルを変換することができます。 とても便利ですね。

終わりに

Object Mapperがここまでできることを知らないで実装すると、とてもごちゃごちゃとしたやばいコードを書くことになります。 それを防ぐためにも、こうして使えるものは正しく使っていくことが大切だと思いました。

アドベントカレンダーも終盤ですが、最後まで楽しんでいただけると幸いです。

qiita.com

quarkusを使う(コンパイル編)

こんばんは、エキサイト株式会社の中尾です。

quarkusといえば、native compileでしょう。 まずはjvmで。

shogo.nakao@localhost:(main*) $ docker build -f src/main/docker/Dockerfile.jvm -t quarkus/hobby-jvm . 
[+] Building 100.3s (11/11) FINISHED
 => [internal] load build definition from Dockerfile.jvm                                                                                                                                                                                                                   0.0s
 => => transferring dockerfile: 2.15kB                                                                                                                                                                                                                                     0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                                                          0.0s
 => => transferring context: 111B                                                                                                                                                                                                                                          0.0s
 => [internal] load metadata for registry.access.redhat.com/ubi8/ubi-minimal:8.4                                                                                                                                                                                           7.9s
 => [1/6] FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4@sha256:c536d4c63253318fdfc1db499f8f4bb0881db7fbd6f3d1554b4d54c812f85cc7                                                                                                                                    14.1s
 => => resolve registry.access.redhat.com/ubi8/ubi-minimal:8.4@sha256:c536d4c63253318fdfc1db499f8f4bb0881db7fbd6f3d1554b4d54c812f85cc7                                                                                                                                     0.0s
 => => sha256:d46336f50433ab27336fad8f9b251b2f68a66d376c902dfca23a6851acae502c 39.29MB / 39.29MB                                                                                                                                                                          12.5s
 => => sha256:be961ec6866344c06fe85e53011321da508bc495513bb75a45fc41f6182921b6 1.74kB / 1.74kB                                                                                                                                                                             3.5s
 => => sha256:c536d4c63253318fdfc1db499f8f4bb0881db7fbd6f3d1554b4d54c812f85cc7 1.47kB / 1.47kB                                                                                                                                                                             0.0s
 => => sha256:4d6547bbb7c5fe0f0d37b491c0c1975ff96bf4cf1c26dd665e3a5d70918b5564 737B / 737B                                                                                                                                                                                 0.0s
 => => sha256:e7685639f55280ffe8da3724cb1d342091a1ceea79af5eb91a07ce97cd32f221 4.28kB / 4.28kB                                                                                                                                                                             0.0s
 => => extracting sha256:d46336f50433ab27336fad8f9b251b2f68a66d376c902dfca23a6851acae502c                                                                                                                                                                                  1.4s
 => => extracting sha256:be961ec6866344c06fe85e53011321da508bc495513bb75a45fc41f6182921b6                                                                                                                                                                                  0.0s
 => [internal] load build context                                                                                                                                                                                                                                          0.3s
 => => transferring context: 22.66MB                                                                                                                                                                                                                                       0.3s
 => [2/6] RUN microdnf install curl ca-certificates java-17-openjdk-headless     && microdnf update     && microdnf clean all     && mkdir /deployments     && chown 1001 /deployments     && chmod "g+rwX" /deployments     && chown 1001:root /deployments     && curl  75.7s
 => [3/6] COPY --chown=1001 build/quarkus-app/lib/ /deployments/lib/                                                                                                                                                                                                       0.1s
 => [4/6] COPY --chown=1001 build/quarkus-app/*.jar /deployments/                                                                                                                                                                                                          0.0s
 => [5/6] COPY --chown=1001 build/quarkus-app/app/ /deployments/app/                                                                                                                                                                                                       0.0s
 => [6/6] COPY --chown=1001 build/quarkus-app/quarkus/ /deployments/quarkus/                                                                                                                                                                                               0.0s
 => exporting to image                                                                                                                                                                                                                                                     2.4s
 => => exporting layers                                                                                                                                                                                                                                                    2.4s
 => => writing image sha256:7a4f731341a6d3c9b04e1ffe98783be021280fe109fea48425fe49cb983cf860                                                                                                                                                                               0.0s
 => => naming to docker.io/quarkus/hobby-jvm                                                                                                                                                                                                                               0.0s```
shogo.nakao@localhost:(main*) $ docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/hobby-jvm
exec java -Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -XX:+ExitOnOutOfMemoryError -cp . -jar /deployments/quarkus-run.jar
Listening for transport dt_socket at address: 5005
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-12-20 14:04:51,601 INFO  [io.quarkus] (main) hobby v1.0.0 on JVM (powered by Quarkus 2.5.1.Final) started in 1.055s. Listening on: http://0.0.0.0:8080
2021-12-20 14:04:51,602 INFO  [io.quarkus] (main) Profile prod activated.
2021-12-20 14:04:51,603 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-validator, jdbc-mysql, mybatis, narayana-jta, resteasy, resteasy-jackson, smallrye-context-propagation, vertx]
^C2021-12-20 14:05:17,653 INFO  [io.quarkus] (Shutdown thread) hobby stopped in 0.036s
shogo.nakao@localhost:(main*) $ docker run -i --rm -p 8080:8080 quarkus/hobby-jvm
exec java -Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager -XX:+ExitOnOutOfMemoryError -cp . -jar /deployments/quarkus-run.jar
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-12-20 14:05:21,306 INFO  [io.quarkus] (main) hobby v1.0.0 on JVM (powered by Quarkus 2.5.1.Final) started in 0.982s. Listening on: http://0.0.0.0:8080
2021-12-20 14:05:21,307 INFO  [io.quarkus] (main) Profile prod activated.
2021-12-20 14:05:21,307 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-validator, jdbc-mysql, mybatis, narayana-jta, resteasy, resteasy-jackson, smallrye-context-propagation, vertx]

debug on で 1.055s,debug offで0.982sです。もちろんサイズが小さいこともありますが、、、早すぎじゃないですか?

続いてnativeです。初回でエラーが出る方は、sdk man でgraalVMを入れて下さい。

Cannot find the `native-image` in the GRAALVM_HOME, JAVA_HOME and System PATH. Install it using `gu install native-image`

そして待ちに待ったnative compile開始!!

...

......

.........

compileできなかった。。。

なんと、、、recordクラスに対応していなかった、mybatisも対応していなかった。。

どうやらissuesで21.3.0.r17でrecordクラスどうにかして欲しいみたいなのを見つけた。

https://github.com/quarkusio/quarkus/issues/20891

くっそう。。。悲しい、、、

できた瞬間、速攻ためそう。

最後に、弊社では採用もバシバシ実施しているので興味のあるかたがいましたらご応募ください。

www.wantedly.com



Advent Calendar 2021を引き続き楽しんでいただけると嬉しいです。

qiita.com

Spring Bootで、Webアクセスのパラメータをクラスで受け取るときの注意点

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

Spring BootでAPIなどを作る時は、Webからのアクセスを受け取ることになります。 そのアクセスにクエリパラメータ等でパラメータが付いている場合、そのパラメータを何かしらの方法で受け取る必要があります。

今回は、クラスを使ってパラメータを受け取るときに、予期しないデータを受け取ってしまう場合がある点について説明していきます。

クラスでパラメータを受け取る方法

例えば、以下のアクセスを受け付けるとします。

http://localhost/sample?test1=aaa&test2=bbb

その場合、以下のコードで受け取ることができます。

import lombok.Value;

import java.beans.ConstructorProperties;

@Value
public class SampleModel {
    String test1;
    String test2;

    @ConstructorProperties({"test1", "test2"})
    public SampleModel(String test1, String test2) {
        this.test1 = test1;
        this.test2 = test2;
    }
}
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping
public class SampleController {
    @GetMapping("sample")
    public String sample(
            @ModelAttribute SampleModel sampleModel
    ) {
        return "OK";
    }
}

実際にデバッグモードで実行してみると、SampleModelに以下のデータが入っていることがわかります。

SampleModel(test1=aaa, test2=bbb)

上記のコードであれば問題はありませんが、実は特定のパターンだと想定外のデータが入ってしまうことがあります。

想定外のデータが入ってしまうパターン

アクセスは同じく

http://localhost/sample?test1=aaa&test2=bbb

とし、受け取りクラスのコードを少し変えてみます。

import lombok.Data;

import java.beans.ConstructorProperties;

// ValueではなくDataで受け取る(各プロパティにSetterが付く)
@Data
public class SampleModel {
    String test1;
    String test2;

    @ConstructorProperties({"test1", "test2"})
    public SampleModel(String test1, String test2) {
        this.test1 = test1;

        // test2は、一部文字列を変更してからプロパティに代入する
        this.test2 = test2 + "ccc";
    }
}

こちらは同じコードです。

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

@RestController
@RequestMapping
public class SampleController {
    @GetMapping("sample")
    public String sample(
            @ModelAttribute SampleModel sampleModel
    ) {
        return "OK";
    }
}

本来は

SampleModel(test1=aaa, test2=bbbccc)

こうなってほしいはずですが、実際は

SampleModel(test1=aaa, test2=bbb)

こうなってしまいます。

どうやら、「 Setter が存在し、かつアクセスのパラメータと同じ名前のプロパティ」があると、コンストラクタでプロパティに値を代入した後に、直接アクセスのパラメータの値をプロパティに代入してしまい、結果として上書きされてしまうようです。

このような場合、 Setter をつけないようにすることで解決できます。

import lombok.Value;

import java.beans.ConstructorProperties;

// SetterがつかないValueを使用する
@Value
public class SampleModel {
    String test1;
    String test2;

    @ConstructorProperties({"test1", "test2"})
    public SampleModel(String test1, String test2) {
        this.test1 = test1;
        this.test2 = test2 + "ccc";
    }
}

このコードであれば、想定通り以下のデータとなります。

SampleModel(test1=aaa, test2=bbbccc)

最後に

今回はシンプルな例でしたが、複雑になってくると見つけるのが大変になっていく恐れがあります。 コンストラクタでデータを入れる場合は Setter は不要なはずなので、可能な限り外していくことをおすすめします。

TABLE定義を設計する時、カラム名に半角数値を入れるのはやめよう

こんにちは、エキサイト株式会社の中尾です。

出落ちみたいなタイトルなのですが、みなさんはテーブル設計の時、カラム名はどのようにしていますか?

基本は - 半角英字のみ - 極力正式名称をカラム名に、妙な省略はしない - package、classなど特定の言語の予約後になる可能性のある単語は避ける

だと、ほとんどの言語で互換性があり安定して使えると思います。

また、カラム名に半角数値を入れて横にカラムを伸ばすのは極力やめたほうが良いです。

そういう時は、

  • そもそも複数入るのか確認する。
  • 縦にデータを持ち、typeで分ける。

などいろいろアプローチがあるはずです。

例えば、仮に半角数値を使う時、どっちを使いますか?

  • tag_1
  • tag1

どっちを使っても、ローワーキャメルケースでは以下で現れます。

public String getTag1(){
    retrurn this->tag_1;
}

public String getTag1(){
    retrurn this->tag1;
}

キャメルケースでは半角英字をアッパーキャメルで表示しますが、数値では表現できません。これは不便ですよね。

つまり、ORMで自動でモデルを作成する場合など、キャメルケースにした場合にどっちにでも取れるカラム名にすると、大体不具合が発生することが多いです、 うまくコード変換が行われずに以下のようになる場合もあります。

public String getTag_1(){
    retrurn this->tag_1;
}

テーブル設計でそもそものデータ構造を考えるのも大事ですが、カラム名にも気をつけてあげられたら嬉しいと思います。

Rxdartを使用して値の変化を他タブなど別画面に通知する方法

これは エキサイトホールディングス Advent Calendar 2021 21日目の記事です。

エキサイト株式会社の高野です。今回はFlutterにおける別画面への値渡しや通知をする方法についてです。

実装

まずはじめに通知を受ける変数として以下を定義します。

final hoge = BehaviorSubject<void>.seeded({});

この void の部分を任意の型にすることによって通知を投げる側からその型の値を受け渡しすることができます。

受け取る際には任意の画面のinitStateなりで以下のように定義します。

hoge.listen((value) => _exFunction())
        .addTo(subscription);

これで通知の受け取りができました。
次に投げる側です

hoge.sink.add({})

こちらを任意の場所で呼んであげることによって通知を投げれます。
addのなかに定義した型の渡したい値をいれます。(ex: add('fugafuga') )

以上で受け渡しができるようになります。これを別クラスにシングルトンで作って呼び出せるようにすると使いやすいと思います。

最後に

弊社では絶賛採用強化中です。もしご興味がある方がいましたら下記リンクよりアクセスいただけると幸いです。(カジュアルからもOKです) www.wantedly.com

SpringBoot DevToolsのHot RestartとLiveReloadでサクサク開発を行う

エキサイト株式会社エンジニアの佐々木です。2021年エキサイトホールディングス アドベントカレンダー19日を担当させていただきます。

余談

エキサイトのメディア事業部では、SpringBootを使用して過去の技術的負債に立ち向かっております。私がジョインしてから1.5年以上が経過しましたが、半年以上の開発を経てメンバーも、悪い設計の見直しや改善、無駄処理の置き換え、クエリチューニングやパフォーマンス改善を通してサーバコストの圧縮を行っており成果がではじめています。サーバコストが半分以下になっていながらも、アプリケーションの負荷対策や開発速度などのパフォーマンスは上がっているところがほとんどで、技術観点や設計観点でも、ジョインしたときと比べて、成長をしているかと思います。

はじめに

SpringBootで開発する際に、Javaコンパイル言語であるのでコードを編集したらアプリケーションの再起動が必要になります。スクリプト言語のように次回実行時に動的にロードされるとかは通常ありません。SpringBootでは、Hot Reloadはないけど、Hot Restartはあるよといったものです。Hot Reloadより遅いけど、Hot Restartでも十分戦えるものになっているかと思います。バックエンドとフロントエンドの両方をご紹介します。

バックエンド

まず、バックエンド側のHot Restartの設定を紹介になります。

設定

依存関係を解消してくれるbuild.gradleに、SpringBoot devtoolsのパッケージを追加します。

dependencies {
    ...
        developmentOnly 'org.springframework.boot:spring-boot-devtools'    // この設定を追加
    ...

SpringBootの設定ファイルであるapplication.yaml(application.properties)に下記の設定を追加します。

spring.devtools.restart.enabled=true

これで準備は完了になります。

動作確認

動作確認は、

  1. IntelliJでSpringBootを起動
  2. コードを編集(コメントアウトしているコードのコメントを解除)
  3. ビルド (ファイル単体ビルド ⌘ + Shift + F9 or プロジェクトビルド ⌘ + F9 )

を実行します。これを行うと、SpringBoot DevToolsが変更を検知して、Hot Restartをしてくれます。このHot Restartは、アプリケーション全部を再起動しているわけではなく、必要な箇所だけやっているっぽいので、通常の再起動よりかなり高速です。

f:id:earu:20211219141741g:plain

さらに

ビルドのショートカットすら押したくない人もいるかと思います。IntelliJは、SpringBoot実行時には、コンパイルが走らないという設定があるので、これを解除します。

  • Preferences > Build, Execution, Deployment > Compiler > Build Project Automatically にチェックを入れます
  • Preferences > Advanced Settings > Allow auto-make to start even if developed application is currently running にチェックを入れます ※ Intellijの古いバージョンだと registly設定からauto-make で検索してください!!!

Auto Make は、ちょっとディレイがあるので、個人的には面倒でもビルドショートカットを押すのがオススメです。

フロントエンド

フロントエンドでは、LiveReloadという機構がありますので、これを使います。SpringBoot DevToolsは、このLiveReloadに依存しています。

livereload.com

設定

application.properties(application.yaml)に下記の設定を行います。

spring.devtools.livereload.enabled=true
spring.thymeleaf.cache=false

Chrome Extensionをインストール

Chromeに下記のExtensionをインストールします。

chrome.google.com

LiveReloadを動作させたいURLを開き、有効にします。

f:id:earu:20211219122336p:plain
LiveReloadのExteion

設定は以上で完了です。

動作確認

動作確認は、

  1. IntelliJでSpringBootを起動
  2. コードを編集(Hello, Spring -> Hello, LiveReload)
  3. ビルド (ファイル単体ビルド ⌘ + Shift + F9 or プロジェクトビルド ⌘ + F9 )

標準出力(テンプレートを通さない出力)もサーバサイドテンプレート出力もどちらにも対応しています。

標準出力

f:id:earu:20211219144738g:plain

サーバサイドテンプレート出力

f:id:earu:20211219143631g:plain

テンプレートのみ修正の場合

テンプレートのみ修正の場合は、再起動なしでOKです。SpringBoot DevToolsがファイル変更を検知して、LiveReloadをキックしてくれます。

f:id:earu:20211219143144g:plain

まとめ

バックエンドはスクリプト言語のようにまではいきませんが、少しはサクサク開発ができるようにはなります。設定がやや必要にはなりますが、静的型付け言語が持つ便利さを享受するのと引き換えにこのくらいの簡単な設定であれば、許容範囲ないかなと思います。フロントエンドの方は、最近ではReact.jsやVue.jsなどクライアントサイドのフレームワークが充実し、サーバサイドテンプレートは流行りませんが、SPAだとSEOは不安ですし、SSGやISRだとビルドのタイミング等に気を配る必要があります。規模によって適切なものを選択するのがいいかとおもいます。

最後に

最後まで読んでいただいてありがとうございます。引き続き、2021年エキサイトホールディングス アドベントカレンダーをお楽しみいただけると幸いです。

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

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

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