카테고리 구현

김수호·2024년 11월 10일

e 커머스에서 카테고리를 구현할 때 국제화에 대해서 고민을 안했던것 같아서
이번 기회에 구현해보고자 한다.

@Entity

    @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로 어떤 언어인지 체크하고 그에 해당하는 이름을 띄워주는것으로 국제화 코드에 대한 문제를 해결하려고 했다.

문제. 중복되는 네임을 유지하는게 좋은지 아니면 지우는게 좋은지 잘 모르겠다..

@Service

   // 특정 깊이를 기반으로 카테고리 가져오기
    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 라는 요청을 보내게 되면


라는 결과를 받게 된다.

왜 이렇게 만들었나요?

1. 트리구조로 만든 이유?

카테고리를 계층적으로 관리하기 때문에 결국 유연하게 관리할 수 있다. 만약 depth가 4로 늘어나게 되어도 그대로 depth만 늘리면 해결되는 부분이며 더 많은 children이 생겨도 더 아무런 문제가 없다.

검색의 장점이나 대규모 데이터에 대한 장점이 있다는데 솔직히 아직 나에게는 와닿지 않는다.

2. 국제화 코드로 만든 이유?

e커머스이기 때문에 해외 트래픽에도 대응할 수 있는 느낌으로 만들면 좋지 않을까 라는 생각에 추가하고 싶었다.

profile
정답을 모르지만 답을 찾는 법을 알고, 그 답을 찾아낼 것이다. 그럼 괜찮지 않은가? -크리스 가드너-

0개의 댓글