1414import com .back .domain .scenario .repository .ScenarioRepository ;
1515import com .back .domain .scenario .repository .SceneCompareRepository ;
1616import com .back .domain .scenario .repository .SceneTypeRepository ;
17- import com .back .global .ai .dto .result .BaseScenarioResult ;
18- import com .back .global .ai .dto .result .DecisionScenarioResult ;
19- import com .back .global .ai .service .AiService ;
2017import com .back .global .common .PageResponse ;
2118import com .back .global .exception .ApiException ;
2219import com .back .global .exception .ErrorCode ;
2926import org .springframework .dao .DataIntegrityViolationException ;
3027import org .springframework .data .domain .Page ;
3128import org .springframework .data .domain .Pageable ;
32- import org .springframework .scheduling .annotation .Async ;
3329import org .springframework .stereotype .Service ;
3430import org .springframework .transaction .annotation .Transactional ;
3531
@@ -60,9 +56,6 @@ public class ScenarioService {
6056 // Object Mapper 주입
6157 private final ObjectMapper objectMapper ;
6258
63- // AI Service 주입
64- private final AiService aiService ;
65-
6659 // Scenario Transaction Service 주입
6760 private final ScenarioTransactionService scenarioTransactionService ;
6861
@@ -92,8 +85,8 @@ public ScenarioStatusResponse createScenario(Long userId,
9285 validationResult .decisionLine
9386 );
9487
95- // 3. 비동기 AI 처리 트리거 (트랜잭션 외부)
96- processScenarioGenerationAsync (scenarioId );
88+ // 3. 비동기 AI 처리 트리거 (트랜잭션 외부, 별도 Bean에서 호출 )
89+ scenarioTransactionService . processScenarioGenerationAsync (scenarioId );
9790
9891 return new ScenarioStatusResponse (
9992 scenarioId ,
@@ -278,8 +271,8 @@ private ScenarioStatusResponse handleFailedScenarioRetry(Scenario failedScenario
278271 // 1. 상태 업데이트 (트랜잭션)
279272 Long scenarioId = retryScenarioInTransaction (failedScenario .getId ());
280273
281- // 2. 비동기 AI 처리 트리거 (트랜잭션 외부)
282- processScenarioGenerationAsync (scenarioId );
274+ // 2. 비동기 AI 처리 트리거 (트랜잭션 외부, 별도 Bean에서 호출 )
275+ scenarioTransactionService . processScenarioGenerationAsync (scenarioId );
283276
284277 return new ScenarioStatusResponse (
285278 scenarioId ,
@@ -305,91 +298,6 @@ protected Long retryScenarioInTransaction(Long scenarioId) {
305298 return scenario .getId ();
306299 }
307300
308- // 비동기 방식으로 AI 시나리오 생성
309- @ Async ("aiTaskExecutor" )
310- public void processScenarioGenerationAsync (Long scenarioId ) {
311- try {
312- // 1. 상태를 PROCESSING으로 업데이트 (별도 트랜잭션)
313- scenarioTransactionService .updateScenarioStatus (scenarioId , ScenarioStatus .PROCESSING , null );
314-
315- // 2. AI 생성에 필요한 모든 데이터를 트랜잭션 내에서 미리 로드
316- Scenario scenarioWithData = scenarioTransactionService .prepareScenarioData (scenarioId );
317-
318- // 3. AI 시나리오 생성 (트랜잭션 외부에서 실행)
319- AiScenarioGenerationResult result = executeAiGeneration (scenarioWithData );
320-
321- // 4. 결과 저장 및 완료 상태 업데이트 (별도 트랜잭션)
322- scenarioTransactionService .saveAiResult (scenarioId , result );
323- scenarioTransactionService .updateScenarioStatus (scenarioId , ScenarioStatus .COMPLETED , null );
324-
325- log .info ("Scenario generation completed successfully for ID: {}" , scenarioId );
326-
327- } catch (Exception e ) {
328- // 5. 실패 상태 업데이트 (별도 트랜잭션)
329- scenarioTransactionService .updateScenarioStatus (scenarioId , ScenarioStatus .FAILED ,
330- "시나리오 생성 실패: " + e .getMessage ());
331- log .error ("Scenario generation failed for ID: {}, error: {}" ,
332- scenarioId , e .getMessage (), e );
333- }
334- }
335-
336- // AI 호출 전용 메서드 (트랜잭션 없음)
337- private AiScenarioGenerationResult executeAiGeneration (Scenario scenario ) {
338- // AI 호출 로직 (미리 로드된 데이터 사용)
339- DecisionLine decisionLine = scenario .getDecisionLine ();
340- BaseLine baseLine = decisionLine .getBaseLine ();
341-
342- // 베이스 시나리오 확보
343- Scenario baseScenario = ensureBaseScenarioExists (baseLine );
344-
345- // AI 호출 (트랜잭션 외부) with 타임아웃 (60초)
346- DecisionScenarioResult aiResult = aiService
347- .generateDecisionScenario (decisionLine , baseScenario )
348- .orTimeout (60 , java .util .concurrent .TimeUnit .SECONDS )
349- .exceptionally (ex -> {
350- log .error ("Decision scenario generation timeout or error for scenario ID: {}" , scenario .getId (), ex );
351- throw new ApiException (ErrorCode .AI_REQUEST_TIMEOUT , "시나리오 생성 시간 초과 (60초)" );
352- })
353- .join ();
354-
355- return new AiScenarioGenerationResult (aiResult );
356- }
357-
358- // 베이스 시나리오 확보 (없으면 생성)
359- private Scenario ensureBaseScenarioExists (BaseLine baseLine ) {
360- return scenarioRepository .findByBaseLineIdAndDecisionLineIsNull (baseLine .getId ())
361- .orElseGet (() -> createBaseScenario (baseLine ));
362- }
363-
364- // 베이스 시나리오 생성
365- private Scenario createBaseScenario (BaseLine baseLine ) {
366- log .info ("Creating base scenario for BaseLine ID: {}" , baseLine .getId ());
367-
368- // 1. AI 호출 with 타임아웃 (180초 - 테스트용)
369- BaseScenarioResult aiResult = aiService .generateBaseScenario (baseLine )
370- .orTimeout (180 , java .util .concurrent .TimeUnit .SECONDS )
371- .exceptionally (ex -> {
372- log .error ("Base scenario generation timeout or error for BaseLine ID: {}" , baseLine .getId (), ex );
373- throw new ApiException (ErrorCode .AI_REQUEST_TIMEOUT , "베이스 시나리오 생성 시간 초과 (180초)" );
374- })
375- .join ();
376-
377- // 2. 베이스 시나리오 엔티티 생성
378- Scenario baseScenario = Scenario .builder ()
379- .user (baseLine .getUser ())
380- .decisionLine (null ) // 베이스 시나리오는 DecisionLine 없음
381- .baseLine (baseLine ) // 베이스 시나리오는 BaseLine 연결
382- .status (ScenarioStatus .COMPLETED ) // 베이스는 바로 완료
383- .build ();
384-
385- Scenario savedScenario = scenarioRepository .save (baseScenario );
386-
387- // 3. AI 결과 적용
388- scenarioTransactionService .applyBaseScenarioResult (savedScenario , aiResult );
389-
390- return savedScenario ;
391- }
392-
393301 // 시나리오 생성 상태 조회
394302 @ Transactional (readOnly = true )
395303 public ScenarioStatusResponse getScenarioStatus (Long scenarioId , Long userId ) {
0 commit comments