Series/실전!

느슨한 결합도의 설계를 위해! (1)

Hyunec 2021. 12. 3. 22:29

높은 응집도와 느슨한 결합도의 설계는 객체 지향을 공부하는 사람이라면 누구나 고민하는 주제입니다.

공부는 했었지만 부끄럽게도 잘 활용하지 못하던 중 같이 공부하는 로치의 글을 보고, 또 운 좋게 설계가 필요한 업무를 맡게 되면서 개발한 내용을 기록해봅니다.

 

 

응집도와 결합도

응집도와 결합도는 소프트웨어 품질을 결정 짓는 요소이다. 대다수의 사람들은 코드를 작성할때 "응집도" 와 "결합도" 를 생각하지 않은채 관성적으로 코드를 적고는 한다. 응집도와 결합도는

devroach.tistory.com

업무 배경

  1. 알림 톡 발송의 주체는 외부 업체로 Lambda를 통해 연동되어 있습니다.
  2. 발송 기록은 저장되고 있습니다. (백엔드 - RDB, Lambda - DynamoDB)

 

개발 요건

  1. Lambda API의 주소가 변경되었습니다. (v1 -> v2)
  2. v2에는 jwt 인증이 추가되었습니다.
    1. jwt 만료 시간은 5분이며 refresh token 은 없습니다.

 

AS-IS 분석

  1. 개행을 좀 더 의미 있게 할 수 있을 것 같습니다.
  2. 모든 환경 변수가 @Value로 각 서비스에 반복되어 주입되고 있습니다.
  3. 환경 변수 외의 공통 변수는 ApiGatewayCommon에 관리되고 있지만 좀 더 잘 관리할 수 있을 것 같습니다.
  4. 상수들이 public으로 선언되어 있습니다. 그리고 좀 더 일관성 있게 관리하고 싶습니다.
  5. validation 로직이 반복되고 있습니다.
  6. 모든 서비스에서 Lambda 호출 로직이 반복되고 있습니다. 그리고 실패 로직이 너무 복잡합니다.
  7. Request는 각 service 별로 다르기에 inner class로 만든 것은 좋지만, 좀 더 정제할 수 있을 것 같습니다.
  8. Response는 모든 service에서 같기에 중복을 제거할 수 있을 것 같습니다.

 

AS-IS service 중 하나를 각색한 코드를 첨부하며 다음 글에서는 개선 과정을 기록해보겠습니다.

 

2021.12.03 - [Computer Science/Software Architecture] - 느슨한 결합도의 설계를 위해! (1)

 

@Service
@RequiredArgsConstructor
public class ResetPasswordService {

 // 1. 환경 변수로 관리되는 baseUrl
 @Value("${aws.lambda.apiGateway}")
 public String BASE_URL;

 // 2. public static final 변수들
 public static final String TEMPLATE_CODE = "/company/resetpw";

 public static final Supplier<CustomGraphQLException> RESET_PASSWORD_EXCEPTION =
   CustomGraphQLException.supply("M0001");

 public static final Integer RESET_PASSWORD_LIMIT = 10;

 private final RestTemplate restTemplate;

 private final SysSmsSendService sysSmsSendService;

 public void send(String receiveNumber, String paramsPassword) {
  Request request = Request.of(receiveNumber, paramsPassword);

  // 3. validation 로직과 일치하지 않는 메소드명
  int sendCount = sysSmsSendService.getResetPassword(request.getReceiveNumber());

  if (sendCount >= RESET_PASSWORD_LIMIT) {
   throw RESET_PASSWORD_EXCEPTION.get();
  }

  HttpEntity<?> entity = new HttpEntity<>(request, getHttpHeaders());

  ResponseEntity<Response> exchange =
    restTemplate.exchange(BASE_URL + TEMPLATE_CODE, POST, entity, Response.class);

  // 4. 복잡한 API 호출 정상 여부 판단
  if (isNull(exchange.getBody()) || isNull(exchange.getBody().getText())
    || !SUCCESS.equals(exchange.getBody().getText())) {
   throw CustomGraphQLException.supply("API GATEWAY 통신 오류").get();
  }

  // 5. 전송 기록
  sysSmsSendService.insertSysSmsSend(...);
 }

 public static class Request {

  private String templateCode;
  private String receiveNumber;
  private String sendingNumber;
  private String paramsPassword;
  private String paramsLoginUrl;

  public static Request of(String receiveNumber, String paramsPassword) {
   // 6. 카카오톡 templateCode
   String templateCode = "company_reset_pw";
   // 7. 작동 환경에 따라 달라지는 redirect url
   String paramsLoginUrl = "https://prod.company.com/login";
   if (isStageServer()) {
    paramsLoginUrl = "https://stage.company.co.kr/login";
   }
   if (isDevServer()) {
    paramsLoginUrl = "https://dev.company.co.kr/login";
   }
   return new Request(
     templateCode,
     SplitAndJoinUtils.joinCountryContactsNumber(receiveNumber),
     // 8. 공통으로 관리되는 발송 번호
     ApiGatewayCommon.SENDING_NUMBER,
     paramsPassword,
     paramsLoginUrl
   );
  }
 }

 // 9. 공통 응답
 public static class Response {

  private String messageId;
  private String to;
  private String status;
  private String text;
 }
}