확장에 열려 있고 수정에 닫혀 있는 구조를 설계할 때 가장 핵심이 되는 개념은 개방-폐쇄 원칙(Open-Closed Principle, OCP)이다.
하지만 프로젝트 마무리 때 긴급하게 하느라 이것을 위반하고 그저 하드 코딩을 했었다.
public void saveRelationsByCategory(Afternote afternote, AfternoteCreateRequest request) {
// 모든 카테고리에서 receiver 저장 (옵션)
if (request.getReceivers() != null && !request.getReceivers().isEmpty()) {
saveReceivers(afternote, request);
}
switch (request.getCategory()) {
case SOCIAL:
saveSocialCredentials(afternote, request);
break;
case GALLERY:
// receivers는 위에서 이미 처리됨
break;
case PLAYLIST:
savePlaylist(afternote, request);
break;
}
}
나름대로의 확장성을 추구했지만 저 밑의 헬퍼함수들이 늘어가면서 코드들은 계속해서 길어져 갔고 카테고리가 생긴다면 훨씬 더 복잡해질 것 같았다. 또한 비슷한 코드가 updateRelationByCategory에도 존재했기 때문에 다른 전략을 구사해야할 것 같았다.
Strategy + Factory pattern 적용
그래서 나는 strategy 와 Factory pattern을 적용하고자 마음 먹었다. 카테고리가 아무리 추가 되더라도 나는 그에 맞는 Strategy를 추가해주기만 하면 기존 코드들을 수정할 필요가 없는 것이다.
package com.afternote.domain.afternote.service.relation;
import com.afternote.domain.afternote.dto.AfternoteCreateRequest;
import com.afternote.domain.afternote.model.Afternote;
import com.afternote.domain.afternote.model.AfternoteCategoryType;
public interface AfternoteCategoryRelationStrategy {
AfternoteCategoryType category();
void save(Afternote afternote, AfternoteCreateRequest request);
void update(Afternote afternote, AfternoteCreateRequest request);
}
그래서 일단 interface를 만들어 save와 update 전략을 각각의 factory 안에 넣어놓기로 했다.
package com.afternote.domain.afternote.service.relation;
import com.afternote.domain.afternote.model.AfternoteCategoryType;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
@Component
@RequiredArgsConstructor
public class AfternoteRelationStrategyFactory {
private final List<AfternoteCategoryRelationStrategy> strategies;
private final Map<AfternoteCategoryType, AfternoteCategoryRelationStrategy> strategyMap =
new EnumMap<>(AfternoteCategoryType.class);
@PostConstruct
void init() {
for (AfternoteCategoryRelationStrategy strategy : strategies) {
if (strategyMap.containsKey(strategy.category())) {
throw new IllegalStateException("Duplicate relation strategy for category: " + strategy.category());
}
strategyMap.put(strategy.category(), strategy);
}
}
public AfternoteCategoryRelationStrategy get(AfternoteCategoryType categoryType) {
AfternoteCategoryRelationStrategy strategy = strategyMap.get(categoryType);
if (strategy == null) {
throw new IllegalStateException("No relation strategy for category: " + categoryType);
}
return strategy;
}
}
그래서 이런식으로 만들어버리면 Map 형태로 strategy가 저장되어서 런타임 중에도 바꿀 수도 있다는 유연성을 얻을 수 있었고, 코드 중복들을 없앨 수가 있었다.
public void saveRelationsByCategory(Afternote afternote, AfternoteCreateRequest request) {
// 모든 카테고리에서 receiver 저장 (옵션)
if (request.getReceivers() != null && !request.getReceivers().isEmpty()) {
saveReceivers(afternote, request);
}
AfternoteCategoryRelationStrategy strategy = relationStrategyFactory.get(request.getCategory());
strategy.save(afternote, request);
}
그래서 실제 활용할 때 strategy를 category를 따라 불러와서 저장되어 있는 save 혹은 update를 사용할 수가 있는 것이다.
validation도 마찬가지로 적용 완료 했다.
'Project > Afternote' 카테고리의 다른 글
| [CDN] S3 presigned URL에서 CDN 도메인 전환하기 (0) | 2026.04.12 |
|---|---|
| [Github Action] CI/CD 적용기 (0) | 2026.04.05 |
| [VPC/EC2/RDS] 인프라 적용기 (0) | 2026.03.29 |
| [트러블 슈팅] Test 환경 구축 (0) | 2026.03.22 |
| [암호화] 간단 공부 (0) | 2026.01.30 |