diff --git a/backend/build.gradle b/backend/build.gradle index a6a53ce12..adf45185d 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -41,6 +41,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-cache' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect' @@ -70,6 +71,8 @@ dependencies { implementation 'software.amazon.awssdk:s3:2.20.121' implementation 'software.amazon.awssdk:url-connection-client:2.20.121' + implementation 'com.github.ben-manes.caffeine:caffeine' + testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured:5.3.1' diff --git a/backend/src/main/java/zipgo/admin/application/AdminService.java b/backend/src/main/java/zipgo/admin/application/AdminService.java index 7374a6c33..7196fa358 100644 --- a/backend/src/main/java/zipgo/admin/application/AdminService.java +++ b/backend/src/main/java/zipgo/admin/application/AdminService.java @@ -2,6 +2,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import zipgo.admin.dto.BrandCreateRequest; @@ -11,6 +12,10 @@ import zipgo.admin.dto.PrimaryIngredientCreateRequest; import zipgo.brand.domain.Brand; import zipgo.brand.domain.repository.BrandRepository; +import zipgo.pet.domain.Breed; +import zipgo.pet.domain.PetSize; +import zipgo.pet.domain.repository.BreedRepository; +import zipgo.pet.domain.repository.PetSizeRepository; import zipgo.petfood.domain.Functionality; import zipgo.petfood.domain.PetFood; import zipgo.petfood.domain.PetFoodFunctionality; @@ -31,6 +36,8 @@ public class AdminService { private final FunctionalityRepository functionalityRepository; private final PrimaryIngredientRepository primaryIngredientRepository; private final PetFoodRepository petFoodRepository; + private final BreedRepository breedRepository; + private final PetSizeRepository petSizeRepository; public Long createBrand(BrandCreateRequest request, String imageUrl) { Brand brand = request.toEntity(imageUrl); @@ -149,4 +156,10 @@ public void deletePetFood(Long petFoodId) { petFoodRepository.deleteById(petFoodId); } + @CacheEvict(cacheNames = "breeds", key = "'allBreeds'") + public Long createBreed(Long petSizeId, String breedName) { + PetSize petSize = petSizeRepository.getReferenceById(petSizeId); + Breed breed = breedRepository.save(Breed.builder().name(breedName).petSize(petSize).build()); + return breed.getId(); + } } diff --git a/backend/src/main/java/zipgo/admin/dto/BreedCreateRequest.java b/backend/src/main/java/zipgo/admin/dto/BreedCreateRequest.java new file mode 100644 index 000000000..da9a844ee --- /dev/null +++ b/backend/src/main/java/zipgo/admin/dto/BreedCreateRequest.java @@ -0,0 +1,8 @@ +package zipgo.admin.dto; + +public record BreedCreateRequest( + Long petSizeId, + String breedName +) { + +} diff --git a/backend/src/main/java/zipgo/admin/presentation/AdminController.java b/backend/src/main/java/zipgo/admin/presentation/AdminController.java index 0374dffd8..f5d2a958e 100644 --- a/backend/src/main/java/zipgo/admin/presentation/AdminController.java +++ b/backend/src/main/java/zipgo/admin/presentation/AdminController.java @@ -18,6 +18,7 @@ import zipgo.admin.application.AdminService; import zipgo.admin.dto.BrandCreateRequest; import zipgo.admin.dto.BrandSelectResponse; +import zipgo.admin.dto.BreedCreateRequest; import zipgo.admin.dto.FunctionalityCreateRequest; import zipgo.admin.dto.FunctionalitySelectResponse; import zipgo.admin.dto.PetFoodCreateRequest; @@ -127,4 +128,12 @@ public ResponseEntity deletePetFood(@PathVariable Long petFoodId) { return ResponseEntity.noContent().build(); } + @PostMapping("/breeds") + public ResponseEntity createBreeds( + @RequestBody BreedCreateRequest breedCreateRequest + ) { + Long breedId = adminService.createBreed(breedCreateRequest.petSizeId(), breedCreateRequest.breedName()); + return ResponseEntity.created(URI.create("/breeds/" + breedId)).build(); + } + } diff --git a/backend/src/main/java/zipgo/common/cache/CacheType.java b/backend/src/main/java/zipgo/common/cache/CacheType.java new file mode 100644 index 000000000..ef241cbf4 --- /dev/null +++ b/backend/src/main/java/zipgo/common/cache/CacheType.java @@ -0,0 +1,16 @@ +package zipgo.common.cache; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum CacheType { + + BREEDS("breeds", 1), + ; + + private final String name; + private final int maxSize; + +} diff --git a/backend/src/main/java/zipgo/common/config/CacheConfig.java b/backend/src/main/java/zipgo/common/config/CacheConfig.java new file mode 100644 index 000000000..a4cf990b6 --- /dev/null +++ b/backend/src/main/java/zipgo/common/config/CacheConfig.java @@ -0,0 +1,39 @@ +package zipgo.common.config; + +import java.util.Arrays; +import java.util.List; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.caffeine.CaffeineCache; +import org.springframework.cache.support.SimpleCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import zipgo.common.cache.CacheType; + +@EnableCaching +@Configuration +public class CacheConfig { + + @Bean + public CacheManager cacheManager() { + SimpleCacheManager cacheManager = new SimpleCacheManager(); + cacheManager.setCaches(caches()); + return cacheManager; + } + + private List caches() { + return Arrays.stream(CacheType.values()) + .map(cacheType -> new CaffeineCache(cacheType.getName(), cache(cacheType))) + .toList(); + } + + private Cache cache(CacheType cacheType) { + return Caffeine.newBuilder() + .maximumSize(cacheType.getMaxSize()) + .build(); + } + +} diff --git a/backend/src/main/java/zipgo/pet/application/PetQueryService.java b/backend/src/main/java/zipgo/pet/application/PetQueryService.java index d7af33cf4..4eae65f9c 100644 --- a/backend/src/main/java/zipgo/pet/application/PetQueryService.java +++ b/backend/src/main/java/zipgo/pet/application/PetQueryService.java @@ -31,7 +31,7 @@ public Pet readPet(Long petId) { } public List readBreeds() { - Breeds breeds = Breeds.from(breedRepository.findAll()); + Breeds breeds = Breeds.from(breedRepository.findAllBreeds()); return breeds.getOrderedBreeds(); } diff --git a/backend/src/main/java/zipgo/pet/domain/repository/BreedRepository.java b/backend/src/main/java/zipgo/pet/domain/repository/BreedRepository.java index 3f31cdb2f..1a7fc0432 100644 --- a/backend/src/main/java/zipgo/pet/domain/repository/BreedRepository.java +++ b/backend/src/main/java/zipgo/pet/domain/repository/BreedRepository.java @@ -2,6 +2,8 @@ import java.util.List; import java.util.Optional; + +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.jpa.repository.JpaRepository; import zipgo.pet.domain.Breed; import zipgo.pet.domain.PetSize; @@ -9,6 +11,11 @@ public interface BreedRepository extends JpaRepository { + @Cacheable(cacheNames = "breeds", key = "'allBreeds'") + default List findAllBreeds() { + return findAll(); + } + Optional findByPetSizeAndName(PetSize petSize, String name); default Breed getByPetSizeAndName(PetSize petSize, String name) { diff --git a/backend/src/main/java/zipgo/review/application/ReviewQueryService.java b/backend/src/main/java/zipgo/review/application/ReviewQueryService.java index 5cbb77d5d..ca82b165f 100644 --- a/backend/src/main/java/zipgo/review/application/ReviewQueryService.java +++ b/backend/src/main/java/zipgo/review/application/ReviewQueryService.java @@ -116,7 +116,7 @@ public GetReviewMetadataResponse getReviewMetadata() { } private List findAllBreeds() { - Breeds breeds = Breeds.from(breedRepository.findAll()); + Breeds breeds = Breeds.from(breedRepository.findAllBreeds()); return breeds.getOrderedBreeds().stream() .map(breed -> new Metadata(breed.getId(), breed.getName())) .toList();