これはなんですか?
私がSpring Bootのプロジェクトを建てるときにやってることをまとめた。
2021年5月18日時点の話です。
API受けサーバ向け、Thymeleafはもうやらないと決めたので割愛だけど多分なんか追加するだけでいけるはず・・。
主にMaven(pom.xml)。時々クラスやconfigure、関連クラスの配置。
ほんとにこれが正解なのか?と疑問があるところはあまり詳しく書いていない。
特にAWSまわりは情報が無く手探りだった。
ほぼ必ずやること
その時の最新版をチョイスする
現状だと 2.5.0
開発しているうちに最新版が出るかどうかでRCを使うか判断する。
Spring initializrでプロジェクトの雛形をダウンロードする
毎回細かく変わっていてやるので、基本的にメジャーバージョンが変わるのであれば以前のプロジェクトから引き継いだりしないほうがよい。
その際に group
や artifact
は関連レポジトリの有無などを含めきちんと設計、考慮しておく。
JavaのLTS向け設定(現状は11)
<properties>
<java.version>11</java.version>
</properties>
lombok入れる
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
今どきボイラーコードをぐつぐつする人は居ないはず。
ModelMapper入れる
<dependency>
<groupId>io.github.yoshikawaa.modelmapper.spring.boot</groupId>
<artifactId>modelmapper-spring-boot-starter</artifactId>
<version>0.1.0</version>
<type>pom</type>
</dependency>
今どき単純なコンバーターを手で書く必要はあまりないけどたまにある。
OpenApi Generatorの組み込みとyamlの執筆
Swaggerなんとかはちょっとあくどい戦いを見てしまったので、Openapi Generatorを現状では支持しているが、開発状況によっては見捨てないといけないかもしれない。私は悲しい。
今どきREST APIのコントローラーだのクライアントだのをつるしで書く人はいないと信じる。
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<!-- RELEASE_VERSION -->
<version>4.3.1</version>
<!-- /RELEASE_VERSION -->
<executions>
<!-- コントローラー用 -->
<execution>
<id>api</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/api.yaml</inputSpec>
<language>spring</language>
<configOptions>
<generateApis>true</generateApis>
<apiPackage>jp.nreso.project.api</apiPackage>
<generateModels>true</generateModels>
<modelPackage>jpjp.nreso.project.model</modelPackage>
<generateSupportingFiles>false</generateSupportingFiles>
<sourceFolder>src/main</sourceFolder>
</configOptions>
<output>${project.build.directory}/generated-sources/api</output>
</configuration>
</execution>
<!-- クライアント用 通信先その1 -->
<execution>
<id>client1</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/client1.yaml</inputSpec>
<language>java</language>
<configOptions>
<generateApis>true</generateApis>
<apiPackage>jp.nreso.project.client1</apiPackage>
<generateModels>true</generateModels>
<modelPackage>jp.nreso.project.client1.model</modelPackage>
<sourceFolder>src/main</sourceFolder>
</configOptions>
<output>${project.build.directory}/generated-sources/client1/client</output>
</configuration>
</execution>
<!-- クライアント用 通信先その2 -->
<execution>
<id>client2</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/client2.yaml</inputSpec>
<language>java</language>
<configOptions>
<generateApis>true</generateApis>
<apiPackage>jp.nreso.project.client2</apiPackage>
<generateModels>true</generateModels>
<modelPackage>jp.nreso.project.client2.model</modelPackage>
<sourceFolder>src/main</sourceFolder>
</configOptions>
<output>${project.build.directory}/generated-sources/client2/client</output>
</configuration>
</execution>
</executions>
</plugin>
上記だけだとダメで、生成されたコードからビルドのじゃまになるコードを消す、
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<delete dir="${project.basedir}/target/generated-sources/api/src/test"/>
<delete dir="${project.basedir}/target/generated-sources/api/src/main/org"/>
<delete
dir="${project.basedir}/target/generated-sources/api/src/main/resources"/>
<delete dir="${project.basedir}/target/generated-sources/client1/client/src/test"/>
<delete>
<fileset
dir="${project.basedir}/target/generated-sources/api/src/main/jp/nreso/api"
includes="*Controller.java"/>
</delete>
<delete dir="${project.basedir}/target/generated-sources/client2/src/test"/>
<delete dir="${project.basedir}/target/generated-sources/client2/src/main/org"/>
<delete>
<fileset
dir="${project.basedir}/target/generated-sources/client2/src/main/jp/nreso/api"
includes="*Controller.java"/>
</delete>
</target>
</configuration>
</execution>
</executions>
生成したコードが使えるようにソースパスに追加。
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${basedir}/target/generated-sources/api/src/main</source>
<source>${basedir}/target/generated-sources/client1/src/main</source>
<source>${basedir}/target/generated-sources/client2/client/src/main</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
手間かかるなー でもやっておかないと・・
Springdocの設定
開発時に書いたyamlを可視化、実行するために必要。
なお、本番環境ではこいつは殺さないといけないので、SwaggerController.javaにキルスイッチを実装しておく。
(探せばなんか公式のスイッチありそう)
また、複数のyamlはindex.htmlを書いて選べるようにしておくといいんじゃないかな。
<plugin>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-maven-plugin</artifactId>
<version>0.3</version>
<executions>
<execution>
<phase>integration-test</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
@Controller
public class SwaggerController {
@Value("${springfox.documentation.swagger.enable:false}") // これがキルスイッチ
private boolean enable;
@RequestMapping("/")
public String index() {
return enable ? "select.html" : "index.html"; // disabledの場合はindex.htmlなどの魔除けでも404 not foundでもよい
}
@RequestMapping(value = "/api-docs/{api}",
produces = { "application/json" },
method = RequestMethod.GET)
public ResponseEntity<Map> apiDocs(@ApiParam(value = "api", required = true) @PathVariable("api") String api) throws IOException {
String yamlFile = api + ".yaml";
if (enable) {
Yaml yaml = new Yaml();
InputStream is = new ClassPathResource(yamlFile).getInputStream();
Map<String, Object> obj = yaml.load(is);
return new ResponseEntity<>(obj, HttpStatus.OK);
}
throw new RuntimeException();
}
}
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Hello</title>
<meta charset="utf-8" />
</head>
<body>
<h1>Select your API</h1>
<p>
<a href="swagger-ui/index.html?url=/api/api-docs/api">Controller</a>
<a href="swagger-ui/index.html?url=/api/api-docs/client1">client 1</a>
<a href="swagger-ui/index.html?url=/api/api-docs/client1">client 2</a>
</p>
</body>
</html>
Databaseアクセスのため、mybatisまたはdaoの設定
MySQLを使うことが多い
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
Error Advisorの設定
プロジェクトの package を jp.nreso.project
とした場合、どこでもいいのでエラーアドバイザを設定する。
具体的に言うとエラーハンドリングとよばれる類のもので、サービスから上がってきたもろもろのRuntimeExceptionを適切なHTTPレスポンスに変換すること。
この記事が参考になるかもしれません。
https://qiita.com/NagaokaKenichi/items/2f199134a881a776b717
以下は必要ならやること
Spring Security
HTTP Headerでの認証認可が必要ならConfuigureのクラス設定する。
JWT(Auth0)
JWTすこ。
今走ってるプロジェクトだとcognitoと連携しているもよう(別な人にPoC任せてるので、やってないので後で上がったものを見ておこう)
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
キャッシュの設定
キャッシュ的なものが必要な場合、 Service の中にメンバ持ったりせず、キャッシュに委ねる。
実装がややめんどくさい。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
ログイン系の処理の場合はLOG AppenderにMDCを設定
フィルタで下記を設定して、消す処理を入れる
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (認証必要ですよー) {
MDC.put("deviceId", ユーザID);
}
try {
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
これによってアクセスログにユーザIDを出せるからよき。
UT/mockの設定
たぶん initialzr がやってくれると思うけど一応。
あとmockのdependenciesを入れておく。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
参照するレポジトリの設定
initializr に最初からついてるかもしれないけど、Springのものを書いておく。
Nexusなど立ててプライベートなmavenレポジトリを運用している人もここに追記する。
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</pluginRepository>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
AWSの場合
どんどん適当になってきてすまない。
Spring CloudやAWSのSDKの依存関係を設定
AWSのSDKをdependenciesに追加したり、Spring Cloudでなんとかなっていたり正解がよくわからないので恥ずかしくあまり書きたくない。
Log Appenderにアクセスログを追加
kubernetesのpodからCloud Watch Logsにログを送るのに必要。
現状これでなんとか・・なってそう。
githubで公開されてるやつのうち、1個以上使ってはいけないやつがある。少なくともこれはちゃんとずっとworkしてた。
<dependency>
<groupId>com.j256.cloudwatchlogbackappender</groupId>
<artifactId>cloudwatchlogbackappender</artifactId>
<version>2.1</version>
</dependency>
こいつをこんなかんじのクラス作って。
public class AccessLogAppender<E> extends ConsoleAppender<E>
こんなかんじの設定おいてやらないと。
<configuration>
<appender name="CONSOLE" class="jp.nreso.project.api.log.AccessLogAppender">
<dateFormat>yyyyMMdd_HHmm</dateFormat>
<encoder>
<pattern>%h %l %user "%r" %s %b</pattern>
</encoder>
</appender>
<appender-ref ref="CONSOLE"/>
</configuration>
ちゃんとに送られないもよう。
この道を通らなくても FluentDの設定をコンテナに対しを行えばよいはずだけど、どうしてもログの出力をJava側に置きたかった事情があったのです。
ECRのDockerの設定
CI/CD用にレポジトリのURLやクレデンシャルなどの情報をmaven実行時にパラメータで指定できるように_:(´ཀ`」 ∠):_
この先は文字がかすれて読めない