엔티티에 의존성 주입을 해야할까?

2023. 3. 8. 19:37프로젝트/[Sleeper] 수면관리 어플리케이션

728x90

프로젝트를 리팩토링 하던 도중에 아래와 같은 에러를 만났다.

 

java.lang.NullPointerException
	at econo.app.sleeper.domain.character.XpPolicy.calculateXp(XpPolicy.java:12)
	at econo.app.sleeper.service.character.CharacterService.updateCharacterXp(CharacterService.java:54)
	at econo.app.sleeper.service.character.CharacterService$$FastClassBySpringCGLIB$$40ddb51d.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
    	...

 

 

 

현재 상황

 

@RequiredArgsConstructor
public abstract class XpPolicy {

    private Sleep sleep;

    public Integer calculateXp(Long level){
        Integer increasingExperience = sleep.calculateXp();
        Integer bonusExperience = calculateBonusXp(increasingExperience, level);
        return increasingExperience + bonusExperience;
    }

    public abstract Integer calculateBonusXp(Integer increasingExperience, Long level);

}

위와 같이 추상클래스가 있고, XpPolicy를 구현하고 있는 FixedBonusXpPolicy 와 PercentBonusXpPolicy 클래스가 있는 상황입니다.

 

@Component
public class FixedBonusXpPolicy extends XpPolicy {
    @Override
    public Integer calculateBonusXp(Integer increasingExperience, Long level) {
        Integer bonusXp = XpOfIncrease.getFixedBonusXpOfLevel(level);
        return bonusXp;
    }

}


@Component
@Primary
public class PercentBonusXpPolicy extends XpPolicy {
    @Override
    public Integer calculateBonusXp(Integer increasingExperience, Long level) {
        Integer percentOfBonusXp = XpOfIncrease.getPercentOfBonusXp(level);
        long bonusXp = Math.round(increasingExperience * ((100 + percentOfBonusXp)/100));
        return (int)bonusXp;
    }
}

 

그리고, 문제가 발생한 부분은 아래와 같습니다.

 

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class CharacterService {

    private final CharacterRepository characterRepository;

    private final XpPolicy xpPolicy;
    
 	@Autowired
    public CharacterService(XpPolicy xpPolicy, CharacterRepository characterRepository){
        this.xpPolicy = xpPolicy;
        this.characterRepository = characterRepository;
    }
    
    @Transactional
    public void updateCharacterXp(Long characterPk) {
        Character character = characterRepository.find(characterPk)
                .orElseThrow(() -> new RestApiException(CommonErrorCode.RESOURCE_NOT_FOUND));
        Integer xp = xpPolicy.calculateXp(character.getLevel());
        character.plusXp(xp);
    }
    
    ...
}

 

여기서는 , XpPolicy의 구현체인 FixedBonusXpPolicy 와 PercentBonusXpPolicy 클래스중 @Primary가 설정되어있는 PercentBonusXpPolicy 클래스가 주입됩니다.

 

디버깅 / 문제 해결

 

추상클래스 XpPolicy의 필드인 sleep이 null값이 나왔다.

 

 

이를 해결할 수 있는 방법은 아래와 같다.

 

  1. 엔티티에 의존성 주입을 받게 한다. -> load time weaving 사용
  2. sleep을 calculateXp의 매개변수로 사용한다.
  3. sleep의 calculateXp 메서드를 서비스 계층에서 처리한다.

 

1번의 경우 XpPolicy와 Sleep의 의존성이 너무 강하게 생깁니다.

2번의 경우 XpPolicy와 Sleep의 의존성이 약해지지만, characterService가 Sleep 엔티티를 의존하게 됩니다.

3번의 경우, characterService가 sleepService를 의존하게 됩니다.

 

위 3가지 중 3번이 의존성 관점에서 제일 낫다고 생각이 들어, 3번으로 문제를 해결하였습니다.

또한, 3번을 적용할 경우에 XpPolicy를 추상클래스를 사용할 이유가 없어져서 인터페이스로 변경하였습니다!!

 

public interface XpPolicy {
    Integer calculateXp(Integer increasingExperience, Long level);

}

 

 

 

이상입니다~

 

 

 

728x90