4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Spring BootからHelidonへの移行:AIを活用したモダナイゼーション (Part 2)

Posted at

Part1では、Helidonの概要と移行のためのプロンプト準備について解説しました。Part 2では、Pythonスクリプトを使用して実際にコード変換を自動化し、Helidonプロジェクトとしてビルドするまでの手順を説明します。

3.5 ファイル分類スクリプトの作成 (Classifier)

AIに適切なプロンプトを渡すためには、各Javaファイルがどの役割(Controller, Service, Repository, Entity)を持っているかを判別する必要があります。

3.5.1 classify.py の作成

nano classify.py

import os

def classify(path):
    text = open(path).read()

    if path.endswith("pom.xml"):
        return "pom"

    if "@RestController" in text:
        return "controller"

    if "JpaRepository" in text or "CrudRepository" in text:
        return "repo"

    if "@Service" in text or "@Component" in text:
        return "service"

    if "@Entity" in text:
        return "entity"

    return "java"

if __name__ == "__main__":
    base_dir = "../spring-petclinic"
    
    for root, dirs, files in os.walk(base_dir):
        for file in files:
            if file.endswith(".java") or file.endswith("pom.xml"):
                path = os.path.join(root, file)
                ftype = classify(path)
                print(f"{path}:{ftype}")

動作確認:

python3 classify.py

3.6 変換実行スクリプトの作成

OpenAI API(または互換API)を呼び出してコードを変換するメインスクリプトを作成します。

3.6.1 APIキーの設定

export OPENAI_API_KEY="YOUR_API_KEY"

3.6.2 convert.py の作成

nano convert.py

import os
from openai import OpenAI
from concurrent.futures import ThreadPoolExecutor, as_completed
from threading import Lock
import time
from datetime import datetime

# 環境変数から設定を読み込み
api_key = (os.getenv("OPENAI_API_KEY") or "").strip()
base_url = (os.getenv("OPENAI_BASE_URL") or "").strip()
model_name = os.getenv("MODEL_NAME", "gpt-4").strip()

if not api_key:
    raise ValueError("OPENAI_API_KEY environment variable is not set")

client = OpenAI(
    api_key=api_key,
    base_url=base_url if base_url else None
)

PROMPTS = {
    "controller": "prompts/controller.txt",
    "repo": "prompts/repo.txt",
    "service": "prompts/service.txt",
    "entity": "prompts/entity.txt",
    "pom": "prompts/pom.txt",
    "java": "prompts/entity.txt"  # fallback
}

# スレッドセーフなカウンタ
success_lock = Lock()
error_lock = Lock()
progress_lock = Lock()
success_count = 0
error_count = 0
completed_count = 0
start_time = None

def print_progress(completed, total):
    global start_time
    if total == 0: return
    
    percentage = (completed / total) * 100
    elapsed = time.time() - start_time if start_time else 0
    
    status = f"[{completed}/{total}] ({percentage:.1f}%) | {int(elapsed)}s elapsed"
    print(f"\r{status}", end="", flush=True)

def convert(path, ftype, max_retries=3):
    file_start_time = time.time()
    try:
        prompt = open(PROMPTS[ftype]).read()
    except KeyError:
        # プロンプトが定義されていないタイプはスキップまたはデフォルト処理
        return False, 0, f"No prompt for type: {ftype}"

    code = open(path).read()
    
    # コードサイズに応じたタイムアウト設定
    code_size_kb = len(code) / 1024
    timeout = min(int(60 + (code_size_kb * 2)), 600)

    messages = [
        {"role": "system", "content": prompt},
        {"role": "user", "content": code},
    ]

    last_error = None
    for attempt in range(max_retries):
        try:
            if attempt > 0:
                time.sleep(min(2 ** attempt, 10))
            
            result = client.chat.completions.create(
                model=model_name,
                temperature=0,
                messages=messages,
                timeout=timeout
            )

            output = result.choices[0].message.content

            # 出力ファイルの保存
            out_path = "output/" + path.replace("../spring-petclinic/", "")
            os.makedirs(os.path.dirname(out_path), exist_ok=True)
            with open(out_path, "w") as f:
                f.write(output)

            elapsed = time.time() - file_start_time
            return True, elapsed
        except Exception as e:
            last_error = e
            # タイムアウト以外はリトライしない等の制御をここに追加可能
    
    elapsed = time.time() - file_start_time
    return False, elapsed, str(last_error)

def convert_with_counter(path, ftype, total):
    global success_count, error_count, completed_count
    
    result_data = convert(path, ftype)
    
    with progress_lock:
        completed_count += 1
        if result_data[0]: # success
            success_count += 1
        else:
            error_count += 1
        print_progress(completed_count, total)
    
    return result_data[0]

if __name__ == "__main__":
    import sys
    
    # ファイルリストの読み込み
    tasks = []
    if os.path.exists("file-list.txt"):
        with open("file-list.txt", "r") as f:
            for line in f:
                if ":" in line:
                    path, ftype = line.strip().split(":", 1)
                    tasks.append((path.strip(), ftype.strip()))

    total = len(tasks)
    max_workers = int(os.getenv("MAX_WORKERS", "2"))
    
    print(f"Found {total} files to convert. Using {max_workers} workers.")
    start_time = time.time()
    print_progress(0, total)
    
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = [executor.submit(convert_with_counter, path, ftype, total) for path, ftype in tasks]
        for future in as_completed(futures):
            pass # 結果はコールバック内で処理済み

    total_time = time.time() - start_time
    print(f"\n\nDone. Success: {success_count}, Failed: {error_count}, Time: {total_time:.1f}s")

3.7 変換プロセスの実行

3.7.1 変換対象リストの生成

python3 classify.py > file-list.txt

3.7.2 自動変換の実行

python3 convert.py

変換されたコードは converter/output/ ディレクトリに出力されます。

3.8 Helidonプロジェクトの作成とコード統合

変換されたコードを受け入れるためのHelidon MPプロジェクトの雛形を作成します。

cd ..
mvn archetype:generate -DinteractiveMode=false \
  -DarchetypeGroupId=io.helidon.archetypes \
  -DarchetypeArtifactId=helidon-mp

生成されたプロジェクトに、converter/output/ 内のコードをコピーして統合します。その後、ビルドと実行を行います。

mvn package
java -jar target/*.jar

3.9 結果

変換が成功すれば、Spring Bootアプリケーションと同等の機能を持つHelidonアプリケーションが起動します。

image-2048x1264.png

4. 結論

AIは開発者のワークフローにおける強力なパートナーとなりつつあります。フレームワークの移行という、従来であれば多大な労力と時間を要したタスクも、AIの支援により大幅に効率化され、コスト削減が可能になります。

今回紹介した手法はSpring BootからHelidonへの移行だけでなく、適切なプロンプトを用意することで、レガシーなJava EE (javax.*) からモダンなJakarta EEへの移行など、様々なモダナイゼーションのシナリオに応用可能です。

関連リンク

4
3
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
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?