Skip to content

Commit

Permalink
Refactor: 루트 도메인 문자열 캐싱 전략을 변경한다.
Browse files Browse the repository at this point in the history
Refactor: 루트 도메인 문자열 캐싱 전략을 변경한다.
  • Loading branch information
hseong3243 authored Apr 28, 2024
2 parents 7c0a59e + 98f34a7 commit b98f084
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 44 deletions.
47 changes: 21 additions & 26 deletions src/main/java/com/seong/shoutlink/domain/common/Trie.java
Original file line number Diff line number Diff line change
@@ -1,46 +1,48 @@
package com.seong.shoutlink.domain.common;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import lombok.Getter;

@Getter
public class Trie {

@Getter
static class Node {

private final char c;
private final Map<Character, Node> children = new HashMap<>();
private final Map<Character, Node> children = new ConcurrentHashMap<>();
private boolean isWord;

public Node(char c) {
this.c = c;
}

public void setWord(boolean word) {
isWord = word;
public void settingWord() {
isWord = true;
}

public boolean hasChildren(char c) {
return children.containsKey(c);
}

public boolean isWord() {
return isWord;
public Node nextNode(char c) {
Node nextNode = children.putIfAbsent(c, new Node(c));
return Optional.ofNullable(nextNode)
.orElseGet(() -> children.get(c));
}

public void addSuggestions(String word, List<String> suggestions, int count) {
if(isWord) {
suggestions.add(word);
}
if(suggestions.size() >= count) {
return;
}
children.forEach((character, childNode) -> {
String suggestionsWord = word + character;
if (childNode.isWord()) {
suggestions.add(suggestionsWord);
}
if (suggestions.size() >= count) {
return;
}
childNode.addSuggestions(suggestionsWord, suggestions, count);
});
}
Expand All @@ -53,40 +55,33 @@ public void addSuggestions(String word, List<String> suggestions, int count) {

private final Node root = new Node(' ');

public synchronized void insert(String word) {
if(word.length() > MAX_WORD_LENGTH) {
public void insert(String word) {
if (word.length() > MAX_WORD_LENGTH) {
return;
}

Node currentNode = root;
for (char c : word.toCharArray()) {
Map<Character, Node> children = currentNode.getChildren();
currentNode = children.getOrDefault(c, new Node(c));
children.put(c, currentNode);
currentNode = currentNode.nextNode(c);
}
currentNode.setWord(true);
currentNode.settingWord();
}

public List<String> search(String prefix, int count) {
if(prefix.length() > MAX_PREFIX_LENGTH) {
if (prefix.length() > MAX_PREFIX_LENGTH) {
prefix = prefix.substring(ZERO, MAX_PREFIX_LENGTH);
}
Node currentNode = root;
for (char c : prefix.toCharArray()) {
if (!currentNode.hasChildren(c)) {
return List.of();
}
Map<Character, Node> children = currentNode.getChildren();
currentNode = children.get(c);
currentNode = currentNode.nextNode(c);
}
return findWords(prefix, currentNode, Math.min(MAX_SUGGESTION, count));
}

private List<String> findWords(String word, Node currentNode, int count) {
List<String> suggestions = new ArrayList<>();
if (currentNode.isWord()) {
suggestions.add(word);
}
currentNode.addSuggestions(word, suggestions, count);
return suggestions;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,17 @@ public Optional<Domain> findByRootDomain(String rootDomain) {

@Override
public Domain save(Domain domain) {
return domainJpaRepository.save(DomainEntity.create(domain))
Domain savedDomain = domainJpaRepository.save(DomainEntity.create(domain))
.toDomain();
domainCacheRepository.insert(savedDomain.getRootDomain());
return savedDomain;
}

@Override
public List<String> findRootDomains(String keyword, int size) {
return domainCacheRepository.findRootDomains(keyword, size);
}

@Override
public void synchronizeRootDomains() {
domainJpaRepository.findRootDomains().forEach(domainCacheRepository::insert);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ public interface DomainRepository {

List<String> findRootDomains(String keyword, int size);

void synchronizeRootDomains();

DomainPaginationResult findDomains(String keyword, int page, int size);

Optional<Domain> findById(Long domainId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.seong.shoutlink.global.config;

import com.seong.shoutlink.domain.common.EventPublisher;
import com.seong.shoutlink.domain.domain.repository.DomainRepositoryImpl;
import com.seong.shoutlink.domain.domain.service.DomainUseCase;
import com.seong.shoutlink.domain.link.service.LinkBundleUseCase;
import com.seong.shoutlink.domain.tag.service.TagUseCase;
Expand All @@ -26,8 +27,10 @@ public LinkBundleEventListener linkBundleEventListener(LinkBundleUseCase linkBun
}

@Bean
public DomainEventListener domainEventListener(DomainUseCase domainUseCase) {
return new DomainEventListener(domainUseCase);
public DomainEventListener domainEventListener(
DomainUseCase domainUseCase,
DomainRepositoryImpl domainRepository) {
return new DomainEventListener(domainUseCase, domainRepository);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.seong.shoutlink.global.config;

import com.seong.shoutlink.domain.domain.service.DomainRepository;
import com.seong.shoutlink.domain.domain.repository.DomainRepositoryImpl;
import com.seong.shoutlink.global.scheduler.DomainScheduler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -11,7 +11,7 @@
public class SchedulerConfig {

@Bean
public DomainScheduler domainScheduler(DomainRepository domainRepository) {
public DomainScheduler domainScheduler(DomainRepositoryImpl domainRepository) {
return new DomainScheduler(domainRepository);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.seong.shoutlink.global.event;

import com.seong.shoutlink.domain.domain.repository.DomainRepositoryImpl;
import com.seong.shoutlink.domain.domain.service.DomainUseCase;
import com.seong.shoutlink.domain.domain.service.request.UpdateDomainCommand;
import com.seong.shoutlink.domain.link.service.event.CreateLinkEvent;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
Expand All @@ -12,10 +15,16 @@
public class DomainEventListener {

private final DomainUseCase domainUseCase;
private final DomainRepositoryImpl domainRepository;

@TransactionalEventListener
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateDomainInfo(CreateLinkEvent event) {
domainUseCase.updateDomain(new UpdateDomainCommand(event.linkId(), event.url()));
}

@EventListener(ApplicationReadyEvent.class)
public void warmUpCache() {
domainRepository.synchronizeRootDomains();
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.seong.shoutlink.global.scheduler;

import com.seong.shoutlink.domain.domain.service.DomainRepository;
import com.seong.shoutlink.domain.domain.repository.DomainRepositoryImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;

@RequiredArgsConstructor
public class DomainScheduler {

private final DomainRepository domainRepository;
private final DomainRepositoryImpl domainRepository;

@Scheduled(cron = "0 0 * * * *")
public void synchronizeRootDomains() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class BaseIntegrationTest {
public abstract class BaseIntegrationTest {

@Autowired
private DatabaseCleaner databaseCleaner;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.seong.shoutlink.domain.domain.repository;

import static org.assertj.core.api.Assertions.assertThat;

import com.seong.shoutlink.base.BaseIntegrationTest;
import com.seong.shoutlink.domain.domain.Domain;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

class DomainRepositoryImplTest extends BaseIntegrationTest {

@Autowired
private DomainRepositoryImpl domainRepository;

@Nested
@DisplayName("save 호출 시")
class SaveTest {

@Test
@DisplayName("성공: 루트 도메인 문자열이 캐싱된다.")
void cachingRootDomain() {
//given
Domain domain = new Domain("asdf");

//when
domainRepository.save(domain);

//then
List<String> rootDomains = domainRepository.findRootDomains("as", 10);
assertThat(rootDomains).containsExactly("asdf");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,4 @@ public DomainLinkPaginationResult findDomainLinks(Domain domain, int page, int s
public List<String> findRootDomains(String keyword, int size) {
return searchAutoComplete.search(keyword, size);
}

@Override
public void synchronizeRootDomains() {
for (Domain domain : memory.values()) {
searchAutoComplete.insert(domain.getRootDomain());
}
}
}

0 comments on commit b98f084

Please sign in to comment.