0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SpringBootで開発したREST APIをLambda×API Gatewayの環境にデプロイし、Route53でカスタムドメイン化する①

Last updated at Posted at 2024-11-24

やりたいこと

SpringBootで開発したREST APIアプリケーションをLambdaにデプロイし、API Gatewayで公開したい。
API GatewayのエンドポイントのURLをRoute53を使ってカスタムドメイン化したい。
RestAPI.jpg

①でやること

少し長くなるので①と②に分けます。
①ではLambda環境で動作するSpringBootアプリケーションの開発、Lambdaへのデプロイ、API Gatewayとの連携まで行います。
②では、API GatewayとRoute53を使った、APIのエンドポイントのカスタムドメイン化を行います。

SpringBoot開発環境

・Java21
・SpringBoot3.4.0
・gradle
・Eclipse

完成したアプリケーション

Githubで公開しています。
今回はLambda環境で動かすために必要な設定のみ取り扱うため、詳しくは下記をご参照ください。

通常のSpringBootアプリとLambda環境で動かすSpringBootアプリの相違点

通常は、下記クラスのようなクラスがキックされることでアプリが実行されるようです。

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class LambdaFunctionSampleApplication {

	public static void main(String[] args) {
		SpringApplication.run(LambdaFunctionSampleApplication.class, args);
	}

}

しかし、Lambda環境ではこのようなクラスをキックすることができず、ClassNotFoundExceptionがスローされてしまいます。

そこで、Lambda環境でも実行できるようにする設定が必要です。

builde.gradleの設定

以下がgladleの設定です。

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.4.0'
	id 'io.spring.dependency-management' version '1.1.6'
	//shadowJarを作成できるようにする。
	id 'com.github.johnrengelman.shadow' version '7.0.0'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(21)
	}
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
	//Lambda上で実行できるようにするための設定できるようにする。
	implementation 'com.amazonaws:aws-lambda-java-core:1.1.0'
}

tasks.named('test') {
	useJUnitPlatform()
}

付け加えたのは2個所。
1つ目は、shadowJarを作成できるようにする設定です。
Lambdaにデプロイする際は、shadowJarとしてビルドする必要があるようです。
(なんでかは全く分かりません、、、)

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.4.0'
	id 'io.spring.dependency-management' version '1.1.6'
	//shadowJarを作成できるようにする。
	id 'com.github.johnrengelman.shadow' version '7.0.0'
}

2つ目はLambda環境で実行できるようにするための設定を行うライブラリに関してです。
すなわち、aws-lambda-java-coreを投入します。

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
	//Lambda上で実行できるようにするための設定できるようにする。
	implementation 'com.amazonaws:aws-lambda-java-core:1.1.0'
}

以上でbuilde.gradleの設定は完了です。

Contolollerの編集

次にContlollerを編集します。
以下が完成したControllerです。

package com.example.demo.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.amazonaws.services.lambda.runtime.Context;
import com.example.demo.entity.SampleEntity;
import com.example.demo.service.SampleService;

import lombok.Data;

@RestController
//inputとcontextのgetter/setterを使えるようにする。
@Data
//Lambda環境で@Serviceクラスをインジェクションするために必要
@ComponentScan({"com.example.demo.service"})
@RequestMapping("/sample")
public class SampleController {
	
	private SampleService service;
	//Lambdaがキックするクラスで使用するために定義
	private Object input;
	private Context context;
	
	//コンストラクタインジェクション(フィールドインジェクションが使えないため)
	@Autowired
	public SampleController(SampleService service){
		this.service = service;
	}
	
	@GetMapping("/api")
	public List<SampleEntity> returnList(){
		return service.createList();
	}

}

まずはクラスに付与されたアノテーションから見ていきます。

@RestController
//inputとcontextのgetter/setterを使えるようにする。
@Data
//Lambda環境で@Serviceクラスをインジェクションするために必要
@ComponentScan({"com.example.demo.service"})
@RequestMapping("/sample")

Lombokの@Dataに関しては、Lambdaがキックするクラスで使われるObject型の変数inputとContext型の変数contextのgetter/setterを補うものです。
また、@ComponentScanは、DIコンテナに登録されたオブジェクトをインジェクションする際に必要になります。Lambda環境では、@ComponentScanを使わないとDIコンテナに登録されたクラスを使うことができないようです。

次にフィールドとコンストラクタに関して。

    private SampleService service;
	//Lambdaがキックするクラスで使用するために定義
	private Object input;
	private Context context;
	
	//コンストラクタインジェクション(フィールドインジェクションが使えないため)
	@Autowired
	public SampleController(SampleService service){
		this.service = service;
	}

フィールドは上記で述べた通り、Lambdaがキックするクラスで使われるObject型の変数inputとContext型の変数contextを定義しています。
また、コンストラクタは@Autowiredを用いたコンストラクタインジェクションを行っていますが、Lambda環境で動かすためにはフィールドインジェクションは使えないようなのでこのような書き方をしています。(相変わらず理由は全く分かりません、、、)

