Spring & OOP
스프링은 객체지향 설계를 가능하게 만드는 인프라를 제공한다.
IoC(Inversion of Control) / DI(Dependency Injection)
AOP(Aspect Oriented Programming)
PSA(Portable Service Abstraction)
이벤트(Event)
위와 같은 기술로 작성하는 코드는 비즈니스 규칙에 집중하고, 기술 · 부가기능은 프레임워크에 위임하는 구조를 만든다.
IoC/DI - 추상화와 의존성 주입
구현 대신 인터페이스에 의존하도록 설계하고, 스프링 컨테이너가 의존성을 주입한다. 이를 통해 구현 교체가 용이해지고, 코드의 결합도를 낮출 수 있다.
객체는 인터페이스에 의존하고 구현 주입은 컨테이너가 담당
프로파일/설정만 바꿔 구현을 교체할 수 있어 테스트 · 배포 환경 분리 용이
public interface PaymentGateway {
void authorize(String orderId, long amount);
}
@Service
class TossPaymentsGateway implements PaymentGateway {
public void authorize(String orderId, long amount) { /* ... */ }
}
@Primary
@Service
class KakaoPayGateway implements PaymentGateway {
public void authorize(String orderId, long amount) { /* ... */ }
}
@Service
class PaymentService {
private final PaymentGateway gateway;
public PaymentService(@Qualifier("tossPaymentsGateway") PaymentGateway gateway) {
this.gateway = gateway;
}
}
AOP - 관심사 분리
로깅, 보안 같은 횡단 관심사를 비즈니스 로직에서 분리하여 관리할 수 있다. 스프링 트랜잭션의 @Transactional
어노테이션도 AOP 기반으로 동작한다.
로깅 · 보안 · 트랜잭션 같은 횡단 관심사를 애스펙트로 분리
프록시 기반으로 메서드 경계에서 정책을 일괄 적용하되, 남용 시 흐름 추적이 어려워지므로 범위를 명확히 관리
@Aspect
@Component
class LoggingAspect {
@Around("execution(* com.example..*Service.*(..))")
public Object around(org.aspectj.lang.ProceedingJoinPoint pjp) throws Throwable {
long start = System.nanoTime();
try {
return pjp.proceed();
} finally {
long elapsed = System.nanoTime() - start;
}
}
}
PSA(Portable Service Abstraction) - 일관된 서비스 추상화
스프링이 제공하는 일관된 서비스 추상화로, 애플리케이션 코드는 공통 인터페이스(또는 애노테이션)에 의존하고 실제 구현(라이브러리/벤더)은 빈 구성으로 교체할 수 있게 한다.
공통 인터페이스/애노테이션에 의존하고 실제 구현은 빈 구성으로 교체
캐시 · 트랜잭션·메시징 등 인프라를 환경에 맞게 바꿔도 비즈니스 코드는 그대로 유지됨
@Service
class ProductService {
@Cacheable(cacheNames = "productById", key = "#productId")
public Product getProduct(long productId) {
// DB 조회 등 비용 큰 연산
return findFromDb(productId);
}
}
// 개발 환경 - Caffeine
@Configuration
@EnableCaching
class CacheConfigDev {
@Bean
CacheManager cacheManager() {
return new org.springframework.cache.caffeine.CaffeineCacheManager("productById");
}
}
// 운영 환경 - Redis
@Configuration
@EnableCaching
class CacheConfigProd {
@Bean
CacheManager cacheManager(RedisConnectionFactory cf) {
return RedisCacheManager.builder(cf).build();
}
}
이벤트 - 모듈 간 결합도 감소
이벤트는 한 모듈의 메시지를 발행하고, 다른 모듈이 후처리를 구독하게 해 관심사를 분리하고 결합도를 낮출 수 있다.
이벤트 발행하고 관심 모듈이 구독하도록 설계해 런타임 결합도를 낮춤
AFTER_COMMIT
과 비동기 리스너로 후처리를 격리하고, Outbox · 멱등 처리로 유실과 중복을 방지 가능
public record OrderPlaced(String orderId, long amount) {
}
@Service
@RequiredArgsConstructor
class OrderService {
private final ApplicationEventPublisher events;
@Transactional
public void placeOrder(String orderId, long amount) {
// 주문 저장 등 핵심 로직
// ...
// 후처리는 이벤트로 알림
events.publishEvent(new OrderPlaced(orderId, amount));
}
}
@Component
class IssueCouponOnOrderPlaced {
// 커밋 이후 실행: DB 일관성 확보 후 외부 연동/부가작업 수행
// @Aync 을 붙이면 비동기 실행
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handle(OrderPlaced event) {
// 쿠폰 발급, 알림 발송 등
}
}
Last updated
Was this helpful?