사이드 프로젝트 작업을 하면서 Service 레이어
에서 결과값을 return
하는 과정에서 불필요한/불편한 코드 작업이 반복되는 경우가 있었다.
예를 들어, 아래의 코드는 어떤 모듈 Service 의 수많은 함수 중 하나의 예이다.
commitment
라는 키워드는 내가 작업 중인 사이드 프로젝트에서 사용되는 용어이므로, 읽기 복잡하다면, 단순히 item
이라는 용어로 치환해서 읽어도 무방하다.
async completeCommitment(commitmentId: string, user: User): Promise<CommitmentInfo> {
try {
const commitmentActivity = await this.commitmentActivityRepo.findOne({
where: {
commitment: { id: commitmentId },
user: { id: user.id },
},
relations: ['commitment'],
});
...
const userInfo = userInfoMapper(user);
const commitmentActivityInfo = commitmentActivityInfoMapper(commitmentActivity);
const commitmentInfo = commitmentInfoMapper(commitment, this.userInfo);
commitmentInfo.activity = commitmentActivityInfo;
return commitmentInfo;
} catch (e) {
...
}
}
코드를 보면, 데이터베이스에서 가져온 모델 데이터를 그대로 Client 에 전달 할 때 발생할 수 있는 문제들을 방지하기 위해 (User의 Password 등등… Client 에서 불필요한 정보)~~~Info
라는 형태로 데이터를 Wrapping
해주는 Mapper
를 사용하고 있다.
아래는 여러 Mapper
중, userInfoMapper
예시이다.
// 예시
const userInfoMapper = (user: User): UserInfo => {
return {
id: user?.id,
nickname: user?.nickname,
createDate: user?.createDate,
};
};
위와 같이 단순히 한 차례 필요한 데이터만 가져오도록 하는 기능이 구현되어 있다.
문제점
여기서 문제는, Service 레이어
의 여러 수많은 함수들에서, commitmentInfo
를 return
하는 과정에서
// 코드 반복
const userInfo = userInfoMapper(user);
const commitmentActivityInfo = commitmentActivityInfoMapper(commitmentActivity);
const commitmentInfo = commitmentInfoMapper(commitment, this.userInfo);
commitmentInfo.activity = commitmentActivityInfo;
return commitmentInfo;
이러한 코드가 반복 되고 있다는 것이다.
commitmentInfo
를 반환하기 위해 코드가 반복 되고 있을 뿐만 아니라, commitmentInfo
의 형태가 바뀌는 때에는 프로젝트의 모든 commitmentInfo
를 생성하고 있는 곳에서 일일이 코드를 전부 수정 해야만 하는 사태가 벌어지게 된다.
더군다나 commitmentInfo
객체를 생성한 후,
commitmentInfo.activity = commitmentActivityInfo;
이런 식으로 activity 를 연결해 주는 코드 순서 역시 조잡해 보이고 혼동하기 쉬워 보인다.
Builder 패턴을 사용하자
Builder
패턴은 이렇게 객체의 생성 절차가 복잡할 때 유용하게 적용해 볼 수 있는 패턴이다.
아래는 CommitmentInfoBuilder
코드이다.
/**
* @description 사용 예시
* const commitmentInfo = new CommitmentInfoBuilder().setUserData(user).setCommitmentData(commitment).build();
*/
export class CommitmentInfoBuilder {
private commitmentInfo: CommitmentInfo;
private userInfo: UserInfo;
private commitmentActivityInfo: CommitmentActivityInfo;
setUserData(user: User): CommitmentInfoBuilder {
this.userInfo = userInfoMapper(user);
return this;
}
setCommitmentActivityData(commitmentActivity: CommitmentActivity): CommitmentInfoBuilder {
this.commitmentActivityInfo = commitmentActivityInfoMapper(commitmentActivity);
return this;
}
setCommitmentData(commitment: Commitment): CommitmentInfoBuilder {
this.commitmentInfo = commitmentInfoMapper(commitment);
return this;
}
build(): CommitmentInfo {
if (this.userInfo) this.commitmentInfo.user = this.userInfo;
if (this.commitmentActivityInfo) this.commitmentInfo.activity = this.commitmentActivityInfo;
return this.commitmentInfo;
}
}
필요한 데이터를 set 하는 set~~~~~Data()
함수에서 자기 자신 (this
) 를 return 하고 있다.
각종 set 함수에서는, 객체에 해당 데이터를 set 할 때 필요한 로직들을 수행하고 있다. 여기선 단순히 데이터에 맞는 Mapper
함수를 실행하고 있다.
아래는 Builder
패턴을 적용하여 코드를 수정한 내용이다.
async completeCommitment(commitmentId: string, user: User): Promise<CommitmentInfo> {
try {
const commitmentActivity = await this.commitmentActivityRepo.findOne({
where: {
commitment: { id: commitmentId },
user: { id: user.id },
},
relations: ['commitment'],
});
...
const commitmentInfo = new CommitmentInfoBuilder()
.setUserData(user)
.setCommitmentActivityData(commitmentActivity)
.setCommitmentData(commitmentActivity.commitment)
.build();
return commitmentInfo;
} catch (e) {
...
}
}
Builder
의 각종 Set 함수 동작 후에, build()
를 호출하는 방식으로 코드를 수정하였다.
각 데이터를 Set 하는 부분에서 해당 로직들을 추상화 해, 데이터를 Set 하는 로직이 변경 되더라도 Builder 에서 일괄적으로 핸들링 할 수 있도록 하였고,
build()
함수에서 Set 한 데이터들을 연결해 주는 로직을 만들어서 commitmentInfo 생성 시의 로직을 핸들링 할 수 있도록 하였다.
디자인 패턴은 개발자들이 프로그래밍을 하다 만나는 어떠한 비슷한 유형의 문제들을 좀 더 잘 해결할 수 있도록 ‘패턴
’ 이라는 형태로 형식화 한 것이다.
현업에서는, 코딩하다가 문제점을 파악하고 디자인 패턴을 적용해 보는 경우가 있고, 이미 알고 있는 디자인 패턴을 토대로 코드를 고민해 가는 경우도 있는 것 같다.
'개발' 카테고리의 다른 글
블록체인 네트워크를 구성하고 장애/복구 테스트 해보기 (Geth) (0) | 2024.03.06 |
---|---|
Node 프로젝트 패키지 최적화 (bundle analyzer) (0) | 2024.03.04 |
EKS환경의 NestJS에서 원본 IP를 제대로 가져오지 못하던 문제 (X-Forwarded-For 헤더) (1) | 2024.03.02 |
백엔드 환경에서 GA4(google analytics4) 데이터 수집하기 (1) | 2024.02.21 |
백그라운드 프로세스 시스템 구축하기 (NestJS, CronJob 스케줄러) (0) | 2024.02.19 |