e 커머스에서 카테고리를 구현할 때 국제화에 대해서 고민을 안했던것 같아서
이번 기회에 구현해보고자 한다.
@Id
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private Category parent;
private Long depth;
@OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
private List<CategoryName> categoryNames = new ArrayList<>(); // 다국어 이름들
@OneToMany(mappedBy = "parent")
private List<Category> children = new ArrayList<>();
public class CategoryName {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name; // 카테고리 이름
private String languageCode; // 예: "en", "ko", "jp" 등
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
private Category category;
}
연관을 시켜서 laguageCode와 이름을 등록하고 있다.
laguageCode로 어떤 언어인지 체크하고 그에 해당하는 이름을 띄워주는것으로 국제화 코드에 대한 문제를 해결하려고 했다.
문제. 중복되는 네임을 유지하는게 좋은지 아니면 지우는게 좋은지 잘 모르겠다..
// 특정 깊이를 기반으로 카테고리 가져오기
public List<CategoryResult> getCategoriesByDepthAndLanguage(int depth, String languageCode) {
List<Category> topLevelCategories = categoryRepository.findAllByParentIsNull();
return topLevelCategories.stream()
.map(category -> mapCategoryWithDepthAndLanguage(category, depth, languageCode))
.collect(Collectors.toList());
}
// 무제한 깊이로 모든 카테고리 가져오기
public List<CategoryResult> getAllCategories(String languageCode) {
List<Category> topLevelCategories = categoryRepository.findAllByParentIsNull();
return topLevelCategories.stream()
.map(category -> mapCategoryWithDepthAndLanguage(category, Integer.MAX_VALUE, languageCode))
.collect(Collectors.toList());
}
// 이름과 깊이에 맞춰 카테고리를 변환
private CategoryResult mapCategoryWithDepthAndLanguage(Category category, int requestedDepth, String languageCode) {
List<CategoryResult> children = mapChildren(category, requestedDepth, languageCode);
// CategoryName에서 언어 코드에 맞는 이름을 찾음
String name = category.getCategoryNames().stream()
.filter(nameEntity -> nameEntity.getLanguageCode().equals(languageCode))
.map(CategoryName::getName)
.findFirst()
.orElseGet(() -> category.getCategoryNames().stream()
.filter(nameEntity -> nameEntity.getLanguageCode().equals("ko"))
.map(CategoryName::getName)
.findFirst()
.orElse("이름 없음"));
CategoryResult result = CategoryResult.of(category, name);
result.setChildren(children);
return result;
}
// 깊이와 언어 코드로 자식 카테고리들 변환
private List<CategoryResult> mapChildren(Category category, int requestedDepth, String languageCode) {
if (category.getDepth() < requestedDepth) {
return category.getChildren().stream()
.map(child -> mapCategoryWithDepthAndLanguage(child, requestedDepth, languageCode))
.collect(Collectors.toList());
}
return Collections.emptyList();
}
깊이에 따라 카테고리를 반환하고 Language에 해당하는 이름을 찾고 찾지못한다면 디폴트인 ko 기준으로 찾는다.
그렇게 자식의 트리에 입력된 깊이에 해당된 모든 카테고리를 가져온다.
http://localhost:8080/categories?lang=en 라는 요청을 보내게 되면

라는 결과를 받게 된다.
왜 이렇게 만들었나요?
카테고리를 계층적으로 관리하기 때문에 결국 유연하게 관리할 수 있다. 만약 depth가 4로 늘어나게 되어도 그대로 depth만 늘리면 해결되는 부분이며 더 많은 children이 생겨도 더 아무런 문제가 없다.
검색의 장점이나 대규모 데이터에 대한 장점이 있다는데 솔직히 아직 나에게는 와닿지 않는다.
e커머스이기 때문에 해외 트래픽에도 대응할 수 있는 느낌으로 만들면 좋지 않을까 라는 생각에 추가하고 싶었다.