Lambdaがキックするクラス(エントリーポイント)

以下がソースコードです。

package com.example.demo.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.example.demo.controller.SampleController;

@SpringBootApplication
//LambdaがSpringBootアプリケーションを起動する際にキックするクラス
//RequestHandlerをimplements
public class StartApp implements RequestHandler<Object, Object> {
	
	//RequestHandlerのメソッド
	@Override
	public Object handleRequest(Object input, Context context) {
		String args[] = new String[0];
		
		try(ConfigurableApplicationContext ctx = SpringApplication.run(SampleController.class, args)) {
			SampleController app = ctx.getBean(SampleController.class);
			app.setInput(input);
			app.setContext(context);
			Object result = app.returnList();
			return result;
		}catch(Exception e) {
			return "error.";
		}
	}

}

まずはアノテーションから見ていきましょう。

@SpringBootApplication

Lambdaがアプリケーションをキックする際のエンドポイントとなるクラスのため@SpringBootApplicationを付与します。

次にクラス宣言に関して

public class StartApp implements RequestHandler<Object, Object> 

gradleに追加したaws-lambda-java-coreライブラリのインターフェースであるRequestHandler をimplementsしています。

RequestHandlerで定義されたメソッドhandleRequest(Object input, Context context) をOverrideすることでLambdaがキックできるようになるようですね。

//RequestHandlerのメソッド
	@Override
	public Object handleRequest(Object input, Context context) {
		String args[] = new String[0];
		
		try(ConfigurableApplicationContext ctx = SpringApplication.run(SampleController.class, args)) {
			SampleController app = ctx.getBean(SampleController.class);
			app.setInput(input);
			app.setContext(context);
			Object result = app.returnList();
			return result;
		}catch(Exception e) {
			return "error.";
		}
	}

正直中身はよくわかりませんが、先ほど作成したControllerクラスとそのメソッドをいい感じ記述すれば動くようです。(笑)
(catchブロックの中身にRuntimeExceptionをthrowする記述を追記してもいいなって思いました。)

最後に実際に動かしてみます。
スクリーンショット 2024-11-24 114318.png
ローカルの開発環境では問題なく動きました!

ビルドする

アプリケーションが完成したので、gradleでビルドします。
ここで注意なのですが、普通のJarでビルドしてもLambda環境では動かないようなので下記のようにshadowJarでビルドしなければならないようです。
(ここでかなり沼りました。)
shadowJar.png

shadowJarをクリックするとビルドが開始され、下記場所にJarファイルが作成されます。
shadowJar完成場所.png

あとはお好きな場所にエクスポートすればOKです!

Lambdaへデプロイ

AWSマネジメントコンソールからLambdaを開きます。
関数→関数の作成を開きます。
関数名は任意の名前、ランタイムはJava21を選択し、それ以外はデフォルトにしました。
スクリーンショット 2024-11-24 110131.png

次に作成した関数を開き、コード→コードソース→アップロード元→.zipまたはjarファイルから、先ほどビルドしたJarファイルをアップロードします。
(ちょっと時間かかります。)
Lambdaにアップロード.png

次に、エントリーポイントとなるクラスのパスをLambdaで指定します。
コード→ランタイム設定を編集を開き、ハンドラを修正します。
forランタイム設定画面.png

今回は、com.example.demo.app.StartApp.javaをLambdaがキックするように設定したいので、example.Helloをcom.example.demo.app.StartAppに変更しました。
ランタイム設定.png

ここまでできたらLambda上でテストをします。
テスト→テストイベント→テストでテストを実行します。
Lambdaテスト画面.png

下記のように問題なく実行できていればOKです!
Lambdaテスト成功.png

API GatewayとLambdaの連携

マネジメントコンソールからAPI Gatewayを開きます。
API→APIを作成→REST APIを作成を選択します。
RESTAPI選択.png

今回はAPI名のみを入力し、それ以外はすべてデフォルトにしました。
API作成.png

次に作成したAPIを開き、メソッドの作成を開きます。
forメソッド作成.png

メソッドタイプはGET(APIの仕様に合ったもの)、統合タイプはLambda、そしてLambda関数には先ほど作成したものを指定し、メソッドを作成します。
メソッド作成.png

メソッドが作成されると、下記のようにリクエストとレスポンスの図が表示されます。
メソッド.png

また、Lambda側からもAPI Gatewayがトリガーになっていることを確認することができます。
Lambda画面にAPIgatewayがつながっている.png

最後にAPIデプロイを行います。
APIデプロイを開き、ステージに新しいステージを選択し、任意のステージ名を入力してデプロイします。
APIデプロイ.png

デプロイ後、下記のように表示されるエンドポイントをクリックし、APIが問題なく動作していればOKです!
エンドポイント.png

最後に

ここまでで、API Gatewayのデフォルトのエンドポイントを使用してSprngBootで作成したREST APIをデプロイすることができました!
次回は、Route53(とACM)を使って独自ドメインからAPIを呼び出せるようにしたいと思います!
続きはこちらから↓↓↓

参考にさせていただいた記事

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?