본문 바로가기

개발기술/Spring

스프링 도입의 배경 ; Java로 스프링 구현해보기

 

원본 코드 

 

문제점 

  • 1. 서비스의 어뎁터 의존관계 복잡도 증가
    • 결제서비스가 머니어뎁터를 호출해서 사용하는 1:1 형태 -> request parameter에 따라서 머니어뎁터 / 카드어뎁터를 호출
  • 2. 서비스 내 어뎁터의 세부 메소드들 호출
  • 머니어뎁터와 카드어뎁터의 세부 구현로직들이 서비스 클래스 내로 호출되어  코드 복잡성이 증가함
    • 향후 어뎁터 종류 및 세부 구현로직 증가시 코드 복잡도 증가 예상

 

package com.zerobase.convpay.service;

import com.zerobase.convpay.dto.PayCancelRequest;
import com.zerobase.convpay.dto.PayCancelResponse;
import com.zerobase.convpay.dto.PayRequest;
import com.zerobase.convpay.dto.PayResponse;
import com.zerobase.convpay.type.*;

public class ConveniencePayService { //편결이
    private final MoneyAdapter moneyAdapter = new MoneyAdapter();
    private final CardAdapter cardAdapter = new CardAdapter();


    public PayResponse pay(PayRequest payRequest) {
        CardUseResult cardUseResult;
        MoneyUseResult moneyUseResult;
        if (payRequest.getPayMethod()== PayMethodType.CARD){
            cardAdapter.authorization();
            cardAdapter.approval();
            cardUseResult = cardAdapter.capture(payRequest.getPayAmount());

        }else {
             moneyUseResult = moneyAdapter.use(payRequest.getPayAmount());
        }

        if (cardUseResult==CardUseResult.USE_FAIL||moneyUseResult == MoneyUseResult.USE_FAILED) {
            return new PayResponse(PayResult.FAIL, 0);
        }
        return new PayResponse(PayResult.SUCCESS, payRequest.getPayAmount());
    }

    public PayCancelResponse payCancel(PayCancelRequest payCancelRequest) {
        MoneyUseCancelResult moneyUseCancelResult = moneyAdapter.useCancel(payCancelRequest.getPayCancelAmount());

        if (moneyUseCancelResult == MoneyUseCancelResult.MONEY_USE_CANCEL_FAILED) {
            return new PayCancelResponse(PayCancelResult.PAY_CANCEL_FAILED, 0);
        }
        return new PayCancelResponse(PayCancelResult.PAY_CANCEL_SUCCESS, payCancelRequest.getPayCancelAmount());
    }


}

 

코드개선1 : Interface 도입 ; 

 

해결책

  • 클래스간 강한결합을 끊어냄 : Interface 도입을 통해서 DIP 적용
    • 의존관계의 클래스들 사이를 interface 거치어 호출하도록 하면 구현체 내부의 변화가 발생해도 interface를 활용한 코드는 변경이 필요없음. 대신 구현체 내부에는 특정 메소드의 구현을 강제화하여 통일성을 강화시킴.

문제점

  • 서비스 내 어뎁터의 세부 메소드들 호출문제는 해결
  • request의 parameter에 따라 인터페이스에 구현체를 주입하는 코드로 인해 클래스간 결합도 여전히 존재,
    • 서비스 클래스의 본 역할과(SRP원칙) 맞지 않는다, 어뎁터가 한가지 더 추가된다면 서비스 클래스를 수정해야함.
package com.zerobase.convpay.service;

import com.zerobase.convpay.dto.PayCancelRequest;
import com.zerobase.convpay.dto.PayCancelResponse;
import com.zerobase.convpay.dto.PayRequest;
import com.zerobase.convpay.dto.PayResponse;
import com.zerobase.convpay.type.*;

public class ConveniencePayService { //편결이
    private final MoneyAdapter moneyAdapter = new MoneyAdapter();
    private final CardAdapter cardAdapter = new CardAdapter();


