들어가며
웰스토리 DID(Digital Information Display) 프로젝트에 중간 투입되면서 겪은 흥미로운 경험을 공유하고자 합니다. 처음에는 제 방식대로 코드를 작성했다가, 팀의 기존 코딩 스타일과 아키텍처를 이해하고 이에 맞춰 전면 리팩토링을 진행한 과정입니다.
🔍 기존 코드의 문제점들
1. 모델 클래스의 과도한 분산
// 수정 전: 용도별로 분산된 3개의 모델
public class RestaurantSaveModel { /* 저장용 */ }
public class RestaurantQueryModel { /* 조회용 */ }
public class RestaurantResponseModel { /* 응답용 */ }
동일한 매장 정보를 다루는데도 불구하고 3개의 서로 다른 모델 클래스가 존재했습니다. 이는 코드 중복과 유지보수성 저하를 야기했습니다.
2. 수동 XSS 체크의 번거로움
// 수정 전: 각 필드마다 수동으로 XSS 체크
model.RESTAURANT_CD = CommonProperties.getXssString(model.RESTAURANT_CD);
model.RESTAURANT_NM = CommonProperties.getXssString(model.RESTAURANT_NM);
model.BRANCH_NAME = CommonProperties.getXssString(model.BRANCH_NAME);
// ... 반복
3. 복잡한 유효성 검사 로직
// 수정 전: 컨트롤러에서 하드코딩된 유효성 검사
if (string.IsNullOrEmpty(model.BRANCH_NAME))
{
resultModel.ERR_CODE = "8001";
resultModel.ERROR_MSG = "지점명을 입력해주세요.";
return Content(JsonConvert.SerializeObject(resultModel, Formatting.Indented));
}
if (!RestaurantBiz.IsValidPhoneNumber(model.CONTACT_NUMBER))
{
resultModel.ERR_CODE = "8001";
resultModel.ERROR_MSG = "올바른 전화번호 형식이 아닙니다.";
return Content(JsonConvert.SerializeObject(resultModel, Formatting.Indented));
}
4. 인라인 SQL과 비즈니스 로직 혼재
// 수정 전: Biz 레이어에서 직접 SQL 작성
const string sql = @"UPDATE DID.CMS_AUTH_REST SET
STORE_NM = :STORE_NM,
TEL_NO = :TEL_NO,
OPERATING_HOURS = :OPERATING_HOURS,
HOLIDAY_HOURS = :HOLIDAY_HOURS,
MOD_ID = :MOD_ID,
MOD_DTM = SYSDATE
WHERE RESTAURANT_CODE = :RESTAURANT_CODE";
🛠️ 리팩토링 과정
1. 모델 통합 및 Data Annotations 적용
// 수정 후: 통합된 모델과 선언적 유효성 검사
public class RestaurantModel
{
[Display(Name = "매장코드")]
public string RESTAURANT_CODE { get; set; }
[Required(ErrorMessage = "지점명을 입력해주세요.")]
[Display(Name = "지점명")]
public string STORE_NM { get; set; }
[Required(ErrorMessage = "연락처를 입력해주세요.")]
[RegularExpression(@"^(0\d{1,2}-\d{3,4}-\d{4}|01[016789]-\d{3,4}-\d{4}|070-\d{3,4}-\d{4}|1588-\d{4}|080-\d{3}-\d{4})$",
ErrorMessage = "올바른 전화번호 형식이 아닙니다. (예: 02-1234-5678, 010-1234-5678)")]
[Display(Name = "연락처")]
public string TEL_NO { get; set; }
[Required(ErrorMessage = "평일 운영시간을 입력해주세요.")]
[Display(Name = "평일 운영시간")]
public string OPERATING_HOURS { get; set; }
[Required(ErrorMessage = "휴일 운영시간을 입력해주세요.")]
[Display(Name = "휴일 운영시간")]
public string HOLIDAY_HOURS { get; set; }
}
개선점:
- 3개 모델을 2개로 통합 (조회용: RestaurantInfoModel, 저장용: RestaurantModel)
- Data Annotations를 통한 선언적 유효성 검사
- 정규표현식을 통한 전화번호 형식 검증
2. 저장 프로시저 도입
// 수정 후: 저장 프로시저 사용
public static ResultModel SaveRestaurantInfo(RestaurantModel model)
{
OracleParameter[] param =
{
new OracleParameter("P_RESTAURANT_CODE", model.RESTAURANT_CODE),
new OracleParameter("P_STORE_NM", model.STORE_NM),
new OracleParameter("P_TEL_NO", model.TEL_NO),
new OracleParameter("P_OPERATING_HOURS", model.OPERATING_HOURS),
new OracleParameter("P_HOLIDAY_HOURS", model.HOLIDAY_HOURS),
new OracleParameter("P_REG_ID", model.REG_ID),
new OracleParameter("CUR", OracleDbType.RefCursor) { Direction = ParameterDirection.Output }
};
var result = OracleHelper.ExecuteDataset(CommonProperties.ConnectionString,
CommandType.StoredProcedure,
"DID.PKG_CMS_RESTAURANT_MNG.PKG_CMS_RESTAURANT_UPDATE", param);
return Util.ConvertDataTable<ResultModel>(result.Tables[0])[0];
}
개선점:
- 기존 팀의 저장 프로시저 패턴 준수
- SQL Injection 방지
- 데이터베이스 로직과 비즈니스 로직 분리
3. ModelState를 활용한 유효성 검사
// 수정 후: 간결한 컨트롤러 로직
[HttpPost]
public ActionResult SaveRestaurantInfo(RestaurantModel model)
{
try
{
// ModelState 유효성 검사
if (!ModelState.IsValid)
{
var errorMsg = ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage)
.FirstOrDefault();
return Json(new ResultModel
{
ERR_CODE = "8001",
ERROR_MSG = "입력 데이터가 유효하지 않습니다: " + errorMsg
});
}
// 비즈니스 로직 실행
ResultModel result = RestaurantBiz.SaveRestaurantInfo(model);
return Json(result);
}
catch (Exception ex)
{
return Json(new ResultModel
{
ERR_CODE = "8999",
ERROR_MSG = "매장 정보 저장 중 예기치 못한 오류가 발생했습니다: " + ex.Message
});
}
}
4. 통합 엔드포인트 설계
// 수정 후: 키오스크용과 관리자용 통합
public static DataSet GetRestaurantInfo(RestaurantInfoModel model)
{
OracleParameter[] param =
{
new OracleParameter("P_RESTAURANT_CODE", model.RESTAURANT_CODE),
new OracleParameter("CUR", OracleDbType.RefCursor) { Direction = ParameterDirection.Output }
};
return OracleHelper.ExecuteDataset(CommonProperties.ConnectionString,
CommandType.StoredProcedure,
"DID.PKG_CMS_RESTAURANT_MNG.PR_USER_RESTAURANT_SELECT", param);
}
📊 개선 결과
정량적 개선
- 코드 라인 수: 약 40% 감소
- 모델 클래스: 3개 → 2개로 통합
- 엔드포인트: 3개 → 2개로 통합
- 유효성 검사 코드: 90% 감소
정성적 개선
- 가독성: Data Annotations로 인한 직관적인 유효성 검사 규칙
- 유지보수성: 중앙화된 모델과 저장 프로시저
- 확장성: 새로운 필드 추가 시 최소한의 코드 변경
- 일관성: 기존 팀 코딩 스타일과의 일치
🎯 얻은 교훈
1. 팀 코딩 스타일의 중요성
중간에 프로젝트에 투입될 때는 기존 아키텍처와 코딩 스타일을 먼저 파악하는 것이 중요합니다. 처음에는 제 방식대로 작성했다가, 팀의 기존 패턴을 이해하고 이에 맞춰 전면 수정하게 되었습니다.
2. 점진적 리팩토링의 효과
한 번에 모든 것을 바꾸려 하지 않고, 기존 구조를 이해한 후 단계적으로 개선하는 것이 더 효과적이었습니다.
3. 선언적 프로그래밍의 장점
Data Annotations를 활용한 선언적 유효성 검사는 코드의 가독성과 유지보수성을 크게 향상시켰습니다.
'MyStory > Deployment and management' 카테고리의 다른 글
| 친구 웨딩 이벤트 웹사이트 만들기! 중 편지 저장 흐름: AWS SQS와 MongoDB를 활용한 구현 (1) | 2025.04.24 |
|---|---|
| iOS 앱 푸시 알림 설정하기: 오류 해결부터 Firebase 연동까지 (0) | 2025.04.21 |
| 안전한 인증 시스템 구현하기: JWT, CSRF 보호 및 보안 구현 (0) | 2025.04.11 |
| GitLab CI/CD 파이프라인을 활용한 GUARDIANSVIEW 프로젝트 배포 (0) | 2025.04.09 |