높은 응집도와 느슨한 결합도의 설계는 객체 지향을 공부하는 사람이라면 누구나 고민하는 주제입니다.
공부는 했었지만 부끄럽게도 잘 활용하지 못하던 중 같이 공부하는 로치의 글을 보고, 또 운 좋게 설계가 필요한 업무를 맡게 되면서 개발한 내용을 기록해봅니다.
업무 배경
- 알림 톡 발송의 주체는 외부 업체로 Lambda를 통해 연동되어 있습니다.
- 발송 기록은 저장되고 있습니다. (백엔드 - RDB, Lambda - DynamoDB)
개발 요건
- Lambda API의 주소가 변경되었습니다. (v1 -> v2)
- v2에는 jwt 인증이 추가되었습니다.
- jwt 만료 시간은 5분이며 refresh token 은 없습니다.
AS-IS 분석
- 개행을 좀 더 의미 있게 할 수 있을 것 같습니다.
- 모든 환경 변수가 @Value로 각 서비스에 반복되어 주입되고 있습니다.
- 환경 변수 외의 공통 변수는 ApiGatewayCommon에 관리되고 있지만 좀 더 잘 관리할 수 있을 것 같습니다.
- 상수들이 public으로 선언되어 있습니다. 그리고 좀 더 일관성 있게 관리하고 싶습니다.
- validation 로직이 반복되고 있습니다.
- 모든 서비스에서 Lambda 호출 로직이 반복되고 있습니다. 그리고 실패 로직이 너무 복잡합니다.
- Request는 각 service 별로 다르기에 inner class로 만든 것은 좋지만, 좀 더 정제할 수 있을 것 같습니다.
- 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;
}
}
'Series > 실전!' 카테고리의 다른 글
Custom LocalDateUtils 리팩토링 (0) | 2022.01.04 |
---|---|
Datadog 에서 GraphQL 모니터링 맛보기 (0) | 2021.12.14 |
느슨한 결합도의 설계를 위해! (2) (0) | 2021.12.04 |
업무에 적절한 pagination 선택하기 (0) | 2021.11.07 |
기술 '잘' 선택하기 (0) | 2021.05.07 |