    public PayResponse pay(PayRequest payRequest) {
        PaymentInterface paymentInterface;
        if (payRequest.getPayMethod()== PayMethodType.CARD){
            paymentInterface = cardAdapter;
        }else {
             paymentInterface = moneyAdapter;
        }
        if (paymentInterface.payment(payRequest.getPayAmount())==PaymentResult.PAYMENT_FAILED) {
            return new PayResponse(PayResult.FAIL, 0);
        }
        return new PayResponse(PayResult.SUCCESS, payRequest.getPayAmount());
    }

    public PayCancelResponse payCancel(PayCancelRequest payCancelRequest) {
        PaymentInterface paymentInterface;
        if (payCancelRequest.getPayMethodType()== PayMethodType.CARD){
            paymentInterface = cardAdapter;
        }else {
            paymentInterface = moneyAdapter;
        }
        CancelPaymentResult cancelPaymentResult = paymentInterface.cancelPayment(payCancelRequest.getPayCancelAmount());
        if (cancelPaymentResult == CancelPaymentResult.CANCEL_PAYMENT_FAILED) {
            return new PayCancelResponse(PayCancelResult.PAY_CANCEL_FAILED, 0);
        }
        return new PayCancelResponse(PayCancelResult.PAY_CANCEL_SUCCESS, payCancelRequest.getPayCancelAmount());
    }


}

 

코드개선2 : Configuration Class도입

 

해결책

  • 별도로 객체를 생성하고 주입해주는 역할을 별도 Class로 분리해주어야함
    • Config라는 class를 생성하여 메소드를 통해서 객체를 생성하고 의존관계를 설정해주는 기능을 분리한다.
    • 결제서비스(Class1) 생성자로 Config class를 활용하여 Interface에 Parameter로 주입할 객체를 받는 코드를 추가한다.
    • Client 코드는 Configuration class를 호출해서 주입받은 ConveniencePayService를 호출한다
public class UserClient {
    public static void main(String[] args) {
        //설정클래스로 스프링컨테이너 생성
        ApplicationContext applicationContext
                = new AnnotationConfigApplicationContext(ApplicationConfig.class);

        //컨테이너로부터 의존성을 주입받음(
        ConveniencePayService conveniencePayService
                = applicationContext.getBean("conveniencePayService",
                ConveniencePayService.class);
@Component
public class ConveniencePayService { //편결이

    private Map<PayMethodType,PaymentInterface> paymentInterfaceMap = new HashMap(); // interface class와 payment.method를 맵핑
    private DiscountInterface discountInterface;

    // ConveniencePayService 생성자 도입으로, paymentinterface로 사용할 set과 
    // discountinterface를 받음
    public ConveniencePayService(Set<PaymentInterface> paymentInterfaceSet,
                                 @Qualifier("discountByConvenience")
                                 DiscountInterface discountInterface) {
        paymentInterfaceSet.forEach(paymentInterface
                -> paymentInterfaceMap.put(
                        paymentInterface.getPayMethod(),paymentInterface));
        this.discountInterface = discountInterface;
    }


    public PayResponse pay(PayRequest payRequest) {
        // 결제수단을 판별하여 그에 맞는 어뎁터 호출하여 인터페이스에 대입하여 결제로직을 처리한다
        PaymentInterface paymentInterface = paymentInterfaceMap.get(payRequest.getPayMethod());

        // 결제로직을 처리하는 어뎁터와 별개로 금액할인은 별도의 인터페이스를 활용하여 사용한다.
        Integer discountedAmount = discountInterface.getDiscountedAmount(payRequest);

        // 할인금액을 받아서 어뎁터별로 결제로직을 돌리고 실패시에 에러메세지를 반환한다.
        if (paymentInterface.payment(discountedAmount) == PaymentResult.PAYMENT_FAILED) {
            return new PayResponse(PayResult.FAIL, 0);
        }
        return new PayResponse(PayResult.SUCCESS, discountedAmount);
    }