Upgrade to Pro — share decks privately, control downloads, hide ads and more …

【業務アプリのモダナイズ】このまま保守・改修し続けて大丈夫ですか?

 【業務アプリのモダナイズ】このまま保守・改修し続けて大丈夫ですか?

Avatar for Satoshi Kaneyasu

Satoshi Kaneyasu

September 21, 2026

More Decks by Satoshi Kaneyasu

Other Decks in Programming

Transcript

  1. Speaker Introduction 氏名:兼安 聡 所属:株式会社サーバーワークス アプリケーションサービス本部 在住:広島 担当: PM、SM、DevOps、仕様駆動開発 SNS(X):@satoshi256kbyte

    • • • • • • 2026 Japan AWS Ambassadors 2024-26 Japan AWS Top Engineers 2024-26 Japan AWS All Certifications Engineers 2025-26 AWS Community Builders 認定スクラムマスター PMP 2
  2. 目次 1. モダナイズとは 2. Java+Spring Bootのモダナイズを考える  要件定義 As-Is To-Be

     基本設計 技術スタック確定  実装とテスト、そして仕様駆動開発 3. 運用定着への道程 4. まとめ
  3. 本発表のモダナイズにおけるAIの役割 ⚫ 本発表では、モダナイズ全てをAI駆動開発で行うとはしていません。 ⚫ モダナイズをウォータフォール開発になぞらえて、工程ごとにAIの使い方を変えています。 工程 AIの役割 要件定義 現行資産の読解・情報収集における壁打ち相手 基本設計

    移行方式・アーキテクチャの技術的な裏取り 詳細設計・実装・単体テスト 機械的な変換作業の自動化、仕様駆動開発によるコード生成 結合テスト・総合テスト テストケース・テストデータの作成、バグ分析の支援 ※本発表ではテスト工程はスキップします 運用・保守 障害発生時のインシデント調査、異常検知の支援 7
  4. 現環境(As-Is)の技術スタック 項目 内容 言語 Java 11 フレームワーク Spring Boot 2.7系、Spring

    Data JPA(Hibernate)、Thymeleaf データベース MySQL 5.7 APサーバー Embedded Tomcat(Spring Boot標準) 認証方式 フォーム認証+インメモリ管理 ログ出力方式 Logback(Spring Boot標準) ファイルストレージ ローカルストレージ 10
  5. 現環境(As-Is)の課題 領域 課題 セキュリティ Spring Boot 2.x(OSSサポートは2023年11月終了)、 Java 11、MySQL 5.7

    のEOLによる脆弱性リスク 実装パターン WebSecurityConfigurerAdapter等、2.x時代の標準的な実装パターンが 既に非推奨化されており、放置するほど将来の書き換えコストが増える アーキテクチャ モノリシックな作りのため、影響範囲の特定や機能単位での改修が困難 インフラ 単一固定サーバーによるスケーリング限界 手動運用による設計書とサーバー設定の剥離 ※UIに関して大きな課題はないとします。 12
  6. 目指す姿(To-Be)の技術スタック 項目 内容 言語 Java 25 フレームワーク Spring Boot 4.x系、Spring

    Data JPA(Hibernate 7)、Thymeleaf データベース Amazon Aurora MySQL 8.x系 APサーバー Embedded Tomcat(Spring Boot標準) 認証方式 フォーム認証+Spring Session + Redis(Amazon ElastiCache) ログ出力方式 Logback(Spring Boot標準)+Amazon CloudWatch Logs ファイルストレージ Amazon S3 15
  7. 目指す姿(To-Be)の技術スタック(AWS)とねらい 領域 採用するAWSサービス ねらい コンテナ実行基盤 Amazon ECS(Fargate) • サーバーレスなコンテナ運用 •

    ローカル開発にDockerを使用することで、 ローカルと本番の環境差を軽減 負荷分散と分岐点 Application Load Balancer • ルーティング最適化とスケーラビリティ確保 • 機能的な分岐点を設置可能へ 外部記憶媒体 Amazon S3 Amazon ElastiCache for Redis • ファイル・セッション・キャッシュの外部化 • スケーラビリティの阻害要因の排除 • リソース不足に陥る可能性を軽減 機密情報管理 AWS Secrets Manager • 設定ファイルのGitへの誤Pushリスクの軽減 • パスワードローテーション機能などの恩恵 AWS CDK • 手動作業の軽減 • 環境構築の再現性の向上 • 設計書と環境の剥離を軽減 IaC 16
  8. コンテナ(Amazon ECS on Fargate)かAWS Lambdaか? 観点 コンテナ(ECS on Fargate) (採用)

    サーバーレス(AWS Lambda) 既存コードの流用 Dockerイメージ化のみでほぼそのまま 移行可能 モノリスのままでは動かず、 関数単位への分割が前提になる 開発・本番環境の一致 ローカルのDocker Composeと本番 Fargateがほぼ同じ環境 ローカルでのLambda実行環境の再現が コンテナほど容易ではない 実行時間の制約 制約なし 最大15分(長時間処理には不向き) コールドスタート 常駐のため意識不要 JVMは初期化が重く、 SnapStart等の対策検討が必要 ⚫ Lambda Web Adapterのように既存コードをそのままLambda化できる仕組みもありますが、 「学習コスト」「サーバーレス化による新たな課題が生まれないか」を踏まえ、 今回は「コンテナ」を選択します。 17
  9. MySQLかPostgreSQLか? 観点 Aurora MySQL(採用) Aurora PostgreSQL 既存資産との互換性 MySQL 5.7からの移行で変更 が少なく、確実性が高い

    方言の違いにより、生SQLやDDLの書き換えが 必要 先進的な機能 基本的なRDBMS機能が中心 RLS(行レベルセキュリティ)や列単位のアクセ ス制御など 移行の手間・リスク 低い クエリ変換やテストの工数が増える ⚫ 業務アプリケーションにおいて現在勢いが勝っているのはPostgreSQL。 将来的に列単位のアクセス制御のような先進機能を使いたいならPostgreSQLが有利です。 ⚫ とはいえMySQLがなくなるわけではないので、確実性を重視してMySQLを選択します。 18
  10. Amazon Cognitoを採用するか? 観点 既存のSpring Boot認証 (採用) Amazon Cognito メリット 実装済みのロジックを変更しないため、

    移行コストも不具合リスクもない マネージド型のユーザー管理、 MFAなど標準機能が充実 マイクロサービスとの相性 サービスが単一(モノリス)のため、 恩恵は限定的 JWTトークンにより複数サービス間で 認証情報を共有しやすい 移行コスト 変更なし ログイン/ログアウトフローの作り直し、 ユーザーストアの移行が必要 ⚫ Cognitoは利便性が高く、マイクロサービス環境での恩恵が大きくはありますが、 今回は一般消費者向けではなく単一の業務アプリであり、移行コストに見合うだけの益がある とは言い切れません。 ⚫ そのため今回の認証はSpring Bootの既存実装のまま残し、スケーリングの妨げとなるセッシ ョンだけをElastiCache for Redisに外部化するとします。 19
  11. モダナイズの3ステップ(段階移行アプローチ) 1) 既存構成をコンテナ化 2) AWS Transform custom による自動アップグレード 3) AWSネイティブアーキテクチャへ移行

    言い換えると、1)ローカルでの作業をやりやすくし、2)AIの力でできる限り自動アップグレードし て、3)AWSに合わせつつ本格的なモダナイズをする、という3ステップです。 単純にAmazon EC2に移行し、 その後でモダナイズするやり方もありますが今回はそれはスキップします。 20
  12. モダナイズの3ステップによる技術的な裏取りと確定 要件定義で決めた技術スタックを、全機能に対して展開する前に 「机上の決定が実際に成立するか」を裏取りして確定させます。 ステップ 検証項目 Step1 コンテナ化 そもそもコンテナ化可能なのか? Windowsサーバーや特異なライブラリを使用している場合、 工夫が必要になる

    Step2 アップグレード ①AWS Transform customでアップグレードできるか? ②アップグレードした結果が動くか? ③Transform customではアップグレードできないものを見極め →後工程(実装フェーズ)のタスクとして挙げる Step3 クラウドネイティブ検証 To-Beの構成で、小規模に動作確認する 22
  13. Step1 コンテナ化の検証 ステップ 担当 内容 現行環境の情報収集とAIへの投入 人間 設計書の収集、サーバーへのログインによる 設定・バージョン確認 コンテナのベースイメージの選定

    AI+人間 OS・JDKバージョンに応じた適切なベースイメージ を選定 依存ライブラリの取得可否確認 AI+人間 パッケージ管理でインストールできないライブラリを 洗い出し、リポジトリに同梱 Dockerfileと docker-compose.ymlの生成 AI 収集した情報を基に、コンテナ定義ファイルをAIで 生成 動作検証 人間 ローカルのdocker-compose環境で実際に起動し、 動作を確認 差分の修正 人間+AI 動かない箇所を人間が切り分け、AIに修正させる 23
  14. Step2 AWS Transform customのスコープの見極め 一旦全体をTransform customで、自動でアップグレードできる範囲を見極めます。 ⚫ Java 11 →

    25へのバージョンアップ ⚫ Spring Boot 2.x → 4.x ⚫ javax → jakarta 名前空間移行 ⚫ Spring Security DSL 更新 ⚫ 依存ライブラリの互換性修正などを一括変換 とはいえ、以下のものは高確率で対応できないので、最初から別タスクを検討しておきます。 変更点 内容 ファイルストレージのS3化 ローカルファイルパスへの直書きを、S3 SDK呼び出しに変更 セッションの持ち方 インメモリ保持から、ElastiCache for Redisへの外部化(Spring Session設定) DB接続情報の取得方法 application.ymlの固定値から、Secrets Manager経由の動的取得へ変更 画面側のパス生成 ファイルパス・画像パスの生成ロジックを、S3のURL生成に合わせて変更 ログ出力の方法 アプリコードの変更ではなく、サーバー設定側の対応で済む可能性もある 24
  15. 実装工程のWBS-1 WBS 開発手法・アプローチ 役割分担 SDD(仕様駆動開発) • 人間が既存情報を収集 • AIがコード生成 •

    人間がレビュー ローカル実行基盤の作成 (Docker Compose等) SDD(仕様駆動開発) • 人間が既存情報を収集 • AIがコード生成 • 人間がレビュー Java 11→25 アップグレード AWS Transform custom • AIが自動変換 • 人間がレビュー Spring Boot 2.x→4.x アップグレード AWS Transform custom • AIが自動変換 • 人間がレビュー Transform customで変換しきれない 箇所の修正 SDD(仕様駆動開発) • AIがコード生成 • 人間がレビュー 既存アプリのコンテナ化 28
  16. 実装工程のWBS-2 WBS 開発手法・アプローチ 役割分担 新アーキテクチャのIaC作成 (ユニットテスト込み) SDD(仕様駆動開発) • AIがコード生成 •

    人間がレビュー ファイルストレージのS3化 SDD(仕様駆動開発) • AIがコード生成 • 人間がレビュー セッション・キャッシュの外部化 SDD(仕様駆動開発) • AIがコード生成 • 人間がレビュー DB接続情報の取得方法の変更 SDD(仕様駆動開発) • AIがコード生成 • 人間がレビュー 画面側のファイルパス・画像パス生成の 変更 SDD(仕様駆動開発) • AIがコード生成 • 人間がレビュー 29
  17. データベース移行方針 ⚫ MySQL5.7から8.x系の移行は、AWS Database Migration Serviceを使う方法が考えられるが、 一般的な業務アプリケーションなら、ダンプ&リストアで事足りる可能性が高いです。 ⚫ MySQL 5.7から8.xへの移行では、デフォルトのソート順(ORDER

    BY句なしの場合)など、 いくつかの挙動が変わっています。 ⚫ これらの挙動の変化をAIに調査&修正させつつ、テスト工程で挙動の違いに着目したテスト項目を挙 げると良いでしょう。 31
  18. AIに生成させたDockerfile・docker-compose.ymlの例 AppサーバーDockerfile # Stage 1: Build FROM maven:3.8-eclipse-temurin-11 AS build

    WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src COPY checkstyle.xml . RUN mvn package -DskipTests -B # Stage 2: Run FROM eclipse-temurin:11-jre WORKDIR /app COPY --from=build /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"] docker-compose.yml version: '3.8' services: app: build: . ports: - "8080:8080" environment: SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/employee_db?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=UTF-8&serverTimezone=Asia/Tokyo SPRING_DATASOURCE_USERNAME: app_user SPRING_DATASOURCE_PASSWORD: app_password volumes: - photo-data:/data/employee-photos depends_on: db: condition: service_healthy db: image: mysql:5.7 ports: - "3306:3306" environment: MYSQL_ROOT_PASSWORD: root_password MYSQL_DATABASE: employee_db MYSQL_USER: app_user MYSQL_PASSWORD: app_password volumes: - db-data:/var/lib/mysql command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s timeout: 5s retries: 5 volumes: db-data: photo-data: 32
  19. AWS Transform custom の設定ファイル(config.yaml) codeRepositoryPath: ./step1-legacy transformationName: AWS/spring-boot-version-upgrade buildCommand: mvn

    clean install validationCommands: | mvn checkstyle:check mvn verify additionalPlanContext: | 途中省略 ## 実施してほしい変更(優先度順) 途中省略 ## 検証について - buildCommand・validationCommands(mvn checkstyle:check, mvn verify)を 実際に実行し、全て成功する状態まで修正を繰り返してから完了としてください。 ビルド・テストを一度も実行せずに「完了」と報告することは禁止します - checkstyle違反を消すためだけに、使っていないimport文を追加したまま 残す、あるいはテストコードを無効化・削除・skipすることで検証を 通すことは禁止します。原因そのものを修正してください (パス・リクエスト/レスポンス形式) 1. pom.xml の spring-boot-starter-parent のバージョンを 4.1.x に更新し、 java.version プロパティを 25 に変更する 2. ソースコード全体で javax.* パッケージを jakarta.* に置換する (persistence, validation, servlet の各名前空間) 3. WebSecurityConfigurerAdapter を継承するクラスを廃止し、 SecurityFilterChain を返す @Bean メソッドへ書き換える 4. antMatchers() を requestMatchers() に、authorizeRequests() を authorizeHttpRequests() に置換し、and() チェーンをラムダDSLへ書き換える 5. Jackson の ObjectMapper 利用箇所(本体コードおよびテストコード)の import を tools.jackson.databind.ObjectMapper に更新する (アノテーションの import は変更しない) 6. MySQL Connectorの依存関係を mysql:mysql-connector-java から com.mysql:mysql-connector-j へ切り替える 7. thymeleaf-extras-springsecurity5 の依存関係を thymeleaf-extras-springsecurity6 へ 切り替え、Thymeleafテンプレート内のnamespace宣言も合わせて更新する 8. Flyway の依存関係を、flyway-core の直接依存のままにせず spring-boot-starter-flyway 経由に切り替える。 Spring Boot 4.1では flyway-core を直接依存させるだけではFlyway自体が起動せず、 アプリ起動時にテーブルが1つも作成されない(Schema validation: missing table エラーになる)ため、これは必須の変更です 9. Dockerfile のビルドステージ・実行ステージのベースイメージを、pom.xmlの java.version(25)に合わせて更新する (maven:3.9-eclipse-temurin-25 / eclipse-temurin:25-jre)。 pom.xmlだけJava 25にしてDockerfileをJava 11のまま放置すると、 コンテナビルド時に「release version 25 not supported」で失敗します 右側に続く 33
  20. AWS Transform custom の変換結果の例 @Configuration @EnableWebSecurity public class SecurityConfig extends

    WebSecurityConfigurerAdapter { 変換前 @Configuration @EnableWebSecurity public class SecurityConfig { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/css/**", "/js/**").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .defaultSuccessUrl("/employees", true) .permitAll() .and() .httpBasic() .and() .logout() .logoutSuccessUrl("/login?logout") .permitAll() .and() .csrf() .ignoringAntMatchers("/api/**"); } } 変換後 @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/css/**", "/js/**").permitAll() .anyRequest().authenticated() ) .formLogin(form -> form .loginPage("/login") .defaultSuccessUrl("/employees", true) .permitAll() ) .httpBasic(httpBasic -> { }) .logout(logout -> logout .logoutSuccessUrl("/login?logout") .permitAll() ) .csrf(csrf -> csrf .ignoringRequestMatchers("/api/**") ); return http.build(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { PasswordEncoder encoder = PasswordEncoderFactories .createDelegatingPasswordEncoder(); auth.inMemoryAuthentication() .withUser("admin") .password(encoder.encode("admin123")) .roles("ADMIN"); } } @Bean public UserDetailsService userDetailsService() { PasswordEncoder encoder = PasswordEncoderFactories .createDelegatingPasswordEncoder(); var user = User.withUsername("admin") .password(encoder.encode("admin123")) .roles("ADMIN") .build(); return new InMemoryUserDetailsManager(user); } 34
  21. AWS Transform custom で対応しきれなかったもの 項目 内容 指示見直し後再実行 で解消 Dockerfile Dockerfile

    のベースイメージが Java 11 のままで、pom.xml の java.version: 25 と矛盾し、コンテナビルドが失敗する ◦ 未使用のインポート SecurityConfig.java に未使用の import が残り、Checkstyleが失敗す る ◦ flyway-core の直接依存 flyway-core の直接依存のままで spring-boot-starter-flyway に切り 替わっておらず、Flyway自体が起動せずテーブルが作成されない ◦ プラグインのバージョン maven-checkstyle-pluginが3.3.1のままで、includeTestSourceRoots パラメータが認識されず警告が出ていた (テストソースがCheckstyle対象から漏れる可能性があった) ⚫ 対応できなかったものは指示を具体化して再実行かけることで成功するパターンもあります。 ⚫ とはいえ、細かい部分でカバーしきれないものは存在するので100%にはなり得ません。 ⚫ Transform customで綺麗に移行することに拘るのも効率的ではないので、 初回で対応できなかったものについて指示を具体化するか、 直接修正かけるかはコスト・スケジュールとの相談となります。 35
  22. AIと仕様駆動開発で修正したコードの例① @Service public class PhotoStorageService { 修正前 @Service public class

    EmployeePhotoService { 修正後 private final String uploadDir; private final FileStorageService fileStorageService; public PhotoStorageService(@Value("${app.upload.dir}") String uploadDir) { this.uploadDir = uploadDir; } public EmployeePhotoService(FileStorageService fileStorageService) { this.fileStorageService = fileStorageService; } public String store(MultipartFile file) { if (file == null || file.isEmpty()) { throw new IllegalArgumentException("アップロードされた写真が空です"); } if (!ALLOWED_CONTENT_TYPES.contains(file.getContentType())) { throw new IllegalArgumentException( "対応していないファイル形式です(jpg/png/webpのみ): " + file.getContentType()); } public String store(MultipartFile file) { if (file == null || file.isEmpty()) { throw new IllegalArgumentException("アップロードされた写真が空です"); } if (!ALLOWED_CONTENT_TYPES.contains(file.getContentType())) { throw new IllegalArgumentException( "対応していないファイル形式です(jpg/png/webpのみ): " + file.getContentType()); } try { Path dir = Paths.get(uploadDir); Files.createDirectories(dir); String key = "employee-photos/" + UUID.randomUUID() + extractExtension(file.getOriginalFilename()); try { fileStorageService.store(key, file.getInputStream(), file.getContentType(), file.getSize()); } catch (IOException e) { throw new UncheckedIOException("写真の保存に失敗しました", e); } return key; String extension = extractExtension(file.getOriginalFilename()); String photoKey = UUID.randomUUID() + extension; Path target = dir.resolve(photoKey); } file.transferTo(target); return photoKey; } catch (IOException e) { throw new RuntimeException("写真の保存に失敗しました", e); } public InputStream load(String photoKey) { return fileStorageService.load(photoKey); } } } public InputStream load(String photoKey) { try { Path target = Paths.get(uploadDir).resolve(photoKey); return Files.newInputStream(target); } catch (IOException e) { throw new RuntimeException("写真の読み込みに失敗しました: " + photoKey, e); } } } public interface FileStorageService { void store(String key, InputStream inputStream, String contentType, long size); InputStream load(String key); void delete(String key); boolean exists(String key); } @Service @Profile("aws") public class S3FileStorageService implements FileStorageService { 36
  23. AIと仕様駆動開発で修正したコードの例② EmployeePhotoService 呼び出し FileStorageService インターフェース 実装 実装 @Profile("local") LocalFileStorageService @Profile(”aws")

    S3FileStorageService ローカル開発の時はこちら ローカルディスクに保存 本番環境はこちら Amazon S3に保存 ※事前にAIにローカル開発を意識させないと、このようなコードを書いてくれない時があります。 37
  24. 静的閾値と異常検知(Amazon CloudWatch Anomaly Detection) ⚫ メトリクスにアラームを設定し、異常を早期検知可能にします。 ⚫ 明確に閾値が設けられるものは、静的な閾値でアラームを設定。 ⚫ 静的な閾値を設けるのが難しいものは、

    CloudWatch Anomaly Detection(機械学習による異常検知)が有効です。 エラー数など静的な閾値が定められるものは静的アラーム リクエスト数など傾向から判断するしかないものはAnomaly Detection 引用:アラーム評価 - Amazon CloudWatch 引用:CloudWatch 異常検出の使用 - Amazon CloudWatch 41
  25. 継続的な改善を維持する体制を整える ⚫ CI/CDパイプラインを構築し、セキュリティチェック、テスト、デプロイを自動化します。 ⚫ 基本的にテストの範囲は少しずつ広げていくのが望ましいです。 新機能から自動テストの対象を広げ、 現新比較への依存を段階的に減らしていきます。 ⚫ AWS Transform

    custom をCI/CDパイプラインに組み込めば、 将来のフレームワーク・ライブラリアップグレードも継続的に自動化することができます。 Transform Custom パイプライン 月一度の定期実行 Tranform Custom による変換 変換結果を プルリクエストへ デプロイパイプライン プルリクエストのマージで発動 手動マージ リンター テスト セキュリティチェック デプロイ 43
  26. まとめ ⚫ モダナイズは「動いているから触らない」から「変化に強い状態を保ち続ける」への転換 ⚫ モダナイズは変化を妨げる要因の排除に注力する ⚫ 本発表では、モダナイズの工程ごとにAIの使い方を変えました 私は実作業にAIをフル活用するとしても、AIに投入する適切な単位に分けるところまでは、 AIの補助を得つつも人間主体でやるべきと考えます 工程

    AIの役割 要件定義 現行資産の読解・情報収集における壁打ち相手 基本設計 移行方式・アーキテクチャの技術的な裏取り 詳細設計・実装・単体テスト 機械的な変換作業の自動化、仕様駆動開発による コード生成 運用・保守 障害発生時のインシデント調査、異常検知の支援 45
  27. 参考資料 ⚫ ⚫ AWSサービス関連 ⚫ データベース移行関連  AWS Transform custom

     MySQL 8.0 Reference Manual: Sorting Rows  AWS Transform custom: AI-driven Java modernization to reduce tech debt  Removal of implicit and explicit sorting for GROUP BY  Amazon ECS on AWS Fargate(ユーザーガイド)  AWS Lambda Web Adapter(GitHub)  Amazon Aurora MySQLについて  Aurora MySQL version 3(MySQL 8.0互換)  Amazon ElastiCacheとは  AWS Secrets Managerとは  AWS Cloud Development Kit (AWS CDK) v2 デベロッパーガイド  Amazon Cognitoとは  AWS Database Migration Service (DMS)とは  AWS Security Hubとは  AWS DevOps Agentについて ⚫ 運用監視関連  アラーム評価 - Amazon CloudWatch  CloudWatch 異常検出の使用 - Amazon CloudWatch  Monitoring Distributed Systems フレームワーク・言語関連  Spring Boot 2.7 Support Period Extended(Spring公式ブログ)  Spring Boot 3.0 Migration Guide(Spring公式Wiki、javax→jakarta移行)  Spring Security: Upgrading the Deprecated WebSecurityConfigurerAdapter(Baeldung) 47