From e6549205a22d746cd8847040c337c55da17fe3b0 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 8 Feb 2026 23:45:51 +0300 Subject: [PATCH 1/8] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=BA=D1=8D=D1=88=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=81=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20Redis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлен RedisCacheConfig для настройки кэширования - Обновлён application.yml для конфигурации Redis - Добавлены аннотации @Cacheable и @CacheEvict в сервисы для категорий, событий, тегов, пользователей и проектов - Обновлён docker-compose.yml для запуска Redis --- build.gradle | 2 ++ docker-compose.yml | 7 +++++- .../eventhub/EventHubApplication.java | 4 +-- .../eventhub/config/RedisCacheConfig.java | 25 +++++++++++++++++++ .../service/impl/CategoryReadService.java | 2 ++ .../service/impl/CategoryServiceImpl.java | 4 +++ .../domain/service/impl/EventReadService.java | 2 ++ .../domain/service/impl/EventServiceImpl.java | 4 +++ .../service/impl/MetadataReadService.java | 2 ++ .../service/impl/ProjectServiceImpl.java | 4 +++ .../domain/service/impl/TagReadService.java | 2 ++ .../domain/service/impl/TagServiceImpl.java | 4 +++ .../domain/service/impl/UserReadService.java | 2 ++ .../domain/service/impl/UserServiceImpl.java | 7 +++++- src/main/resources/application.yml | 10 +++++++- 15 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 src/main/java/ru/practicum/eventhub/config/RedisCacheConfig.java diff --git a/build.gradle b/build.gradle index 2513d38..c8baba7 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-cache' + implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation "org.mapstruct:mapstruct:${mapstructVersion}" implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-database-postgresql' diff --git a/docker-compose.yml b/docker-compose.yml index b74800f..99942a2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,4 +8,9 @@ services: POSTGRES_DB: postgres POSTGRES_PASSWORD: 123456 ports: - - "5432:5432" \ No newline at end of file + - "5432:5432" + + redis: + image: redis:8.4.0 + ports: + - "6379:6379" \ No newline at end of file diff --git a/src/main/java/ru/practicum/eventhub/EventHubApplication.java b/src/main/java/ru/practicum/eventhub/EventHubApplication.java index ebdbd65..1757cd7 100644 --- a/src/main/java/ru/practicum/eventhub/EventHubApplication.java +++ b/src/main/java/ru/practicum/eventhub/EventHubApplication.java @@ -2,10 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.cache.annotation.EnableCaching; @SpringBootApplication -@EnableTransactionManagement +@EnableCaching public class EventHubApplication { public static void main(String[] args) { diff --git a/src/main/java/ru/practicum/eventhub/config/RedisCacheConfig.java b/src/main/java/ru/practicum/eventhub/config/RedisCacheConfig.java new file mode 100644 index 0000000..ae44018 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/config/RedisCacheConfig.java @@ -0,0 +1,25 @@ +package ru.practicum.eventhub.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +@Component +public class RedisCacheConfig { + + @Bean + public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) { + RedisCacheConfiguration config = RedisCacheConfiguration + .defaultCacheConfig() + .entryTtl(Duration.ofHours(1)) + .disableCachingNullValues(); + + return RedisCacheManager.builder(connectionFactory) + .cacheDefaults(config) + .build(); + } +} diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryReadService.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryReadService.java index 5ace37a..af0f6bd 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryReadService.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryReadService.java @@ -3,6 +3,7 @@ import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.eventhub.api.exception.ConflictException; @@ -18,6 +19,7 @@ public class CategoryReadService { private final CategoryRepository categoryRepository; @Transactional(readOnly = true) + @Cacheable(value = "categories", key = "#id") public Category findById(UUID id) { return categoryRepository.findById(id).orElseThrow(() -> { log.error("Категория с id={} не найдена", id); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryServiceImpl.java index f86c4dc..789a738 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryServiceImpl.java @@ -2,6 +2,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -61,6 +63,7 @@ public CategoryDto getCategoryById(UUID id) { @Override @Transactional + @CachePut(value = "categories", key = "#id") public CategoryDto updateCategory(UUID id, CategoryUpdateDto dto) { Category category = categoryReadService.findByIdForUpdate(id); @@ -75,6 +78,7 @@ public CategoryDto updateCategory(UUID id, CategoryUpdateDto dto) { @Override @Transactional + @CacheEvict(value = "categories", key = "#id") public void deleteCategory(UUID id) { categoryReadService.findById(id); categoryRepository.deleteById(id); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventReadService.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/EventReadService.java index 893309d..b266a1c 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventReadService.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/EventReadService.java @@ -3,6 +3,7 @@ import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.eventhub.domain.model.Event; @@ -17,6 +18,7 @@ public class EventReadService { private final EventRepository eventRepository; @Transactional(readOnly = true) + @Cacheable(value = "events", key = "#id") public Event findById(UUID id) { return eventRepository.findById(id).orElseThrow(() -> { log.error("Событие с id={} не найдено", id); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java index f6f5a3c..b3113b4 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java @@ -2,6 +2,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -60,6 +62,7 @@ public EventDto getEventById(UUID id) { @Override @Transactional + @CachePut(value = "events", key = "#id") public EventDto updateEvent(UUID id, EventUpdateDto dto) { Event event = eventReadService.findByIdForUpdate(id); @@ -74,6 +77,7 @@ public EventDto updateEvent(UUID id, EventUpdateDto dto) { @Override @Transactional + @CacheEvict(value = "events", key = "#id") public void deleteEvent(UUID id) { eventReadService.findById(id); eventRepository.deleteById(id); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/MetadataReadService.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/MetadataReadService.java index 033eab8..e8b89b4 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/MetadataReadService.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/MetadataReadService.java @@ -3,6 +3,7 @@ import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.eventhub.api.exception.ConflictException; @@ -18,6 +19,7 @@ public class MetadataReadService { private final UserMetadataRepository userMetadataRepository; @Transactional(readOnly = true) + @Cacheable(value = "userMetadata", key = "#userId") public UserMetadata findByUserId(UUID userId) { return userMetadataRepository.findByUserId(userId).orElseThrow(() -> { log.error("User-metadata для user с id={} не найдена", userId); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectServiceImpl.java index c75c268..932de7b 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectServiceImpl.java @@ -2,6 +2,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -53,6 +55,7 @@ public ProjectDto getProjectByCategory(UUID categoryId, UUID projectId) { @Override @Transactional + @CachePut(value = "projects", key = "#projectId") public ProjectDto updateForCategory(UUID categoryId, UUID projectId, ProjectUpdateDto dto) { categoryReadService.findById(categoryId); Project project = projectReadService.findByIdAndCategoryId(projectId, categoryId); @@ -67,6 +70,7 @@ public ProjectDto updateForCategory(UUID categoryId, UUID projectId, ProjectUpda @Override @Transactional + @CacheEvict(value = "projects", key = "#projectId") public void deleteForCategory(UUID categoryId, UUID projectId) { Category category = categoryReadService.findById(categoryId); Project project = projectReadService.findByIdAndCategoryId(projectId, categoryId); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagReadService.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/TagReadService.java index b895e35..c305671 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagReadService.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/TagReadService.java @@ -3,6 +3,7 @@ import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.eventhub.api.exception.ConflictException; @@ -18,6 +19,7 @@ public class TagReadService { private final TagRepository tagRepository; @Transactional(readOnly = true) + @Cacheable(value = "tags", key = "#tagId") public Tag findById(UUID tagId) { return tagRepository.findById(tagId).orElseThrow(() -> { log.error("Тег с id={} не найден", tagId); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java index 868a14b..d36d21d 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java @@ -2,6 +2,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -92,6 +94,7 @@ public TagDto getTagByEvent(UUID eventId, UUID tagId) { @Override @Transactional + @CachePut(value = "tags", key = "#tagId") public TagDto updateTag(UUID tagId, TagUpdateDto dto) { Tag tag = tagReadService.findById(tagId); @@ -116,6 +119,7 @@ public void deleteForEvent(UUID eventId, UUID tagId) { @Override @Transactional + @CacheEvict(value = "tags", key = "#tagId") public void deleteTag(UUID tagId) { Tag tag = tagReadService.findById(tagId); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/UserReadService.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/UserReadService.java index 387730c..c0f98f6 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/UserReadService.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/UserReadService.java @@ -3,6 +3,7 @@ import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.eventhub.api.exception.ConflictException; @@ -18,6 +19,7 @@ public class UserReadService { private final UserRepository userRepository; @Transactional(readOnly = true) + @Cacheable(value = "users", key = "#id") public User findById(UUID id) { return userRepository.findById(id).orElseThrow(() -> { log.error("Пользователь с id={} не найден", id); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/UserServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/UserServiceImpl.java index 353a399..b8accf0 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/UserServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/UserServiceImpl.java @@ -2,6 +2,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -56,12 +58,14 @@ public PagedResponse getUsers(Pageable pageable) { @Override @Transactional(readOnly = true) public UserDto getUserById(UUID id) { + User user = userReadService.findById(id); log.info("Запрошен пользователь с id={}", id); - return userMapper.toDto(userReadService.findById(id)); + return userMapper.toDto(user); } @Override @Transactional + @CachePut(value = "users", key = "#id") public UserDto updateUser(UUID id, UserUpdateDto dto) { User user = userReadService.findByIdForUpdate(id); @@ -76,6 +80,7 @@ public UserDto updateUser(UUID id, UserUpdateDto dto) { @Override @Transactional + @CacheEvict(value = "users", key = "#id") public void deleteUser(UUID id) { userReadService.findById(id); userRepository.deleteById(id); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index a461708..4048e99 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -20,4 +20,12 @@ spring: flyway: enabled: true baseline-on-migrate: true - default-schema: event_hub \ No newline at end of file + default-schema: event_hub + + cache: + type: redis + + data: + redis: + host: localhost + port: 6379 \ No newline at end of file From 32034e9e118a960bc26e4f8c8c039cf599730d8d Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 10 Feb 2026 02:34:19 +0300 Subject: [PATCH 2/8] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B8=D0=BD=D1=82=D0=B5=D0=B3=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=81=20Tag=20Analytics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлен клиент TagAnalyticsClient для работы с API аналитики тегов - Обновлён TagService для получения статистики тегов - Изменён метод getTagByEvent в TagService для возврата TagWithStatsDto - Добавлен новый DTO TagWithStatsDto для передачи данных о тегах с их статистикой - Обновлён mappers для поддержки нового DTO - Обновлён application.yml для добавления URL сервиса аналитики --- build.gradle | 18 ++++++++-- gradle.properties | 4 +++ .../eventhub/EventHubApplication.java | 2 ++ .../api/dto/response/TagStatsDto.java | 11 ++++++ .../api/dto/response/TagWithStatsDto.java | 19 +++++++++++ .../eventhub/api/mapper/TagMapper.java | 4 +++ .../controller/EventTagController.java | 5 +-- .../eventhub/domain/service/TagService.java | 3 +- .../domain/service/impl/TagServiceImpl.java | 13 +++++-- .../infrastructure/TagAnalyticsClient.java | 19 +++++++++++ src/main/resources/application.yml | 6 +++- src/main/resources/openapi/openapi.yml | 34 ++++++++++++++++++- 12 files changed, 128 insertions(+), 10 deletions(-) create mode 100644 src/main/java/ru/practicum/eventhub/api/dto/response/TagStatsDto.java create mode 100644 src/main/java/ru/practicum/eventhub/api/dto/response/TagWithStatsDto.java create mode 100644 src/main/java/ru/practicum/eventhub/infrastructure/TagAnalyticsClient.java diff --git a/build.gradle b/build.gradle index c8baba7..ce080ae 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,8 @@ plugins { id 'java' - id 'org.springframework.boot' version '3.5.7' - id 'io.spring.dependency-management' version '1.1.7' - id 'org.openapi.generator' version '7.18.0' + id 'org.springframework.boot' version "${springBootVersion}" + id 'io.spring.dependency-management' version "${dependencyManagementVersion}" + id 'org.openapi.generator' version "${openapiGeneratorVersion}" } group = 'ru.practicum' @@ -15,6 +15,10 @@ java { } } +ext { + set('springCloudVersion', "2024.0.0") +} + configurations { compileOnly { extendsFrom annotationProcessor @@ -32,6 +36,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-cache' implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' implementation "org.mapstruct:mapstruct:${mapstructVersion}" implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-database-postgresql' @@ -47,6 +52,12 @@ dependencies { testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} + openApiGenerate { generatorName = "spring" inputSpec = "$rootDir/src/main/resources/openapi/openapi.yml" @@ -78,6 +89,7 @@ openApiGenerate { "TagCreateDto" : "ru.practicum.eventhub.api.dto.request.TagCreateDto", "TagUpdateDto" : "ru.practicum.eventhub.api.dto.request.TagUpdateDto", "TagDto" : "ru.practicum.eventhub.api.dto.response.TagDto", + "TagWithStatsDto": "ru.practicum.eventhub.api.dto.response.TagWithStatsDto", "ErrorResponse" : "ru.practicum.eventhub.api.exception.ErrorResponse" ] diff --git a/gradle.properties b/gradle.properties index 924049d..2b885ae 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,6 @@ +springBootVersion=3.4.2 +dependencyManagementVersion=1.1.7 +openapiGeneratorVersion=7.10.0 + mapstructVersion=1.6.3 swaggerVersion=2.2.38 \ No newline at end of file diff --git a/src/main/java/ru/practicum/eventhub/EventHubApplication.java b/src/main/java/ru/practicum/eventhub/EventHubApplication.java index 1757cd7..1e71110 100644 --- a/src/main/java/ru/practicum/eventhub/EventHubApplication.java +++ b/src/main/java/ru/practicum/eventhub/EventHubApplication.java @@ -3,9 +3,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableCaching +@EnableFeignClients public class EventHubApplication { public static void main(String[] args) { diff --git a/src/main/java/ru/practicum/eventhub/api/dto/response/TagStatsDto.java b/src/main/java/ru/practicum/eventhub/api/dto/response/TagStatsDto.java new file mode 100644 index 0000000..ef13703 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/api/dto/response/TagStatsDto.java @@ -0,0 +1,11 @@ +package ru.practicum.eventhub.api.dto.response; + +import java.time.OffsetDateTime; +import java.util.UUID; + +public record TagStatsDto( + UUID tagId, + long usageCount, + OffsetDateTime lastUsedAt +) { +} diff --git a/src/main/java/ru/practicum/eventhub/api/dto/response/TagWithStatsDto.java b/src/main/java/ru/practicum/eventhub/api/dto/response/TagWithStatsDto.java new file mode 100644 index 0000000..32fd4a7 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/api/dto/response/TagWithStatsDto.java @@ -0,0 +1,19 @@ +package ru.practicum.eventhub.api.dto.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import ru.practicum.eventhub.api.model.EventShortDto; + +import java.time.OffsetDateTime; +import java.util.List; +import java.util.UUID; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record TagWithStatsDto( + UUID id, + String name, + String description, + List events, + Long usageCount, + OffsetDateTime lastUsedAt +) { +} diff --git a/src/main/java/ru/practicum/eventhub/api/mapper/TagMapper.java b/src/main/java/ru/practicum/eventhub/api/mapper/TagMapper.java index a7a8185..e908a8d 100644 --- a/src/main/java/ru/practicum/eventhub/api/mapper/TagMapper.java +++ b/src/main/java/ru/practicum/eventhub/api/mapper/TagMapper.java @@ -7,6 +7,8 @@ import ru.practicum.eventhub.api.dto.request.TagCreateDto; import ru.practicum.eventhub.api.dto.request.TagUpdateDto; import ru.practicum.eventhub.api.dto.response.TagDto; +import ru.practicum.eventhub.api.dto.response.TagStatsDto; +import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; import ru.practicum.eventhub.domain.model.Tag; import static org.mapstruct.MappingConstants.ComponentModel.SPRING; @@ -16,6 +18,8 @@ public interface TagMapper { TagDto toDto(Tag tag); + TagWithStatsDto toDtoWithStats(Tag tag, TagStatsDto stats); + @Mapping(target = "id", ignore = true) @Mapping(target = "version", ignore = true) @Mapping(target = "createdAt", ignore = true) diff --git a/src/main/java/ru/practicum/eventhub/controller/EventTagController.java b/src/main/java/ru/practicum/eventhub/controller/EventTagController.java index ea38f19..95abac3 100644 --- a/src/main/java/ru/practicum/eventhub/controller/EventTagController.java +++ b/src/main/java/ru/practicum/eventhub/controller/EventTagController.java @@ -12,6 +12,7 @@ import ru.practicum.eventhub.api.dto.request.TagUpdateDto; import ru.practicum.eventhub.api.dto.response.EventDto; import ru.practicum.eventhub.api.dto.response.TagDto; +import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; import ru.practicum.eventhub.api.model.PageOfEvents; import ru.practicum.eventhub.api.model.PageOfTags; import ru.practicum.eventhub.application.EventApplicationService; @@ -90,8 +91,8 @@ public ResponseEntity getEvents(Integer page, Integer size) { } @Override - public ResponseEntity getTagByEventId(UUID eventId, UUID tagId) { - TagDto tagDto = tagService.getTagByEvent(eventId, tagId); + public ResponseEntity getTagByEventId(UUID eventId, UUID tagId) { + TagWithStatsDto tagDto = tagService.getTagByEvent(eventId, tagId); return ResponseEntity.ok(tagDto); } diff --git a/src/main/java/ru/practicum/eventhub/domain/service/TagService.java b/src/main/java/ru/practicum/eventhub/domain/service/TagService.java index 1c83cc4..2ea3da7 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/TagService.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/TagService.java @@ -4,6 +4,7 @@ import ru.practicum.eventhub.api.dto.request.TagCreateDto; import ru.practicum.eventhub.api.dto.request.TagUpdateDto; import ru.practicum.eventhub.api.dto.response.TagDto; +import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; import ru.practicum.eventhub.domain.dto.PagedResponse; import java.util.UUID; @@ -17,7 +18,7 @@ public interface TagService { PagedResponse getTagsByEvent(UUID eventId, Pageable pageable); - TagDto getTagByEvent(UUID eventId, UUID tagId); + TagWithStatsDto getTagByEvent(UUID eventId, UUID tagId); TagDto updateTag(UUID tagId, TagUpdateDto dto); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java index d36d21d..39af6ed 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java @@ -11,6 +11,8 @@ import ru.practicum.eventhub.api.dto.request.TagCreateDto; import ru.practicum.eventhub.api.dto.request.TagUpdateDto; import ru.practicum.eventhub.api.dto.response.TagDto; +import ru.practicum.eventhub.api.dto.response.TagStatsDto; +import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; import ru.practicum.eventhub.api.exception.ConflictException; import ru.practicum.eventhub.api.mapper.TagMapper; import ru.practicum.eventhub.domain.dto.PagedResponse; @@ -20,6 +22,7 @@ import ru.practicum.eventhub.domain.service.TagService; import ru.practicum.eventhub.domain.util.PageValidator; import ru.practicum.eventhub.domain.validation.TagValidationService; +import ru.practicum.eventhub.infrastructure.TagAnalyticsClient; import java.util.Iterator; import java.util.UUID; @@ -33,6 +36,7 @@ public class TagServiceImpl implements TagService { private final TagReadService tagReadService; private final EventReadService eventReadService; private final TagValidationService tagValidationService; + private final TagAnalyticsClient tagAnalyticsClient; @Override @Transactional @@ -57,6 +61,8 @@ public TagDto addTagToEvent(UUID eventId, UUID tagId) { } event.addTag(tag); + tagAnalyticsClient.incrementUsage(tag.getId()); + log.info("Тег с id={} добавлен к событию с id={}", tagId, eventId); return tagMapper.toDto(tag); } @@ -85,11 +91,14 @@ public PagedResponse getTagsByEvent(UUID eventId, Pageable pageable) { @Override @Transactional(readOnly = true) - public TagDto getTagByEvent(UUID eventId, UUID tagId) { + public TagWithStatsDto getTagByEvent(UUID eventId, UUID tagId) { eventReadService.findById(eventId); Tag tag = tagReadService.findByIdAndEventsId(tagId, eventId); + + TagStatsDto stats = tagAnalyticsClient.getStats(tagId); + log.info("Запрошен тег с id={} для события с id={}", tagId, eventId); - return tagMapper.toDto(tag); + return tagMapper.toDtoWithStats(tag, stats); } @Override diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/TagAnalyticsClient.java b/src/main/java/ru/practicum/eventhub/infrastructure/TagAnalyticsClient.java new file mode 100644 index 0000000..81ff5c3 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/infrastructure/TagAnalyticsClient.java @@ -0,0 +1,19 @@ +package ru.practicum.eventhub.infrastructure; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import ru.practicum.eventhub.api.dto.response.TagStatsDto; + +import java.util.UUID; + +@FeignClient(name = "tag-analytics", url = "${services.tag-analytics.url}") +public interface TagAnalyticsClient { + + @PostMapping("/api/v1/tags/{id}/used") + void incrementUsage(@PathVariable UUID id); + + @GetMapping("/api/v1/tags/{id}/stats") + TagStatsDto getStats(@PathVariable UUID id); +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4048e99..c2b0121 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -28,4 +28,8 @@ spring: data: redis: host: localhost - port: 6379 \ No newline at end of file + port: 6379 + +services: + tag-analytics: + url: http://localhost:8081 \ No newline at end of file diff --git a/src/main/resources/openapi/openapi.yml b/src/main/resources/openapi/openapi.yml index 7d15861..c0c7dae 100644 --- a/src/main/resources/openapi/openapi.yml +++ b/src/main/resources/openapi/openapi.yml @@ -1139,7 +1139,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TagDto' + $ref: '#/components/schemas/TagWithStatsDto' '400': description: Некорректный id content: @@ -1698,6 +1698,38 @@ components: items: $ref: '#/components/schemas/EventShortDto' + TagWithStatsDto: + type: object + properties: + id: + type: string + format: uuid + description: Идентификатор тега + example: "c1d2e3f4-5678-1234-abcd-9876543210ab" + name: + type: string + description: Название тега + example: "Java" + description: + type: string + description: Описание тега + example: "Тег для событий, связанных с Java." + events: + type: array + description: Список событий с этим тегом + items: + $ref: '#/components/schemas/EventDto' + usageCount: + type: integer + format: int64 + description: Число использований + example: 5 + lastUsedAt: + type: string + format: date-time + description: Дата и время последнего изменения статистики + example: "2025-11-05T15:30:00+03:00" + TagShortDto: type: object properties: From 0cdf26d33fc6ea9a4d9d5a7b3ad7aebcf9c62ebf Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 10 Feb 2026 21:02:10 +0300 Subject: [PATCH 3/8] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA?= =?UTF-8?q?=D0=B0=20Circuit=20Breaker=20=D0=B4=D0=BB=D1=8F=20Tag=20Analyti?= =?UTF-8?q?cs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлен класс TagAnalyticsFallback для обработки ошибок - Обновлён TagAnalyticsClient с использованием fallback - Настроены параметры Circuit Breaker в application.yml - Добавлена зависимость spring-cloud-starter-circuitbreaker-resilience4j в build.gradle --- build.gradle | 1 + .../infrastructure/TagAnalyticsClient.java | 7 +++++- .../fallback/TagAnalyticsFallback.java | 24 +++++++++++++++++++ src/main/resources/application.yml | 22 +++++++++++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ru/practicum/eventhub/infrastructure/fallback/TagAnalyticsFallback.java diff --git a/build.gradle b/build.gradle index ce080ae..bf14a7a 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-cache' implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' + implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j' implementation "org.mapstruct:mapstruct:${mapstructVersion}" implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-database-postgresql' diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/TagAnalyticsClient.java b/src/main/java/ru/practicum/eventhub/infrastructure/TagAnalyticsClient.java index 81ff5c3..87f4536 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/TagAnalyticsClient.java +++ b/src/main/java/ru/practicum/eventhub/infrastructure/TagAnalyticsClient.java @@ -5,10 +5,15 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import ru.practicum.eventhub.api.dto.response.TagStatsDto; +import ru.practicum.eventhub.infrastructure.fallback.TagAnalyticsFallback; import java.util.UUID; -@FeignClient(name = "tag-analytics", url = "${services.tag-analytics.url}") +@FeignClient( + name = "tag-analytics", + url = "${services.tag-analytics.url}", + fallback = TagAnalyticsFallback.class +) public interface TagAnalyticsClient { @PostMapping("/api/v1/tags/{id}/used") diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/fallback/TagAnalyticsFallback.java b/src/main/java/ru/practicum/eventhub/infrastructure/fallback/TagAnalyticsFallback.java new file mode 100644 index 0000000..c155d0b --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/infrastructure/fallback/TagAnalyticsFallback.java @@ -0,0 +1,24 @@ +package ru.practicum.eventhub.infrastructure.fallback; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.practicum.eventhub.api.dto.response.TagStatsDto; +import ru.practicum.eventhub.infrastructure.TagAnalyticsClient; + +import java.util.UUID; + +@Slf4j +@Component +public class TagAnalyticsFallback implements TagAnalyticsClient { + + @Override + public void incrementUsage(UUID id) { + log.warn("Сервис Tag Analytics недоступен. Не удалось увеличить счетчик использования для тега с id={}", id); + } + + @Override + public TagStatsDto getStats(UUID id) { + log.warn("Сервис Tag Analytics недоступен. Не удалось получить статистику для тега с id={}", id); + return null; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c2b0121..b912e55 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -30,6 +30,28 @@ spring: host: localhost port: 6379 + cloud: + openfeign: + circuitbreaker: + enabled: true + +resilience4j: + circuitbreaker: + instances: + tagAnalytics: + slidingWindowSize: 10 + failureRateThreshold: 50 + waitDurationInOpenState: 10s + + retry: + instances: + tagAnalytics: + maxAttempts: 3 + waitDuration: 500ms + exponentialBackoffMultiplier: 2 + enableRandomizedWait: true + randomizedWaitFactor: 0.5 + services: tag-analytics: url: http://localhost:8081 \ No newline at end of file From 9814eb9e772bb6d9327a868fbada85d62bc969fa Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 12 Feb 2026 17:04:26 +0300 Subject: [PATCH 4/8] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=BA=D1=8D=D1=88=D0=B8=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BB=D1=8F=20=D0=BA=D0=B0=D1=82?= =?UTF-8?q?=D0=B5=D0=B3=D0=BE=D1=80=D0=B8=D0=B9=20=D0=B8=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлен класс CategoryCacheInvalidator для удаления кэша при удалении категории - Добавлен класс CategoryCacheService для обновления кэша категорий - Добавлен класс ProjectCacheIndexService для управления индексом проектов по категориям - Обновлены методы в CategoryServiceImpl и ProjectServiceImpl для работы с кэшированием - Обновлены DTO классы для соответствия изменениям --- .../api/dto/response/CategoryDto.java | 2 +- .../eventhub/api/dto/response/EventDto.java | 2 +- .../eventhub/api/dto/response/ProjectDto.java | 2 +- .../eventhub/api/dto/response/TagDto.java | 2 +- .../api/dto/response/TagStatsDto.java | 2 - .../api/dto/response/TagWithStatsDto.java | 4 +- .../api/dto/response/UserMetadataDto.java | 2 +- .../eventhub/config/RedisCacheConfig.java | 7 +- .../domain/event/CategoryDeletedEvent.java | 6 ++ .../domain/repository/TagRepository.java | 11 +++ .../service/impl/CategoryReadService.java | 2 - .../service/impl/CategoryServiceImpl.java | 7 ++ .../domain/service/impl/EventReadService.java | 2 - .../domain/service/impl/EventServiceImpl.java | 36 ++++++- .../service/impl/MetadataReadService.java | 2 - .../service/impl/ProjectServiceImpl.java | 16 +++- .../domain/service/impl/TagReadService.java | 2 - .../domain/service/impl/TagServiceImpl.java | 30 +++++- .../service/impl/UserMetadataServiceImpl.java | 2 + .../domain/service/impl/UserReadService.java | 2 - .../domain/service/impl/UserServiceImpl.java | 8 +- .../cache/CategoryCacheInvalidator.java | 34 +++++++ .../cache/CategoryCacheService.java | 27 ++++++ .../cache/EventCacheService.java | 39 ++++++++ .../cache/ManyToManyCacheIndexService.java | 54 +++++++++++ .../cache/ProjectCacheIndexService.java | 39 ++++++++ .../infrastructure/cache/TagCacheService.java | 95 +++++++++++++++++++ .../fallback/TagAnalyticsFallback.java | 2 +- .../migration/V1__create_initial_tables.sql | 2 +- src/main/resources/openapi/openapi.yml | 53 ++++------- 30 files changed, 425 insertions(+), 69 deletions(-) create mode 100644 src/main/java/ru/practicum/eventhub/domain/event/CategoryDeletedEvent.java create mode 100644 src/main/java/ru/practicum/eventhub/infrastructure/cache/CategoryCacheInvalidator.java create mode 100644 src/main/java/ru/practicum/eventhub/infrastructure/cache/CategoryCacheService.java create mode 100644 src/main/java/ru/practicum/eventhub/infrastructure/cache/EventCacheService.java create mode 100644 src/main/java/ru/practicum/eventhub/infrastructure/cache/ManyToManyCacheIndexService.java create mode 100644 src/main/java/ru/practicum/eventhub/infrastructure/cache/ProjectCacheIndexService.java create mode 100644 src/main/java/ru/practicum/eventhub/infrastructure/cache/TagCacheService.java diff --git a/src/main/java/ru/practicum/eventhub/api/dto/response/CategoryDto.java b/src/main/java/ru/practicum/eventhub/api/dto/response/CategoryDto.java index f7c67c5..bbb8a87 100644 --- a/src/main/java/ru/practicum/eventhub/api/dto/response/CategoryDto.java +++ b/src/main/java/ru/practicum/eventhub/api/dto/response/CategoryDto.java @@ -15,6 +15,6 @@ public record CategoryDto( OffsetDateTime updatedAt, List projects ) { - public record ProjectShortDto(UUID id, String name) { + public record ProjectShortDto(UUID id) { } } diff --git a/src/main/java/ru/practicum/eventhub/api/dto/response/EventDto.java b/src/main/java/ru/practicum/eventhub/api/dto/response/EventDto.java index eb2568d..ca7f31a 100644 --- a/src/main/java/ru/practicum/eventhub/api/dto/response/EventDto.java +++ b/src/main/java/ru/practicum/eventhub/api/dto/response/EventDto.java @@ -17,6 +17,6 @@ public record EventDto( OffsetDateTime updatedAt, List tags ) { - public record TagShortDto(UUID id, String name) { + public record TagShortDto(UUID id) { } } diff --git a/src/main/java/ru/practicum/eventhub/api/dto/response/ProjectDto.java b/src/main/java/ru/practicum/eventhub/api/dto/response/ProjectDto.java index fea34e8..49ac5ad 100644 --- a/src/main/java/ru/practicum/eventhub/api/dto/response/ProjectDto.java +++ b/src/main/java/ru/practicum/eventhub/api/dto/response/ProjectDto.java @@ -14,6 +14,6 @@ public record ProjectDto( OffsetDateTime updatedAt, CategoryShortDto category ) { - public record CategoryShortDto(UUID id, String name) { + public record CategoryShortDto(UUID id) { } } diff --git a/src/main/java/ru/practicum/eventhub/api/dto/response/TagDto.java b/src/main/java/ru/practicum/eventhub/api/dto/response/TagDto.java index 5727f03..bfc3deb 100644 --- a/src/main/java/ru/practicum/eventhub/api/dto/response/TagDto.java +++ b/src/main/java/ru/practicum/eventhub/api/dto/response/TagDto.java @@ -12,6 +12,6 @@ public record TagDto( String description, List events ) { - public record EventShortDto(UUID id, String title) { + public record EventShortDto(UUID id) { } } diff --git a/src/main/java/ru/practicum/eventhub/api/dto/response/TagStatsDto.java b/src/main/java/ru/practicum/eventhub/api/dto/response/TagStatsDto.java index ef13703..76c4113 100644 --- a/src/main/java/ru/practicum/eventhub/api/dto/response/TagStatsDto.java +++ b/src/main/java/ru/practicum/eventhub/api/dto/response/TagStatsDto.java @@ -1,10 +1,8 @@ package ru.practicum.eventhub.api.dto.response; import java.time.OffsetDateTime; -import java.util.UUID; public record TagStatsDto( - UUID tagId, long usageCount, OffsetDateTime lastUsedAt ) { diff --git a/src/main/java/ru/practicum/eventhub/api/dto/response/TagWithStatsDto.java b/src/main/java/ru/practicum/eventhub/api/dto/response/TagWithStatsDto.java index 32fd4a7..a4b228b 100644 --- a/src/main/java/ru/practicum/eventhub/api/dto/response/TagWithStatsDto.java +++ b/src/main/java/ru/practicum/eventhub/api/dto/response/TagWithStatsDto.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import ru.practicum.eventhub.api.model.EventShortDto; -import java.time.OffsetDateTime; import java.util.List; import java.util.UUID; @@ -13,7 +12,6 @@ public record TagWithStatsDto( String name, String description, List events, - Long usageCount, - OffsetDateTime lastUsedAt + TagStatsDto stats ) { } diff --git a/src/main/java/ru/practicum/eventhub/api/dto/response/UserMetadataDto.java b/src/main/java/ru/practicum/eventhub/api/dto/response/UserMetadataDto.java index 2b7eeda..425a220 100644 --- a/src/main/java/ru/practicum/eventhub/api/dto/response/UserMetadataDto.java +++ b/src/main/java/ru/practicum/eventhub/api/dto/response/UserMetadataDto.java @@ -16,6 +16,6 @@ public record UserMetadataDto( OffsetDateTime createdAt, OffsetDateTime updatedAt ) { - public record UserShortDto(UUID id, String username, String email) { + public record UserShortDto(UUID id) { } } diff --git a/src/main/java/ru/practicum/eventhub/config/RedisCacheConfig.java b/src/main/java/ru/practicum/eventhub/config/RedisCacheConfig.java index ae44018..b09d09b 100644 --- a/src/main/java/ru/practicum/eventhub/config/RedisCacheConfig.java +++ b/src/main/java/ru/practicum/eventhub/config/RedisCacheConfig.java @@ -4,6 +4,9 @@ import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.stereotype.Component; import java.time.Duration; @@ -16,7 +19,9 @@ public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) RedisCacheConfiguration config = RedisCacheConfiguration .defaultCacheConfig() .entryTtl(Duration.ofHours(1)) - .disableCachingNullValues(); + .disableCachingNullValues() + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); return RedisCacheManager.builder(connectionFactory) .cacheDefaults(config) diff --git a/src/main/java/ru/practicum/eventhub/domain/event/CategoryDeletedEvent.java b/src/main/java/ru/practicum/eventhub/domain/event/CategoryDeletedEvent.java new file mode 100644 index 0000000..5833229 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/domain/event/CategoryDeletedEvent.java @@ -0,0 +1,6 @@ +package ru.practicum.eventhub.domain.event; + +import java.util.UUID; + +public record CategoryDeletedEvent(UUID categoryId) { +} diff --git a/src/main/java/ru/practicum/eventhub/domain/repository/TagRepository.java b/src/main/java/ru/practicum/eventhub/domain/repository/TagRepository.java index 82a6646..fed22e0 100644 --- a/src/main/java/ru/practicum/eventhub/domain/repository/TagRepository.java +++ b/src/main/java/ru/practicum/eventhub/domain/repository/TagRepository.java @@ -5,9 +5,13 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import ru.practicum.eventhub.domain.model.Tag; +import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.UUID; public interface TagRepository extends JpaRepository { @@ -24,4 +28,11 @@ public interface TagRepository extends JpaRepository { boolean existsByName(String name); boolean existsByIdAndEventsId(UUID tagId, UUID eventId); + + @Query(""" + select t from Tag t + join t.events e + where t.id = :tagId and e.id in :eventIds + """) + List findAllByTagIdAndEventIds(@Param("tagId") UUID tagId, @Param("eventIds") Set eventIds); } diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryReadService.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryReadService.java index af0f6bd..5ace37a 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryReadService.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryReadService.java @@ -3,7 +3,6 @@ import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.eventhub.api.exception.ConflictException; @@ -19,7 +18,6 @@ public class CategoryReadService { private final CategoryRepository categoryRepository; @Transactional(readOnly = true) - @Cacheable(value = "categories", key = "#id") public Category findById(UUID id) { return categoryRepository.findById(id).orElseThrow(() -> { log.error("Категория с id={} не найдена", id); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryServiceImpl.java index 789a738..932b9a6 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryServiceImpl.java @@ -4,6 +4,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -13,6 +15,7 @@ import ru.practicum.eventhub.api.dto.response.CategoryDto; import ru.practicum.eventhub.api.mapper.CategoryMapper; import ru.practicum.eventhub.domain.dto.PagedResponse; +import ru.practicum.eventhub.domain.event.CategoryDeletedEvent; import ru.practicum.eventhub.domain.model.Category; import ru.practicum.eventhub.domain.repository.CategoryRepository; import ru.practicum.eventhub.domain.service.CategoryService; @@ -29,6 +32,7 @@ public class CategoryServiceImpl implements CategoryService { private final CategoryMapper categoryMapper; private final CategoryReadService categoryReadService; private final CategoryValidationService categoryValidationService; + private final ApplicationEventPublisher eventPublisher; @Override @Transactional @@ -55,6 +59,7 @@ public PagedResponse getCategories(Pageable pageable) { @Override @Transactional(readOnly = true) + @Cacheable(value = "categories", key = "#id") public CategoryDto getCategoryById(UUID id) { Category category = categoryReadService.findById(id); log.info("Отправлена категория c id={}", id); @@ -82,6 +87,8 @@ public CategoryDto updateCategory(UUID id, CategoryUpdateDto dto) { public void deleteCategory(UUID id) { categoryReadService.findById(id); categoryRepository.deleteById(id); + + eventPublisher.publishEvent(new CategoryDeletedEvent(id)); log.info("Удалена категория с id={}", id); } } diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventReadService.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/EventReadService.java index b266a1c..893309d 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventReadService.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/EventReadService.java @@ -3,7 +3,6 @@ import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.eventhub.domain.model.Event; @@ -18,7 +17,6 @@ public class EventReadService { private final EventRepository eventRepository; @Transactional(readOnly = true) - @Cacheable(value = "events", key = "#id") public Event findById(UUID id) { return eventRepository.findById(id).orElseThrow(() -> { log.error("Событие с id={} не найдено", id); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java index b3113b4..c917127 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java @@ -2,8 +2,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -14,21 +13,32 @@ import ru.practicum.eventhub.api.mapper.EventMapper; import ru.practicum.eventhub.domain.dto.PagedResponse; import ru.practicum.eventhub.domain.model.Event; +import ru.practicum.eventhub.domain.model.Tag; import ru.practicum.eventhub.domain.repository.EventRepository; import ru.practicum.eventhub.domain.service.EventService; import ru.practicum.eventhub.domain.util.PageValidator; import ru.practicum.eventhub.domain.validation.EventValidationService; +import ru.practicum.eventhub.infrastructure.cache.EventCacheService; +import ru.practicum.eventhub.infrastructure.cache.ManyToManyCacheIndexService; +import ru.practicum.eventhub.infrastructure.cache.TagCacheService; +import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; @Slf4j @Service @RequiredArgsConstructor public class EventServiceImpl implements EventService { + private static final String EVENT_TAG_RELATION = "event_tag"; + private final EventRepository eventRepository; private final EventReadService eventReadService; private final EventMapper eventMapper; private final EventValidationService eventValidationService; + private final EventCacheService eventCacheService; + private final ManyToManyCacheIndexService relationIndexService; + private final TagCacheService tagCacheService; @Override @Transactional @@ -54,6 +64,7 @@ public PagedResponse getEvents(Pageable pageable) { @Override @Transactional(readOnly = true) + @Cacheable(value = "events", key = "#id") public EventDto getEventById(UUID id) { Event event = eventReadService.findById(id); log.info("Запрошено событие с id={}", id); @@ -62,25 +73,40 @@ public EventDto getEventById(UUID id) { @Override @Transactional - @CachePut(value = "events", key = "#id") public EventDto updateEvent(UUID id, EventUpdateDto dto) { Event event = eventReadService.findByIdForUpdate(id); eventValidationService.validateUpdate(dto); + Set oldTagIds = event.getTags().stream().map(Tag::getId).collect(Collectors.toSet()); event = eventMapper.updateEventFromDto(dto, event); - event = eventRepository.save(event); + + Set addedTagIds = event.getTags().stream().map(Tag::getId).collect(Collectors.toSet()); + addedTagIds.removeAll(oldTagIds); + + for (UUID tagId : addedTagIds) { + relationIndexService.add(EVENT_TAG_RELATION, id, tagId); + } + eventCacheService.refresh(id); + for (UUID tagId : addedTagIds) { + tagCacheService.refreshByEvent(id, tagId); + } + log.info("Обновлено событие с id={}", id); return eventMapper.toDto(event); } @Override @Transactional - @CacheEvict(value = "events", key = "#id") public void deleteEvent(UUID id) { eventReadService.findById(id); eventRepository.deleteById(id); + + relationIndexService.deleteLeft(EVENT_TAG_RELATION, id); + + eventCacheService.evict(id); + log.info("Удалено событие с id={}", id); } } diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/MetadataReadService.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/MetadataReadService.java index e8b89b4..033eab8 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/MetadataReadService.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/MetadataReadService.java @@ -3,7 +3,6 @@ import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.eventhub.api.exception.ConflictException; @@ -19,7 +18,6 @@ public class MetadataReadService { private final UserMetadataRepository userMetadataRepository; @Transactional(readOnly = true) - @Cacheable(value = "userMetadata", key = "#userId") public UserMetadata findByUserId(UUID userId) { return userMetadataRepository.findByUserId(userId).orElseThrow(() -> { log.error("User-metadata для user с id={} не найдена", userId); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectServiceImpl.java index 932de7b..72fbb0c 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectServiceImpl.java @@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -18,6 +19,8 @@ import ru.practicum.eventhub.domain.service.ProjectService; import ru.practicum.eventhub.domain.util.PageValidator; import ru.practicum.eventhub.domain.validation.ProjectValidationService; +import ru.practicum.eventhub.infrastructure.cache.CategoryCacheService; +import ru.practicum.eventhub.infrastructure.cache.ProjectCacheIndexService; import java.util.UUID; @@ -30,6 +33,8 @@ public class ProjectServiceImpl implements ProjectService { private final ProjectReadService projectReadService; private final CategoryReadService categoryReadService; private final ProjectValidationService projectValidationService; + private final ProjectCacheIndexService cacheIndexService; + private final CategoryCacheService categoryCacheService; @Override @Transactional(readOnly = true) @@ -45,17 +50,20 @@ public PagedResponse getProjectsByCategory(UUID categoryId, Pageable @Override @Transactional(readOnly = true) + @Cacheable(value = "projects", key = "#categoryId + ':' + #projectId") public ProjectDto getProjectByCategory(UUID categoryId, UUID projectId) { categoryReadService.findById(categoryId); Project project = projectReadService.findByIdAndCategoryId(projectId, categoryId); + cacheIndexService.addProject(categoryId, projectId); + log.info("Запрошен проект с id={} в категории с id={}", projectId, categoryId); return projectMapper.toDto(project); } @Override @Transactional - @CachePut(value = "projects", key = "#projectId") + @CachePut(value = "projects", key = "#categoryId + ':' + #projectId") public ProjectDto updateForCategory(UUID categoryId, UUID projectId, ProjectUpdateDto dto) { categoryReadService.findById(categoryId); Project project = projectReadService.findByIdAndCategoryId(projectId, categoryId); @@ -70,12 +78,16 @@ public ProjectDto updateForCategory(UUID categoryId, UUID projectId, ProjectUpda @Override @Transactional - @CacheEvict(value = "projects", key = "#projectId") + @CacheEvict(value = "projects", key = "#categoryId + ':' + #projectId") public void deleteForCategory(UUID categoryId, UUID projectId) { Category category = categoryReadService.findById(categoryId); Project project = projectReadService.findByIdAndCategoryId(projectId, categoryId); category.removeProject(project); + + cacheIndexService.removeProject(categoryId, projectId); + categoryCacheService.refresh(categoryId); + log.info("Удален проект с id={} в категории с id={}", projectId, categoryId); } } diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagReadService.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/TagReadService.java index c305671..b895e35 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagReadService.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/TagReadService.java @@ -3,7 +3,6 @@ import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.eventhub.api.exception.ConflictException; @@ -19,7 +18,6 @@ public class TagReadService { private final TagRepository tagRepository; @Transactional(readOnly = true) - @Cacheable(value = "tags", key = "#tagId") public Tag findById(UUID tagId) { return tagRepository.findById(tagId).orElseThrow(() -> { log.error("Тег с id={} не найден", tagId); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java index 39af6ed..464f18e 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java @@ -2,8 +2,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -23,20 +23,29 @@ import ru.practicum.eventhub.domain.util.PageValidator; import ru.practicum.eventhub.domain.validation.TagValidationService; import ru.practicum.eventhub.infrastructure.TagAnalyticsClient; +import ru.practicum.eventhub.infrastructure.cache.EventCacheService; +import ru.practicum.eventhub.infrastructure.cache.ManyToManyCacheIndexService; +import ru.practicum.eventhub.infrastructure.cache.TagCacheService; import java.util.Iterator; +import java.util.Set; import java.util.UUID; @Slf4j @Service @RequiredArgsConstructor public class TagServiceImpl implements TagService { + private static final String EVENT_TAG_RELATION = "event_tag"; + private final TagRepository tagRepository; private final TagMapper tagMapper; private final TagReadService tagReadService; private final EventReadService eventReadService; private final TagValidationService tagValidationService; private final TagAnalyticsClient tagAnalyticsClient; + private final EventCacheService eventCacheService; + private final TagCacheService tagCacheService; + private final ManyToManyCacheIndexService relationIndexService; @Override @Transactional @@ -61,6 +70,11 @@ public TagDto addTagToEvent(UUID eventId, UUID tagId) { } event.addTag(tag); + relationIndexService.add(EVENT_TAG_RELATION, eventId, tagId); + + eventCacheService.refresh(eventId); + tagCacheService.refresh(tagId); + tagAnalyticsClient.incrementUsage(tag.getId()); log.info("Тег с id={} добавлен к событию с id={}", tagId, eventId); @@ -91,6 +105,7 @@ public PagedResponse getTagsByEvent(UUID eventId, Pageable pageable) { @Override @Transactional(readOnly = true) + @Cacheable(value = "tags_by_event", key = "#eventId + ':' + #tagId") public TagWithStatsDto getTagByEvent(UUID eventId, UUID tagId) { eventReadService.findById(eventId); Tag tag = tagReadService.findByIdAndEventsId(tagId, eventId); @@ -112,6 +127,10 @@ public TagDto updateTag(UUID tagId, TagUpdateDto dto) { tag = tagMapper.updateTagFromDto(dto, tag); tag = tagRepository.save(tag); + + Set eventIds = relationIndexService.getLeftIds(EVENT_TAG_RELATION, tagId); + tagCacheService.refreshByEventBatch(tagId, eventIds); + log.info("Обновлен тег с id={}", tagId); return tagMapper.toDto(tag); } @@ -123,12 +142,16 @@ public void deleteForEvent(UUID eventId, UUID tagId) { Tag tag = tagReadService.findByIdAndEventsId(tagId, eventId); event.removeTag(tag); + relationIndexService.remove(EVENT_TAG_RELATION, eventId, tagId); + + eventCacheService.refresh(eventId); + tagCacheService.refreshByEvent(eventId, tagId); + log.info("Тег с id={} отвязан от события с id={}", tagId, eventId); } @Override @Transactional - @CacheEvict(value = "tags", key = "#tagId") public void deleteTag(UUID tagId) { Tag tag = tagReadService.findById(tagId); @@ -140,6 +163,9 @@ public void deleteTag(UUID tagId) { } tagRepository.deleteById(tagId); + + tagCacheService.evictAll(tagId); + log.info("Удален тег с id={}", tagId); } } diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/UserMetadataServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/UserMetadataServiceImpl.java index 6eae5cc..b303b28 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/UserMetadataServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/UserMetadataServiceImpl.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -38,6 +39,7 @@ public PagedResponse getUserMetadata(Pageable pageable) { @Override @Transactional(readOnly = true) + @Cacheable(value = "userMetadata", key = "#userId") public UserMetadataDto getByUserId(UUID userId) { userReadService.findById(userId); UserMetadata userMetadata = metadataReadService.findByUserId(userId); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/UserReadService.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/UserReadService.java index c0f98f6..387730c 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/UserReadService.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/UserReadService.java @@ -3,7 +3,6 @@ import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.eventhub.api.exception.ConflictException; @@ -19,7 +18,6 @@ public class UserReadService { private final UserRepository userRepository; @Transactional(readOnly = true) - @Cacheable(value = "users", key = "#id") public User findById(UUID id) { return userRepository.findById(id).orElseThrow(() -> { log.error("Пользователь с id={} не найден", id); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/UserServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/UserServiceImpl.java index b8accf0..f9e2759 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/UserServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/UserServiceImpl.java @@ -4,6 +4,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.Caching; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -57,6 +59,7 @@ public PagedResponse getUsers(Pageable pageable) { @Override @Transactional(readOnly = true) + @Cacheable(value = "users", key = "#id") public UserDto getUserById(UUID id) { User user = userReadService.findById(id); log.info("Запрошен пользователь с id={}", id); @@ -80,7 +83,10 @@ public UserDto updateUser(UUID id, UserUpdateDto dto) { @Override @Transactional - @CacheEvict(value = "users", key = "#id") + @Caching(evict = { + @CacheEvict(value = "users", key = "#id"), + @CacheEvict(value = "userMetadata", key = "#id") + }) public void deleteUser(UUID id) { userReadService.findById(id); userRepository.deleteById(id); diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/cache/CategoryCacheInvalidator.java b/src/main/java/ru/practicum/eventhub/infrastructure/cache/CategoryCacheInvalidator.java new file mode 100644 index 0000000..64f9f0a --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/infrastructure/cache/CategoryCacheInvalidator.java @@ -0,0 +1,34 @@ +package ru.practicum.eventhub.infrastructure.cache; + +import lombok.RequiredArgsConstructor; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; +import ru.practicum.eventhub.domain.event.CategoryDeletedEvent; + +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +@Component +@RequiredArgsConstructor +public class CategoryCacheInvalidator { + private final CacheManager cacheManager; + private final ProjectCacheIndexService cacheIndexService; + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void handle(CategoryDeletedEvent event) { + UUID categoryId = event.categoryId(); + + Optional.ofNullable(cacheManager.getCache("projects")) + .ifPresent(projectsCache -> { + Set projectIds = cacheIndexService.getProjects(categoryId); + if (projectIds != null) { + projectIds.forEach(pid -> projectsCache.evict(categoryId + ":" + pid)); + } + }); + + cacheIndexService.deleteCategoryIndex(categoryId); + } +} diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/cache/CategoryCacheService.java b/src/main/java/ru/practicum/eventhub/infrastructure/cache/CategoryCacheService.java new file mode 100644 index 0000000..69f8c95 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/infrastructure/cache/CategoryCacheService.java @@ -0,0 +1,27 @@ +package ru.practicum.eventhub.infrastructure.cache; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CachePut; +import org.springframework.stereotype.Component; +import ru.practicum.eventhub.api.dto.response.CategoryDto; +import ru.practicum.eventhub.api.mapper.CategoryMapper; +import ru.practicum.eventhub.domain.model.Category; +import ru.practicum.eventhub.domain.service.impl.CategoryReadService; + +import java.util.UUID; + +@Slf4j +@Component +@RequiredArgsConstructor +public class CategoryCacheService { + private final CategoryReadService categoryReadService; + private final CategoryMapper categoryMapper; + + @CachePut(value = "categories", key = "#categoryId") + public CategoryDto refresh(UUID categoryId) { + Category category = categoryReadService.findById(categoryId); + log.info("Обновлен кеш категории c id={}", categoryId); + return categoryMapper.toDto(category); + } +} diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/cache/EventCacheService.java b/src/main/java/ru/practicum/eventhub/infrastructure/cache/EventCacheService.java new file mode 100644 index 0000000..0f38ae2 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/infrastructure/cache/EventCacheService.java @@ -0,0 +1,39 @@ +package ru.practicum.eventhub.infrastructure.cache; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CachePut; +import org.springframework.stereotype.Component; +import ru.practicum.eventhub.api.dto.response.EventDto; +import ru.practicum.eventhub.api.mapper.EventMapper; +import ru.practicum.eventhub.domain.model.Event; +import ru.practicum.eventhub.domain.service.impl.EventReadService; + +import java.util.UUID; + +@Slf4j +@Component +@RequiredArgsConstructor +public class EventCacheService { + private final EventReadService eventReadService; + private final EventMapper eventMapper; + private final CacheManager cacheManager; + + @CachePut(value = "events", key = "#eventId") + public EventDto refresh(UUID eventId) { + Event event = eventReadService.findById(eventId); + log.info("Обновлен кэш события с id={}", eventId); + return eventMapper.toDto(event); + } + + public void evict(UUID eventId) { + Cache cache = cacheManager.getCache("events"); + if (cache != null) { + cache.evict(eventId); + log.info("Удален кэш события с id={}", eventId); + } + } +} + diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/cache/ManyToManyCacheIndexService.java b/src/main/java/ru/practicum/eventhub/infrastructure/cache/ManyToManyCacheIndexService.java new file mode 100644 index 0000000..cb33bc5 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/infrastructure/cache/ManyToManyCacheIndexService.java @@ -0,0 +1,54 @@ +package ru.practicum.eventhub.infrastructure.cache; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class ManyToManyCacheIndexService { + private final RedisTemplate redisTemplate; + + private String leftKey(String relation, UUID leftId) { + return relation + ":left:" + leftId; + } + + private String rightKey(String relation, UUID rightId) { + return relation + ":right:" + rightId; + } + + public void add(String relation, UUID leftId, UUID rightId) { + redisTemplate.opsForSet().add(leftKey(relation, leftId), rightId.toString()); + redisTemplate.opsForSet().add(rightKey(relation, rightId), leftId.toString()); + } + + public void remove(String relation, UUID leftId, UUID rightId) { + redisTemplate.opsForSet().remove(leftKey(relation, leftId), rightId.toString()); + redisTemplate.opsForSet().remove(rightKey(relation, rightId), leftId.toString()); + } + + public Set getRightIds(String relation, UUID leftId) { + Set ids = redisTemplate.opsForSet().members(leftKey(relation, leftId)); + if (ids == null) return Collections.emptySet(); + return ids.stream().map(UUID::fromString).collect(Collectors.toSet()); + } + + public Set getLeftIds(String relation, UUID rightId) { + Set ids = redisTemplate.opsForSet().members(rightKey(relation, rightId)); + if (ids == null) return Collections.emptySet(); + return ids.stream().map(UUID::fromString).collect(Collectors.toSet()); + } + + public void deleteLeft(String relation, UUID leftId) { + redisTemplate.delete(leftKey(relation, leftId)); + } + + public void deleteRight(String relation, UUID rightId) { + redisTemplate.delete(rightKey(relation, rightId)); + } +} diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/cache/ProjectCacheIndexService.java b/src/main/java/ru/practicum/eventhub/infrastructure/cache/ProjectCacheIndexService.java new file mode 100644 index 0000000..256bad8 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/infrastructure/cache/ProjectCacheIndexService.java @@ -0,0 +1,39 @@ +package ru.practicum.eventhub.infrastructure.cache; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.Set; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class ProjectCacheIndexService { + private final RedisTemplate redisTemplate; + private static final String CATEGORY_PROJECTS_PREFIX = "category_projects:"; + + private String buildKey(UUID categoryId) { + return CATEGORY_PROJECTS_PREFIX + categoryId; + } + + public void addProject(UUID categoryId, UUID projectId) { + redisTemplate.opsForSet() + .add(buildKey(categoryId), projectId.toString()); + } + + public void removeProject(UUID categoryId, UUID projectId) { + redisTemplate.opsForSet() + .remove(buildKey(categoryId), projectId.toString()); + } + + public Set getProjects(UUID categoryId) { + return redisTemplate.opsForSet() + .members(buildKey(categoryId)); + } + + public void deleteCategoryIndex(UUID categoryId) { + redisTemplate.delete(buildKey(categoryId)); + } +} + diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/cache/TagCacheService.java b/src/main/java/ru/practicum/eventhub/infrastructure/cache/TagCacheService.java new file mode 100644 index 0000000..f896687 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/infrastructure/cache/TagCacheService.java @@ -0,0 +1,95 @@ +package ru.practicum.eventhub.infrastructure.cache; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CachePut; +import org.springframework.stereotype.Component; +import ru.practicum.eventhub.api.dto.response.TagDto; +import ru.practicum.eventhub.api.dto.response.TagStatsDto; +import ru.practicum.eventhub.api.mapper.TagMapper; +import ru.practicum.eventhub.domain.model.Event; +import ru.practicum.eventhub.domain.model.Tag; +import ru.practicum.eventhub.domain.repository.TagRepository; +import ru.practicum.eventhub.domain.service.impl.TagReadService; +import ru.practicum.eventhub.infrastructure.TagAnalyticsClient; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +@Slf4j +@Component +@RequiredArgsConstructor +public class TagCacheService { + private static final String EVENT_TAG_RELATION = "event_tag"; + + private final TagReadService tagReadService; + private final TagMapper tagMapper; + private final ManyToManyCacheIndexService relationIndexService; + private final CacheManager cacheManager; + private final TagAnalyticsClient tagAnalyticsClient; + private final TagRepository tagRepository; + + @CachePut(value = "tags", key = "#tagId") + public TagDto refresh(UUID tagId) { + Tag tag = tagReadService.findById(tagId); + log.info("Обновлен кеш тега с id={}", tagId); + return tagMapper.toDto(tag); + } + + public void refreshByEvent(UUID eventId, UUID tagId) { + Tag tag = tagReadService.findByIdAndEventsId(tagId, eventId); + TagStatsDto stats = tagAnalyticsClient.getStats(tagId); + + Cache cache = cacheManager.getCache("tags_by_event"); + if (cache != null) { + cache.put(eventId + ":" + tagId, tagMapper.toDtoWithStats(tag, stats)); + + log.info("Обновлен кэш тега с id={} для события с id={}", tagId, eventId); + } + } + + public void refreshByEventBatch(UUID tagId, Set eventIds) { + if (eventIds == null || eventIds.isEmpty()) return; + + List tags = tagRepository.findAllByTagIdAndEventIds(tagId, eventIds); + + Cache cache = cacheManager.getCache("tags_by_event"); + if (cache == null) return; + + TagStatsDto stats = tagAnalyticsClient.getStats(tagId); + + for (Tag tag : tags) { + for (Event e : tag.getEvents()) { + if (eventIds.contains(e.getId())) { + cache.put(e.getId() + ":" + tagId, tagMapper.toDtoWithStats(tag, stats)); + + log.info("Обновлен кэш тега с id={} для события с id={}", tagId, e.getId()); + } + } + } + } + + public void evictAll(UUID tagId) { + Cache mainCache = cacheManager.getCache("tags"); + if (mainCache != null) { + mainCache.evict(tagId); + } + + Set eventIds = relationIndexService.getLeftIds(EVENT_TAG_RELATION, tagId); + + Cache eventCache = cacheManager.getCache("tags_by_event"); + if (eventCache != null) { + for (UUID eventId : eventIds) { + eventCache.evict(eventId + ":" + tagId); + log.info("Удален кэш тега с id={} для события с id={}", tagId, eventId); + } + } + + relationIndexService.deleteRight(EVENT_TAG_RELATION, tagId); + + log.info("Полностью очищен кэш и индекс для тега id={}", tagId); + } +} diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/fallback/TagAnalyticsFallback.java b/src/main/java/ru/practicum/eventhub/infrastructure/fallback/TagAnalyticsFallback.java index c155d0b..857c036 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/fallback/TagAnalyticsFallback.java +++ b/src/main/java/ru/practicum/eventhub/infrastructure/fallback/TagAnalyticsFallback.java @@ -19,6 +19,6 @@ public void incrementUsage(UUID id) { @Override public TagStatsDto getStats(UUID id) { log.warn("Сервис Tag Analytics недоступен. Не удалось получить статистику для тега с id={}", id); - return null; + return new TagStatsDto(0L, null); } } diff --git a/src/main/resources/db/migration/V1__create_initial_tables.sql b/src/main/resources/db/migration/V1__create_initial_tables.sql index acdab9a..0a0ee24 100644 --- a/src/main/resources/db/migration/V1__create_initial_tables.sql +++ b/src/main/resources/db/migration/V1__create_initial_tables.sql @@ -44,7 +44,7 @@ create table if not exists projects ( version bigint not null, name varchar(100) not null unique, description varchar(255), - category_id uuid not null references categories(id) on delete restrict, + category_id uuid not null references categories(id) on delete cascade, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), constraint chk_projects_name_length check (char_length(name) between 3 and 100), diff --git a/src/main/resources/openapi/openapi.yml b/src/main/resources/openapi/openapi.yml index c0c7dae..5a280e9 100644 --- a/src/main/resources/openapi/openapi.yml +++ b/src/main/resources/openapi/openapi.yml @@ -1488,15 +1488,6 @@ components: format: uuid description: Идентификатор пользователя example: "a1234567-89ab-cdef-0123-456789abcdef" - username: - type: string - description: Имя пользователя - example: "ivan.petrov" - email: - type: string - format: email - description: Email пользователя - example: "ivan.petrov@example.com" UserMetadataDto: type: object @@ -1575,10 +1566,6 @@ components: format: uuid description: Идентификатор категории example: "b1a7e8c2-3d4f-4e2a-9c1a-2b3c4d5e6f7a" - name: - type: string - description: Название категории - example: "Технологии" ProjectDto: type: object @@ -1617,10 +1604,6 @@ components: format: uuid description: Идентификатор проекта example: "e1f2a3b4-5678-1234-abcd-9876543210ab" - name: - type: string - description: Название проекта - example: "EventHub" EventDto: type: object @@ -1671,10 +1654,6 @@ components: format: uuid description: Идентификатор события example: "b1a7e8c2-3d4f-4e2a-9c1a-2b3c4d5e6f7a" - title: - type: string - description: Название события - example: "Митап по Java" TagDto: type: object @@ -1698,6 +1677,20 @@ components: items: $ref: '#/components/schemas/EventShortDto' + TagStatsDto: + type: object + properties: + usageCount: + type: integer + format: int64 + description: Число использований + example: 5 + lastUsedAt: + type: string + format: date-time + description: Дата и время последнего изменения статистики + example: "2025-11-05T15:30:00+03:00" + TagWithStatsDto: type: object properties: @@ -1718,17 +1711,9 @@ components: type: array description: Список событий с этим тегом items: - $ref: '#/components/schemas/EventDto' - usageCount: - type: integer - format: int64 - description: Число использований - example: 5 - lastUsedAt: - type: string - format: date-time - description: Дата и время последнего изменения статистики - example: "2025-11-05T15:30:00+03:00" + $ref: '#/components/schemas/EventShortDto' + stats: + $ref: '#/components/schemas/TagStatsDto' TagShortDto: type: object @@ -1738,10 +1723,6 @@ components: format: uuid description: Идентификатор тега example: "c1d2e3f4-5678-1234-abcd-9876543210ab" - name: - type: string - description: Название тега - example: "Java" PageMeta: type: object From d981c1c543f5b9be25e857f7504fada38dd77095 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 18 Feb 2026 03:14:46 +0300 Subject: [PATCH 5/8] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=BA=D1=8D=D1=88=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F=20=D1=82=D0=B5=D0=B3=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Обновлён конфиг для Feign клиента с добавлением кастомного ErrorDecoder - Добавлены исключения для обработки ошибок TagAnalyticsClient - Изменены пакеты для классов кэширования и сервисов - Обновлены методы кэширования тегов для использования нового подхода - Добавлены новые классы исключений для обработки ошибок --- .../api/dto/response/TagStatsDto.java | 3 +- .../cache/CategoryCacheInvalidator.java | 3 +- .../cache/CategoryCacheService.java | 2 +- .../cache/EventCacheService.java | 2 +- .../cache/TagCacheService.java | 39 +++++++++---------- .../CategoryApplicationService.java | 2 +- .../EventApplicationService.java | 2 +- .../MetadataApplicationService.java | 2 +- .../ProjectApplicationService.java | 2 +- .../{ => openapi}/TagApplicationService.java | 2 +- .../{ => openapi}/UserApplicationService.java | 2 +- .../controller/CategoryProjectController.java | 4 +- .../controller/EventTagController.java | 4 +- .../eventhub/controller/UserController.java | 4 +- .../domain/repository/TagRepository.java | 11 ------ .../domain/service/impl/EventServiceImpl.java | 6 +-- .../service/impl/ProjectServiceImpl.java | 4 +- .../domain/service/impl/TagServiceImpl.java | 12 +++--- .../{ => feign}/TagAnalyticsClient.java | 10 +++-- .../feign/config/FeignConfig.java | 15 +++++++ .../decoder/TagAnalyticsErrorDecoder.java | 23 +++++++++++ .../TagAnalyticsClientException.java | 7 ++++ .../feign/exception/TagNotFoundException.java | 7 ++++ .../fallback/TagAnalyticsFallback.java | 10 ++--- .../ManyToManyCacheIndexService.java | 2 +- .../ProjectCacheIndexService.java | 2 +- .../redis}/config/RedisCacheConfig.java | 6 +-- src/main/resources/application.yml | 5 ++- .../migration/V1__create_initial_tables.sql | 2 +- .../db/migration/V2__add_cascade_update.sql | 37 ++++++++++++++++++ src/main/resources/openapi/openapi.yml | 9 +---- 31 files changed, 158 insertions(+), 83 deletions(-) rename src/main/java/ru/practicum/eventhub/{infrastructure => application}/cache/CategoryCacheInvalidator.java (90%) rename src/main/java/ru/practicum/eventhub/{infrastructure => application}/cache/CategoryCacheService.java (94%) rename src/main/java/ru/practicum/eventhub/{infrastructure => application}/cache/EventCacheService.java (96%) rename src/main/java/ru/practicum/eventhub/{infrastructure => application}/cache/TagCacheService.java (73%) rename src/main/java/ru/practicum/eventhub/application/{ => openapi}/CategoryApplicationService.java (95%) rename src/main/java/ru/practicum/eventhub/application/{ => openapi}/EventApplicationService.java (95%) rename src/main/java/ru/practicum/eventhub/application/{ => openapi}/MetadataApplicationService.java (95%) rename src/main/java/ru/practicum/eventhub/application/{ => openapi}/ProjectApplicationService.java (95%) rename src/main/java/ru/practicum/eventhub/application/{ => openapi}/TagApplicationService.java (96%) rename src/main/java/ru/practicum/eventhub/application/{ => openapi}/UserApplicationService.java (95%) rename src/main/java/ru/practicum/eventhub/infrastructure/{ => feign}/TagAnalyticsClient.java (63%) create mode 100644 src/main/java/ru/practicum/eventhub/infrastructure/feign/config/FeignConfig.java create mode 100644 src/main/java/ru/practicum/eventhub/infrastructure/feign/decoder/TagAnalyticsErrorDecoder.java create mode 100644 src/main/java/ru/practicum/eventhub/infrastructure/feign/exception/TagAnalyticsClientException.java create mode 100644 src/main/java/ru/practicum/eventhub/infrastructure/feign/exception/TagNotFoundException.java rename src/main/java/ru/practicum/eventhub/infrastructure/{ => feign}/fallback/TagAnalyticsFallback.java (63%) rename src/main/java/ru/practicum/eventhub/infrastructure/{cache => redis}/ManyToManyCacheIndexService.java (97%) rename src/main/java/ru/practicum/eventhub/infrastructure/{cache => redis}/ProjectCacheIndexService.java (95%) rename src/main/java/ru/practicum/eventhub/{ => infrastructure/redis}/config/RedisCacheConfig.java (90%) create mode 100644 src/main/resources/db/migration/V2__add_cascade_update.sql diff --git a/src/main/java/ru/practicum/eventhub/api/dto/response/TagStatsDto.java b/src/main/java/ru/practicum/eventhub/api/dto/response/TagStatsDto.java index 76c4113..e3ed316 100644 --- a/src/main/java/ru/practicum/eventhub/api/dto/response/TagStatsDto.java +++ b/src/main/java/ru/practicum/eventhub/api/dto/response/TagStatsDto.java @@ -3,7 +3,6 @@ import java.time.OffsetDateTime; public record TagStatsDto( - long usageCount, - OffsetDateTime lastUsedAt + OffsetDateTime createdAt ) { } diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/cache/CategoryCacheInvalidator.java b/src/main/java/ru/practicum/eventhub/application/cache/CategoryCacheInvalidator.java similarity index 90% rename from src/main/java/ru/practicum/eventhub/infrastructure/cache/CategoryCacheInvalidator.java rename to src/main/java/ru/practicum/eventhub/application/cache/CategoryCacheInvalidator.java index 64f9f0a..2020f16 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/cache/CategoryCacheInvalidator.java +++ b/src/main/java/ru/practicum/eventhub/application/cache/CategoryCacheInvalidator.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.infrastructure.cache; +package ru.practicum.eventhub.application.cache; import lombok.RequiredArgsConstructor; import org.springframework.cache.CacheManager; @@ -6,6 +6,7 @@ import org.springframework.transaction.event.TransactionPhase; import org.springframework.transaction.event.TransactionalEventListener; import ru.practicum.eventhub.domain.event.CategoryDeletedEvent; +import ru.practicum.eventhub.infrastructure.redis.ProjectCacheIndexService; import java.util.Optional; import java.util.Set; diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/cache/CategoryCacheService.java b/src/main/java/ru/practicum/eventhub/application/cache/CategoryCacheService.java similarity index 94% rename from src/main/java/ru/practicum/eventhub/infrastructure/cache/CategoryCacheService.java rename to src/main/java/ru/practicum/eventhub/application/cache/CategoryCacheService.java index 69f8c95..6b8d502 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/cache/CategoryCacheService.java +++ b/src/main/java/ru/practicum/eventhub/application/cache/CategoryCacheService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.infrastructure.cache; +package ru.practicum.eventhub.application.cache; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/cache/EventCacheService.java b/src/main/java/ru/practicum/eventhub/application/cache/EventCacheService.java similarity index 96% rename from src/main/java/ru/practicum/eventhub/infrastructure/cache/EventCacheService.java rename to src/main/java/ru/practicum/eventhub/application/cache/EventCacheService.java index 0f38ae2..4de168d 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/cache/EventCacheService.java +++ b/src/main/java/ru/practicum/eventhub/application/cache/EventCacheService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.infrastructure.cache; +package ru.practicum.eventhub.application.cache; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/cache/TagCacheService.java b/src/main/java/ru/practicum/eventhub/application/cache/TagCacheService.java similarity index 73% rename from src/main/java/ru/practicum/eventhub/infrastructure/cache/TagCacheService.java rename to src/main/java/ru/practicum/eventhub/application/cache/TagCacheService.java index f896687..602c39e 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/cache/TagCacheService.java +++ b/src/main/java/ru/practicum/eventhub/application/cache/TagCacheService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.infrastructure.cache; +package ru.practicum.eventhub.application.cache; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -8,14 +8,13 @@ import org.springframework.stereotype.Component; import ru.practicum.eventhub.api.dto.response.TagDto; import ru.practicum.eventhub.api.dto.response.TagStatsDto; +import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; import ru.practicum.eventhub.api.mapper.TagMapper; -import ru.practicum.eventhub.domain.model.Event; import ru.practicum.eventhub.domain.model.Tag; -import ru.practicum.eventhub.domain.repository.TagRepository; import ru.practicum.eventhub.domain.service.impl.TagReadService; -import ru.practicum.eventhub.infrastructure.TagAnalyticsClient; +import ru.practicum.eventhub.infrastructure.feign.TagAnalyticsClient; +import ru.practicum.eventhub.infrastructure.redis.ManyToManyCacheIndexService; -import java.util.List; import java.util.Set; import java.util.UUID; @@ -30,7 +29,6 @@ public class TagCacheService { private final ManyToManyCacheIndexService relationIndexService; private final CacheManager cacheManager; private final TagAnalyticsClient tagAnalyticsClient; - private final TagRepository tagRepository; @CachePut(value = "tags", key = "#tagId") public TagDto refresh(UUID tagId) { @@ -40,35 +38,34 @@ public TagDto refresh(UUID tagId) { } public void refreshByEvent(UUID eventId, UUID tagId) { + Cache cache = cacheManager.getCache("tags_by_event"); + if (cache == null) return; + Tag tag = tagReadService.findByIdAndEventsId(tagId, eventId); TagStatsDto stats = tagAnalyticsClient.getStats(tagId); - Cache cache = cacheManager.getCache("tags_by_event"); - if (cache != null) { - cache.put(eventId + ":" + tagId, tagMapper.toDtoWithStats(tag, stats)); + TagWithStatsDto dto = tagMapper.toDtoWithStats(tag, stats); - log.info("Обновлен кэш тега с id={} для события с id={}", tagId, eventId); - } + cache.put(eventId + ":" + tagId, dto); + + log.info("Обновлен кэш тега с id={} для события с id={}", tagId, eventId); } - public void refreshByEventBatch(UUID tagId, Set eventIds) { + public void refreshCompositeCacheForTag(UUID tagId, Set eventIds) { if (eventIds == null || eventIds.isEmpty()) return; - List tags = tagRepository.findAllByTagIdAndEventIds(tagId, eventIds); - Cache cache = cacheManager.getCache("tags_by_event"); if (cache == null) return; + Tag tag = tagReadService.findById(tagId); TagStatsDto stats = tagAnalyticsClient.getStats(tagId); - for (Tag tag : tags) { - for (Event e : tag.getEvents()) { - if (eventIds.contains(e.getId())) { - cache.put(e.getId() + ":" + tagId, tagMapper.toDtoWithStats(tag, stats)); + TagWithStatsDto dto = tagMapper.toDtoWithStats(tag, stats); - log.info("Обновлен кэш тега с id={} для события с id={}", tagId, e.getId()); - } - } + for (UUID eventId : eventIds) { + cache.put(eventId + ":" + tagId, dto); + + log.info("Обновлен кэш тега с id={} для события с id={}", tagId, eventId); } } diff --git a/src/main/java/ru/practicum/eventhub/application/CategoryApplicationService.java b/src/main/java/ru/practicum/eventhub/application/openapi/CategoryApplicationService.java similarity index 95% rename from src/main/java/ru/practicum/eventhub/application/CategoryApplicationService.java rename to src/main/java/ru/practicum/eventhub/application/openapi/CategoryApplicationService.java index a651a83..9a214f7 100644 --- a/src/main/java/ru/practicum/eventhub/application/CategoryApplicationService.java +++ b/src/main/java/ru/practicum/eventhub/application/openapi/CategoryApplicationService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.application; +package ru.practicum.eventhub.application.openapi; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/ru/practicum/eventhub/application/EventApplicationService.java b/src/main/java/ru/practicum/eventhub/application/openapi/EventApplicationService.java similarity index 95% rename from src/main/java/ru/practicum/eventhub/application/EventApplicationService.java rename to src/main/java/ru/practicum/eventhub/application/openapi/EventApplicationService.java index 10347b3..8f119b4 100644 --- a/src/main/java/ru/practicum/eventhub/application/EventApplicationService.java +++ b/src/main/java/ru/practicum/eventhub/application/openapi/EventApplicationService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.application; +package ru.practicum.eventhub.application.openapi; import lombok.NonNull; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/ru/practicum/eventhub/application/MetadataApplicationService.java b/src/main/java/ru/practicum/eventhub/application/openapi/MetadataApplicationService.java similarity index 95% rename from src/main/java/ru/practicum/eventhub/application/MetadataApplicationService.java rename to src/main/java/ru/practicum/eventhub/application/openapi/MetadataApplicationService.java index 8df19fe..af7f7ec 100644 --- a/src/main/java/ru/practicum/eventhub/application/MetadataApplicationService.java +++ b/src/main/java/ru/practicum/eventhub/application/openapi/MetadataApplicationService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.application; +package ru.practicum.eventhub.application.openapi; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/ru/practicum/eventhub/application/ProjectApplicationService.java b/src/main/java/ru/practicum/eventhub/application/openapi/ProjectApplicationService.java similarity index 95% rename from src/main/java/ru/practicum/eventhub/application/ProjectApplicationService.java rename to src/main/java/ru/practicum/eventhub/application/openapi/ProjectApplicationService.java index 2f9fbb1..513b698 100644 --- a/src/main/java/ru/practicum/eventhub/application/ProjectApplicationService.java +++ b/src/main/java/ru/practicum/eventhub/application/openapi/ProjectApplicationService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.application; +package ru.practicum.eventhub.application.openapi; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/ru/practicum/eventhub/application/TagApplicationService.java b/src/main/java/ru/practicum/eventhub/application/openapi/TagApplicationService.java similarity index 96% rename from src/main/java/ru/practicum/eventhub/application/TagApplicationService.java rename to src/main/java/ru/practicum/eventhub/application/openapi/TagApplicationService.java index a475b60..4440556 100644 --- a/src/main/java/ru/practicum/eventhub/application/TagApplicationService.java +++ b/src/main/java/ru/practicum/eventhub/application/openapi/TagApplicationService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.application; +package ru.practicum.eventhub.application.openapi; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/ru/practicum/eventhub/application/UserApplicationService.java b/src/main/java/ru/practicum/eventhub/application/openapi/UserApplicationService.java similarity index 95% rename from src/main/java/ru/practicum/eventhub/application/UserApplicationService.java rename to src/main/java/ru/practicum/eventhub/application/openapi/UserApplicationService.java index 5903a42..0a989ce 100644 --- a/src/main/java/ru/practicum/eventhub/application/UserApplicationService.java +++ b/src/main/java/ru/practicum/eventhub/application/openapi/UserApplicationService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.application; +package ru.practicum.eventhub.application.openapi; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/ru/practicum/eventhub/controller/CategoryProjectController.java b/src/main/java/ru/practicum/eventhub/controller/CategoryProjectController.java index bc6d125..e8b3def 100644 --- a/src/main/java/ru/practicum/eventhub/controller/CategoryProjectController.java +++ b/src/main/java/ru/practicum/eventhub/controller/CategoryProjectController.java @@ -13,8 +13,8 @@ import ru.practicum.eventhub.api.dto.response.ProjectDto; import ru.practicum.eventhub.api.model.PageOfCategories; import ru.practicum.eventhub.api.model.PageOfProjects; -import ru.practicum.eventhub.application.CategoryApplicationService; -import ru.practicum.eventhub.application.ProjectApplicationService; +import ru.practicum.eventhub.application.openapi.CategoryApplicationService; +import ru.practicum.eventhub.application.openapi.ProjectApplicationService; import ru.practicum.eventhub.domain.service.CategoryService; import ru.practicum.eventhub.domain.service.ProjectService; diff --git a/src/main/java/ru/practicum/eventhub/controller/EventTagController.java b/src/main/java/ru/practicum/eventhub/controller/EventTagController.java index 95abac3..e467fd2 100644 --- a/src/main/java/ru/practicum/eventhub/controller/EventTagController.java +++ b/src/main/java/ru/practicum/eventhub/controller/EventTagController.java @@ -15,8 +15,8 @@ import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; import ru.practicum.eventhub.api.model.PageOfEvents; import ru.practicum.eventhub.api.model.PageOfTags; -import ru.practicum.eventhub.application.EventApplicationService; -import ru.practicum.eventhub.application.TagApplicationService; +import ru.practicum.eventhub.application.openapi.EventApplicationService; +import ru.practicum.eventhub.application.openapi.TagApplicationService; import ru.practicum.eventhub.domain.service.EventService; import ru.practicum.eventhub.domain.service.TagService; diff --git a/src/main/java/ru/practicum/eventhub/controller/UserController.java b/src/main/java/ru/practicum/eventhub/controller/UserController.java index 71d2edb..35ab201 100644 --- a/src/main/java/ru/practicum/eventhub/controller/UserController.java +++ b/src/main/java/ru/practicum/eventhub/controller/UserController.java @@ -12,8 +12,8 @@ import ru.practicum.eventhub.api.dto.response.UserMetadataDto; import ru.practicum.eventhub.api.model.PageOfMetadata; import ru.practicum.eventhub.api.model.PageOfUsers; -import ru.practicum.eventhub.application.MetadataApplicationService; -import ru.practicum.eventhub.application.UserApplicationService; +import ru.practicum.eventhub.application.openapi.MetadataApplicationService; +import ru.practicum.eventhub.application.openapi.UserApplicationService; import ru.practicum.eventhub.domain.service.UserMetadataService; import ru.practicum.eventhub.domain.service.UserService; diff --git a/src/main/java/ru/practicum/eventhub/domain/repository/TagRepository.java b/src/main/java/ru/practicum/eventhub/domain/repository/TagRepository.java index fed22e0..82a6646 100644 --- a/src/main/java/ru/practicum/eventhub/domain/repository/TagRepository.java +++ b/src/main/java/ru/practicum/eventhub/domain/repository/TagRepository.java @@ -5,13 +5,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import ru.practicum.eventhub.domain.model.Tag; -import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.UUID; public interface TagRepository extends JpaRepository { @@ -28,11 +24,4 @@ public interface TagRepository extends JpaRepository { boolean existsByName(String name); boolean existsByIdAndEventsId(UUID tagId, UUID eventId); - - @Query(""" - select t from Tag t - join t.events e - where t.id = :tagId and e.id in :eventIds - """) - List findAllByTagIdAndEventIds(@Param("tagId") UUID tagId, @Param("eventIds") Set eventIds); } diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java index c917127..b49dcfd 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java @@ -11,6 +11,8 @@ import ru.practicum.eventhub.api.dto.request.EventUpdateDto; import ru.practicum.eventhub.api.dto.response.EventDto; import ru.practicum.eventhub.api.mapper.EventMapper; +import ru.practicum.eventhub.application.cache.EventCacheService; +import ru.practicum.eventhub.application.cache.TagCacheService; import ru.practicum.eventhub.domain.dto.PagedResponse; import ru.practicum.eventhub.domain.model.Event; import ru.practicum.eventhub.domain.model.Tag; @@ -18,9 +20,7 @@ import ru.practicum.eventhub.domain.service.EventService; import ru.practicum.eventhub.domain.util.PageValidator; import ru.practicum.eventhub.domain.validation.EventValidationService; -import ru.practicum.eventhub.infrastructure.cache.EventCacheService; -import ru.practicum.eventhub.infrastructure.cache.ManyToManyCacheIndexService; -import ru.practicum.eventhub.infrastructure.cache.TagCacheService; +import ru.practicum.eventhub.infrastructure.redis.ManyToManyCacheIndexService; import java.util.Set; import java.util.UUID; diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectServiceImpl.java index 72fbb0c..3ca9038 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectServiceImpl.java @@ -12,6 +12,7 @@ import ru.practicum.eventhub.api.dto.request.ProjectUpdateDto; import ru.practicum.eventhub.api.dto.response.ProjectDto; import ru.practicum.eventhub.api.mapper.ProjectMapper; +import ru.practicum.eventhub.application.cache.CategoryCacheService; import ru.practicum.eventhub.domain.dto.PagedResponse; import ru.practicum.eventhub.domain.model.Category; import ru.practicum.eventhub.domain.model.Project; @@ -19,8 +20,7 @@ import ru.practicum.eventhub.domain.service.ProjectService; import ru.practicum.eventhub.domain.util.PageValidator; import ru.practicum.eventhub.domain.validation.ProjectValidationService; -import ru.practicum.eventhub.infrastructure.cache.CategoryCacheService; -import ru.practicum.eventhub.infrastructure.cache.ProjectCacheIndexService; +import ru.practicum.eventhub.infrastructure.redis.ProjectCacheIndexService; import java.util.UUID; diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java index 464f18e..0a730f6 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java @@ -15,6 +15,8 @@ import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; import ru.practicum.eventhub.api.exception.ConflictException; import ru.practicum.eventhub.api.mapper.TagMapper; +import ru.practicum.eventhub.application.cache.EventCacheService; +import ru.practicum.eventhub.application.cache.TagCacheService; import ru.practicum.eventhub.domain.dto.PagedResponse; import ru.practicum.eventhub.domain.model.Event; import ru.practicum.eventhub.domain.model.Tag; @@ -22,10 +24,8 @@ import ru.practicum.eventhub.domain.service.TagService; import ru.practicum.eventhub.domain.util.PageValidator; import ru.practicum.eventhub.domain.validation.TagValidationService; -import ru.practicum.eventhub.infrastructure.TagAnalyticsClient; -import ru.practicum.eventhub.infrastructure.cache.EventCacheService; -import ru.practicum.eventhub.infrastructure.cache.ManyToManyCacheIndexService; -import ru.practicum.eventhub.infrastructure.cache.TagCacheService; +import ru.practicum.eventhub.infrastructure.feign.TagAnalyticsClient; +import ru.practicum.eventhub.infrastructure.redis.ManyToManyCacheIndexService; import java.util.Iterator; import java.util.Set; @@ -75,7 +75,7 @@ public TagDto addTagToEvent(UUID eventId, UUID tagId) { eventCacheService.refresh(eventId); tagCacheService.refresh(tagId); - tagAnalyticsClient.incrementUsage(tag.getId()); + tagAnalyticsClient.createIfAbsent(tag.getId()); log.info("Тег с id={} добавлен к событию с id={}", tagId, eventId); return tagMapper.toDto(tag); @@ -129,7 +129,7 @@ public TagDto updateTag(UUID tagId, TagUpdateDto dto) { tag = tagRepository.save(tag); Set eventIds = relationIndexService.getLeftIds(EVENT_TAG_RELATION, tagId); - tagCacheService.refreshByEventBatch(tagId, eventIds); + tagCacheService.refreshCompositeCacheForTag(tagId, eventIds); log.info("Обновлен тег с id={}", tagId); return tagMapper.toDto(tag); diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/TagAnalyticsClient.java b/src/main/java/ru/practicum/eventhub/infrastructure/feign/TagAnalyticsClient.java similarity index 63% rename from src/main/java/ru/practicum/eventhub/infrastructure/TagAnalyticsClient.java rename to src/main/java/ru/practicum/eventhub/infrastructure/feign/TagAnalyticsClient.java index 87f4536..300d2a2 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/TagAnalyticsClient.java +++ b/src/main/java/ru/practicum/eventhub/infrastructure/feign/TagAnalyticsClient.java @@ -1,23 +1,25 @@ -package ru.practicum.eventhub.infrastructure; +package ru.practicum.eventhub.infrastructure.feign; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import ru.practicum.eventhub.api.dto.response.TagStatsDto; -import ru.practicum.eventhub.infrastructure.fallback.TagAnalyticsFallback; +import ru.practicum.eventhub.infrastructure.feign.config.FeignConfig; +import ru.practicum.eventhub.infrastructure.feign.fallback.TagAnalyticsFallback; import java.util.UUID; @FeignClient( name = "tag-analytics", url = "${services.tag-analytics.url}", - fallback = TagAnalyticsFallback.class + fallback = TagAnalyticsFallback.class, + configuration = FeignConfig.class ) public interface TagAnalyticsClient { @PostMapping("/api/v1/tags/{id}/used") - void incrementUsage(@PathVariable UUID id); + void createIfAbsent(@PathVariable UUID id); @GetMapping("/api/v1/tags/{id}/stats") TagStatsDto getStats(@PathVariable UUID id); diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/feign/config/FeignConfig.java b/src/main/java/ru/practicum/eventhub/infrastructure/feign/config/FeignConfig.java new file mode 100644 index 0000000..9e2ff99 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/infrastructure/feign/config/FeignConfig.java @@ -0,0 +1,15 @@ +package ru.practicum.eventhub.infrastructure.feign.config; + +import feign.codec.ErrorDecoder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import ru.practicum.eventhub.infrastructure.feign.decoder.TagAnalyticsErrorDecoder; + +@Configuration +public class FeignConfig { + + @Bean + public ErrorDecoder errorDecoder() { + return new TagAnalyticsErrorDecoder(); + } +} diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/feign/decoder/TagAnalyticsErrorDecoder.java b/src/main/java/ru/practicum/eventhub/infrastructure/feign/decoder/TagAnalyticsErrorDecoder.java new file mode 100644 index 0000000..4251986 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/infrastructure/feign/decoder/TagAnalyticsErrorDecoder.java @@ -0,0 +1,23 @@ +package ru.practicum.eventhub.infrastructure.feign.decoder; + +import feign.Response; +import feign.codec.ErrorDecoder; +import ru.practicum.eventhub.infrastructure.feign.exception.TagAnalyticsClientException; +import ru.practicum.eventhub.infrastructure.feign.exception.TagNotFoundException; + +public class TagAnalyticsErrorDecoder implements ErrorDecoder { + private final ErrorDecoder defaultDecoder = new Default(); + + @Override + public Exception decode(String methodKey, Response response) { + + if (response.status() >= 400 && response.status() < 500) { + if (response.status() == 404) { + return new TagNotFoundException("Ресурс не найден для метода: " + methodKey); + } + return new TagAnalyticsClientException("Ошибка клиента при вызове метода: " + methodKey + " с кодом статуса: " + response.status()); + } + + return defaultDecoder.decode(methodKey, response); + } +} diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/feign/exception/TagAnalyticsClientException.java b/src/main/java/ru/practicum/eventhub/infrastructure/feign/exception/TagAnalyticsClientException.java new file mode 100644 index 0000000..847c189 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/infrastructure/feign/exception/TagAnalyticsClientException.java @@ -0,0 +1,7 @@ +package ru.practicum.eventhub.infrastructure.feign.exception; + +public class TagAnalyticsClientException extends RuntimeException { + public TagAnalyticsClientException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/feign/exception/TagNotFoundException.java b/src/main/java/ru/practicum/eventhub/infrastructure/feign/exception/TagNotFoundException.java new file mode 100644 index 0000000..380b9fc --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/infrastructure/feign/exception/TagNotFoundException.java @@ -0,0 +1,7 @@ +package ru.practicum.eventhub.infrastructure.feign.exception; + +public class TagNotFoundException extends RuntimeException { + public TagNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/fallback/TagAnalyticsFallback.java b/src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallback.java similarity index 63% rename from src/main/java/ru/practicum/eventhub/infrastructure/fallback/TagAnalyticsFallback.java rename to src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallback.java index 857c036..2cb9f5c 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/fallback/TagAnalyticsFallback.java +++ b/src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallback.java @@ -1,9 +1,9 @@ -package ru.practicum.eventhub.infrastructure.fallback; +package ru.practicum.eventhub.infrastructure.feign.fallback; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import ru.practicum.eventhub.api.dto.response.TagStatsDto; -import ru.practicum.eventhub.infrastructure.TagAnalyticsClient; +import ru.practicum.eventhub.infrastructure.feign.TagAnalyticsClient; import java.util.UUID; @@ -12,13 +12,13 @@ public class TagAnalyticsFallback implements TagAnalyticsClient { @Override - public void incrementUsage(UUID id) { - log.warn("Сервис Tag Analytics недоступен. Не удалось увеличить счетчик использования для тега с id={}", id); + public void createIfAbsent(UUID id) { + log.warn("Сервис Tag Analytics недоступен. Не удалось создать статистику для тега с id={}", id); } @Override public TagStatsDto getStats(UUID id) { log.warn("Сервис Tag Analytics недоступен. Не удалось получить статистику для тега с id={}", id); - return new TagStatsDto(0L, null); + return new TagStatsDto(null); } } diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/cache/ManyToManyCacheIndexService.java b/src/main/java/ru/practicum/eventhub/infrastructure/redis/ManyToManyCacheIndexService.java similarity index 97% rename from src/main/java/ru/practicum/eventhub/infrastructure/cache/ManyToManyCacheIndexService.java rename to src/main/java/ru/practicum/eventhub/infrastructure/redis/ManyToManyCacheIndexService.java index cb33bc5..aba5ee8 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/cache/ManyToManyCacheIndexService.java +++ b/src/main/java/ru/practicum/eventhub/infrastructure/redis/ManyToManyCacheIndexService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.infrastructure.cache; +package ru.practicum.eventhub.infrastructure.redis; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/cache/ProjectCacheIndexService.java b/src/main/java/ru/practicum/eventhub/infrastructure/redis/ProjectCacheIndexService.java similarity index 95% rename from src/main/java/ru/practicum/eventhub/infrastructure/cache/ProjectCacheIndexService.java rename to src/main/java/ru/practicum/eventhub/infrastructure/redis/ProjectCacheIndexService.java index 256bad8..ea34b19 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/cache/ProjectCacheIndexService.java +++ b/src/main/java/ru/practicum/eventhub/infrastructure/redis/ProjectCacheIndexService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.infrastructure.cache; +package ru.practicum.eventhub.infrastructure.redis; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; diff --git a/src/main/java/ru/practicum/eventhub/config/RedisCacheConfig.java b/src/main/java/ru/practicum/eventhub/infrastructure/redis/config/RedisCacheConfig.java similarity index 90% rename from src/main/java/ru/practicum/eventhub/config/RedisCacheConfig.java rename to src/main/java/ru/practicum/eventhub/infrastructure/redis/config/RedisCacheConfig.java index b09d09b..32b28e9 100644 --- a/src/main/java/ru/practicum/eventhub/config/RedisCacheConfig.java +++ b/src/main/java/ru/practicum/eventhub/infrastructure/redis/config/RedisCacheConfig.java @@ -1,17 +1,17 @@ -package ru.practicum.eventhub.config; +package ru.practicum.eventhub.infrastructure.redis.config; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.stereotype.Component; import java.time.Duration; -@Component +@Configuration public class RedisCacheConfig { @Bean diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b912e55..4fd954f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -45,12 +45,15 @@ resilience4j: retry: instances: - tagAnalytics: + tag-analytics: maxAttempts: 3 waitDuration: 500ms exponentialBackoffMultiplier: 2 enableRandomizedWait: true randomizedWaitFactor: 0.5 + ignoreExceptions: + - ru.practicum.eventhub.infrastructure.feign.exception.TagAnalyticsClientException + - ru.practicum.eventhub.infrastructure.feign.exception.TagNotFoundException services: tag-analytics: diff --git a/src/main/resources/db/migration/V1__create_initial_tables.sql b/src/main/resources/db/migration/V1__create_initial_tables.sql index 0a0ee24..acdab9a 100644 --- a/src/main/resources/db/migration/V1__create_initial_tables.sql +++ b/src/main/resources/db/migration/V1__create_initial_tables.sql @@ -44,7 +44,7 @@ create table if not exists projects ( version bigint not null, name varchar(100) not null unique, description varchar(255), - category_id uuid not null references categories(id) on delete cascade, + category_id uuid not null references categories(id) on delete restrict, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), constraint chk_projects_name_length check (char_length(name) between 3 and 100), diff --git a/src/main/resources/db/migration/V2__add_cascade_update.sql b/src/main/resources/db/migration/V2__add_cascade_update.sql new file mode 100644 index 0000000..e2f01e3 --- /dev/null +++ b/src/main/resources/db/migration/V2__add_cascade_update.sql @@ -0,0 +1,37 @@ +-- Добавление ON UPDATE CASCADE для всех внешних ключей с ON DELETE CASCADE + +-- 1. projects.category_id: изменение с RESTRICT на CASCADE с обновлением +alter table projects drop constraint if exists projects_category_id_fkey; +alter table projects + add constraint projects_category_id_fkey + foreign key (category_id) + references categories(id) + on delete cascade + on update cascade; + +-- 2. user_metadata.user_id: добавление ON UPDATE CASCADE +alter table user_metadata drop constraint if exists user_metadata_user_id_fkey; +alter table user_metadata + add constraint user_metadata_user_id_fkey + foreign key (user_id) + references users(id) + on delete cascade + on update cascade; + +-- 3. event_tags.event_id: добавление ON UPDATE CASCADE +alter table event_tags drop constraint if exists event_tags_event_id_fkey; +alter table event_tags + add constraint event_tags_event_id_fkey + foreign key (event_id) + references events(id) + on delete cascade + on update cascade; + +-- 4. event_tags.tag_id: добавление ON UPDATE CASCADE +alter table event_tags drop constraint if exists event_tags_tag_id_fkey; +alter table event_tags + add constraint event_tags_tag_id_fkey + foreign key (tag_id) + references tags(id) + on delete cascade + on update cascade; diff --git a/src/main/resources/openapi/openapi.yml b/src/main/resources/openapi/openapi.yml index 5a280e9..330b07d 100644 --- a/src/main/resources/openapi/openapi.yml +++ b/src/main/resources/openapi/openapi.yml @@ -1680,15 +1680,10 @@ components: TagStatsDto: type: object properties: - usageCount: - type: integer - format: int64 - description: Число использований - example: 5 - lastUsedAt: + createdAt: type: string format: date-time - description: Дата и время последнего изменения статистики + description: Дата и время добавления тега к событию example: "2025-11-05T15:30:00+03:00" TagWithStatsDto: From adb9395899d23639366690a22afbd54d2e20decf Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 6 Mar 2026 22:24:30 +0300 Subject: [PATCH 6/8] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B8=D0=BD=D1=82=D0=B5=D0=B3=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=81=20Tag=20Analytics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлен класс TagAnalyticsFacade для работы с Tag Analytics - Реализован TagAnalyticsSagaOrchestrator для управления добавлением тегов к событиям - Обновлены сервисы TagService и TagCacheService для использования нового фасада - Изменены методы в контроллерах для работы с новыми классами - Обновлены конфигурации Feign для работы с Tag Analytics --- build.gradle | 1 - docker-compose.yml | 5 +- .../analytics/TagAnalyticsFacade.java | 37 +++++++ .../application/cache/TagCacheService.java | 35 ++++--- ...onService.java => CategoryPageMapper.java} | 2 +- ...ationService.java => EventPageMapper.java} | 2 +- ...onService.java => MetadataPageMapper.java} | 2 +- ...ionService.java => ProjectPageMapper.java} | 2 +- ...icationService.java => TagPageMapper.java} | 2 +- ...cationService.java => UserPageMapper.java} | 2 +- .../saga/TagAnalyticsSagaOrchestrator.java | 53 ++++++++++ .../controller/CategoryProjectController.java | 12 +-- .../controller/EventTagController.java | 22 +++-- .../eventhub/controller/UserController.java | 12 +-- .../advice/GlobalControllerAdvice.java | 50 +++++++--- .../domain/repository/TagRepository.java | 4 + .../eventhub/domain/service/TagService.java | 7 +- .../domain/service/impl/EventServiceImpl.java | 28 +++--- .../domain/service/impl/TagReadService.java | 7 ++ .../domain/service/impl/TagServiceImpl.java | 91 ++++++++++------- .../feign/TagAnalyticsClient.java | 20 ++-- .../config/FeignRequestIdInterceptor.java | 20 ++++ .../feign/fallback/TagAnalyticsFallback.java | 24 ----- .../fallback/TagAnalyticsFallbackFactory.java | 98 +++++++++++++++++++ .../eventhub/web/filter/RequestIdFilter.java | 12 ++- src/main/resources/application.yml | 8 +- src/main/resources/openapi/openapi.yml | 2 +- 27 files changed, 415 insertions(+), 145 deletions(-) create mode 100644 src/main/java/ru/practicum/eventhub/application/analytics/TagAnalyticsFacade.java rename src/main/java/ru/practicum/eventhub/application/openapi/{CategoryApplicationService.java => CategoryPageMapper.java} (96%) rename src/main/java/ru/practicum/eventhub/application/openapi/{EventApplicationService.java => EventPageMapper.java} (96%) rename src/main/java/ru/practicum/eventhub/application/openapi/{MetadataApplicationService.java => MetadataPageMapper.java} (96%) rename src/main/java/ru/practicum/eventhub/application/openapi/{ProjectApplicationService.java => ProjectPageMapper.java} (96%) rename src/main/java/ru/practicum/eventhub/application/openapi/{TagApplicationService.java => TagPageMapper.java} (97%) rename src/main/java/ru/practicum/eventhub/application/openapi/{UserApplicationService.java => UserPageMapper.java} (96%) create mode 100644 src/main/java/ru/practicum/eventhub/application/saga/TagAnalyticsSagaOrchestrator.java create mode 100644 src/main/java/ru/practicum/eventhub/infrastructure/feign/config/FeignRequestIdInterceptor.java delete mode 100644 src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallback.java create mode 100644 src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallbackFactory.java diff --git a/build.gradle b/build.gradle index bf14a7a..5bfc14a 100644 --- a/build.gradle +++ b/build.gradle @@ -39,7 +39,6 @@ dependencies { implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j' implementation "org.mapstruct:mapstruct:${mapstructVersion}" - implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-database-postgresql' implementation "io.swagger.core.v3:swagger-annotations:${swaggerVersion}" compileOnly 'org.projectlombok:lombok' diff --git a/docker-compose.yml b/docker-compose.yml index 99942a2..92d4b23 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,9 +3,10 @@ version: '3.7' services: postgres: image: postgres:14 + container_name: event-hub-db environment: - POSTGRES_USER: postgres POSTGRES_DB: postgres + POSTGRES_USER: postgres POSTGRES_PASSWORD: 123456 ports: - "5432:5432" @@ -13,4 +14,4 @@ services: redis: image: redis:8.4.0 ports: - - "6379:6379" \ No newline at end of file + - "6379:6379" diff --git a/src/main/java/ru/practicum/eventhub/application/analytics/TagAnalyticsFacade.java b/src/main/java/ru/practicum/eventhub/application/analytics/TagAnalyticsFacade.java new file mode 100644 index 0000000..8e1caec --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/application/analytics/TagAnalyticsFacade.java @@ -0,0 +1,37 @@ +package ru.practicum.eventhub.application.analytics; + +import io.github.resilience4j.retry.annotation.Retry; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.practicum.eventhub.api.dto.response.TagStatsDto; +import ru.practicum.eventhub.infrastructure.feign.TagAnalyticsClient; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class TagAnalyticsFacade { + private final TagAnalyticsClient tagAnalyticsClient; + + @Retry(name = "tag-analytics") + public TagStatsDto sendAnalytics(UUID tagId) { + return tagAnalyticsClient.createIfAbsent(tagId); + } + + @Retry(name = "tag-analytics") + public TagStatsDto getTagStats(UUID tagId) { + return tagAnalyticsClient.getTagStats(tagId); + } + + @Retry(name = "tag-analytics") + public Map getTagStatsBatch(Set tagIds) { + return tagAnalyticsClient.getTagStatsBatch(tagIds); + } + + @Retry(name = "tag-analytics") + public void delete(UUID tagId) { + tagAnalyticsClient.deleteTagAnalytics(tagId); + } +} diff --git a/src/main/java/ru/practicum/eventhub/application/cache/TagCacheService.java b/src/main/java/ru/practicum/eventhub/application/cache/TagCacheService.java index 602c39e..3391c22 100644 --- a/src/main/java/ru/practicum/eventhub/application/cache/TagCacheService.java +++ b/src/main/java/ru/practicum/eventhub/application/cache/TagCacheService.java @@ -10,11 +10,13 @@ import ru.practicum.eventhub.api.dto.response.TagStatsDto; import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; import ru.practicum.eventhub.api.mapper.TagMapper; +import ru.practicum.eventhub.application.analytics.TagAnalyticsFacade; import ru.practicum.eventhub.domain.model.Tag; import ru.practicum.eventhub.domain.service.impl.TagReadService; -import ru.practicum.eventhub.infrastructure.feign.TagAnalyticsClient; import ru.practicum.eventhub.infrastructure.redis.ManyToManyCacheIndexService; +import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; @@ -28,7 +30,7 @@ public class TagCacheService { private final TagMapper tagMapper; private final ManyToManyCacheIndexService relationIndexService; private final CacheManager cacheManager; - private final TagAnalyticsClient tagAnalyticsClient; + private final TagAnalyticsFacade tagAnalyticsFacade; @CachePut(value = "tags", key = "#tagId") public TagDto refresh(UUID tagId) { @@ -37,36 +39,43 @@ public TagDto refresh(UUID tagId) { return tagMapper.toDto(tag); } - public void refreshByEvent(UUID eventId, UUID tagId) { + public void refreshByEventBatch(UUID eventId, Set tagIds) { + if (tagIds == null || tagIds.isEmpty()) return; + Cache cache = cacheManager.getCache("tags_by_event"); if (cache == null) return; - Tag tag = tagReadService.findByIdAndEventsId(tagId, eventId); - TagStatsDto stats = tagAnalyticsClient.getStats(tagId); + List tags = tagReadService.findAllByIdInAndEventsId(tagIds, eventId); - TagWithStatsDto dto = tagMapper.toDtoWithStats(tag, stats); + Map statsMap = tagAnalyticsFacade.getTagStatsBatch(tagIds); - cache.put(eventId + ":" + tagId, dto); + for (Tag tag : tags) { + TagStatsDto stats = statsMap.get(tag.getId()); + TagWithStatsDto dto = tagMapper.toDtoWithStats(tag, stats); + cache.put(eventId + ":" + tag.getId(), dto); + log.debug("Обновлен кеш ивента с id={} для тега с id={}", eventId, tag.getId()); + } - log.info("Обновлен кэш тега с id={} для события с id={}", tagId, eventId); + log.info("Обновлен кеш ивента с id={} для всех связанных тегов", eventId); } - public void refreshCompositeCacheForTag(UUID tagId, Set eventIds) { + public void refreshCompositeCacheForTag(Tag tag, Set eventIds) { if (eventIds == null || eventIds.isEmpty()) return; Cache cache = cacheManager.getCache("tags_by_event"); if (cache == null) return; - Tag tag = tagReadService.findById(tagId); - TagStatsDto stats = tagAnalyticsClient.getStats(tagId); + UUID tagId = tag.getId(); + TagStatsDto stats = tagAnalyticsFacade.getTagStats(tagId); TagWithStatsDto dto = tagMapper.toDtoWithStats(tag, stats); for (UUID eventId : eventIds) { cache.put(eventId + ":" + tagId, dto); - - log.info("Обновлен кэш тега с id={} для события с id={}", tagId, eventId); + log.debug("Обновлен кэш тега с id={} для события с id={}", tagId, eventId); } + + log.info("Обновлен кэш тега с id={} для всех связанных событий", tagId); } public void evictAll(UUID tagId) { diff --git a/src/main/java/ru/practicum/eventhub/application/openapi/CategoryApplicationService.java b/src/main/java/ru/practicum/eventhub/application/openapi/CategoryPageMapper.java similarity index 96% rename from src/main/java/ru/practicum/eventhub/application/openapi/CategoryApplicationService.java rename to src/main/java/ru/practicum/eventhub/application/openapi/CategoryPageMapper.java index 9a214f7..26ca8d5 100644 --- a/src/main/java/ru/practicum/eventhub/application/openapi/CategoryApplicationService.java +++ b/src/main/java/ru/practicum/eventhub/application/openapi/CategoryPageMapper.java @@ -11,7 +11,7 @@ @Service @RequiredArgsConstructor -public class CategoryApplicationService { +public class CategoryPageMapper { private final CategoryService categoryService; public PageOfCategories getCategories(Pageable pageable) { diff --git a/src/main/java/ru/practicum/eventhub/application/openapi/EventApplicationService.java b/src/main/java/ru/practicum/eventhub/application/openapi/EventPageMapper.java similarity index 96% rename from src/main/java/ru/practicum/eventhub/application/openapi/EventApplicationService.java rename to src/main/java/ru/practicum/eventhub/application/openapi/EventPageMapper.java index 8f119b4..36dd328 100644 --- a/src/main/java/ru/practicum/eventhub/application/openapi/EventApplicationService.java +++ b/src/main/java/ru/practicum/eventhub/application/openapi/EventPageMapper.java @@ -11,7 +11,7 @@ @Service @RequiredArgsConstructor -public class EventApplicationService { +public class EventPageMapper { private final EventService eventService; public PageOfEvents getEvents(Pageable pageable) { diff --git a/src/main/java/ru/practicum/eventhub/application/openapi/MetadataApplicationService.java b/src/main/java/ru/practicum/eventhub/application/openapi/MetadataPageMapper.java similarity index 96% rename from src/main/java/ru/practicum/eventhub/application/openapi/MetadataApplicationService.java rename to src/main/java/ru/practicum/eventhub/application/openapi/MetadataPageMapper.java index af7f7ec..08d7775 100644 --- a/src/main/java/ru/practicum/eventhub/application/openapi/MetadataApplicationService.java +++ b/src/main/java/ru/practicum/eventhub/application/openapi/MetadataPageMapper.java @@ -11,7 +11,7 @@ @Service @RequiredArgsConstructor -public class MetadataApplicationService { +public class MetadataPageMapper { private final UserMetadataService userMetadataService; public PageOfMetadata getUserMetadata(Pageable pageable) { diff --git a/src/main/java/ru/practicum/eventhub/application/openapi/ProjectApplicationService.java b/src/main/java/ru/practicum/eventhub/application/openapi/ProjectPageMapper.java similarity index 96% rename from src/main/java/ru/practicum/eventhub/application/openapi/ProjectApplicationService.java rename to src/main/java/ru/practicum/eventhub/application/openapi/ProjectPageMapper.java index 513b698..d6166c2 100644 --- a/src/main/java/ru/practicum/eventhub/application/openapi/ProjectApplicationService.java +++ b/src/main/java/ru/practicum/eventhub/application/openapi/ProjectPageMapper.java @@ -13,7 +13,7 @@ @Service @RequiredArgsConstructor -public class ProjectApplicationService { +public class ProjectPageMapper { private final ProjectService projectService; public PageOfProjects getProjectsByCategory(UUID categoryId, Pageable pageable) { diff --git a/src/main/java/ru/practicum/eventhub/application/openapi/TagApplicationService.java b/src/main/java/ru/practicum/eventhub/application/openapi/TagPageMapper.java similarity index 97% rename from src/main/java/ru/practicum/eventhub/application/openapi/TagApplicationService.java rename to src/main/java/ru/practicum/eventhub/application/openapi/TagPageMapper.java index 4440556..ce81013 100644 --- a/src/main/java/ru/practicum/eventhub/application/openapi/TagApplicationService.java +++ b/src/main/java/ru/practicum/eventhub/application/openapi/TagPageMapper.java @@ -13,7 +13,7 @@ @Service @RequiredArgsConstructor -public class TagApplicationService { +public class TagPageMapper { private final TagService tagService; public PageOfTags getTags(Pageable pageable) { diff --git a/src/main/java/ru/practicum/eventhub/application/openapi/UserApplicationService.java b/src/main/java/ru/practicum/eventhub/application/openapi/UserPageMapper.java similarity index 96% rename from src/main/java/ru/practicum/eventhub/application/openapi/UserApplicationService.java rename to src/main/java/ru/practicum/eventhub/application/openapi/UserPageMapper.java index 0a989ce..a074e58 100644 --- a/src/main/java/ru/practicum/eventhub/application/openapi/UserApplicationService.java +++ b/src/main/java/ru/practicum/eventhub/application/openapi/UserPageMapper.java @@ -11,7 +11,7 @@ @Service @RequiredArgsConstructor -public class UserApplicationService { +public class UserPageMapper { private final UserService userService; public PageOfUsers getUsers(Pageable pageable) { diff --git a/src/main/java/ru/practicum/eventhub/application/saga/TagAnalyticsSagaOrchestrator.java b/src/main/java/ru/practicum/eventhub/application/saga/TagAnalyticsSagaOrchestrator.java new file mode 100644 index 0000000..fbf1d1a --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/application/saga/TagAnalyticsSagaOrchestrator.java @@ -0,0 +1,53 @@ +package ru.practicum.eventhub.application.saga; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.eventhub.api.dto.response.TagStatsDto; +import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; +import ru.practicum.eventhub.api.mapper.TagMapper; +import ru.practicum.eventhub.application.analytics.TagAnalyticsFacade; +import ru.practicum.eventhub.domain.model.Tag; +import ru.practicum.eventhub.domain.service.TagService; + +import java.util.UUID; + +@Slf4j +@Service +@RequiredArgsConstructor +public class TagAnalyticsSagaOrchestrator { + private final TagService tagService; + private final TagAnalyticsFacade analyticsFacade; + private final TagMapper tagMapper; + + public TagWithStatsDto addTagToEvent(UUID eventId, UUID tagId) { + boolean tagAdded = false; + boolean analyticsCreated = false; + + try { + Tag tag = tagService.addTagLocal(eventId, tagId); + tagAdded = true; + + TagStatsDto tagStatsDto = analyticsFacade.sendAnalytics(tagId); + analyticsCreated = true; + + tagService.cacheRefreshAfterAddTag(eventId, tagId); + + log.info("Тег с id={} добавлен к событию с id={}", tagId, eventId); + return tagMapper.toDtoWithStats(tag, tagStatsDto); + + } catch (Exception ex) { + log.error("Ошибка при добавлении тега с id={} к событию с id={}: {}", tagId, eventId, ex.getMessage()); + + if (analyticsCreated) { + analyticsFacade.delete(tagId); + } + + if (tagAdded) { + tagService.removeTagLocal(eventId, tagId); + } + + throw ex; + } + } +} diff --git a/src/main/java/ru/practicum/eventhub/controller/CategoryProjectController.java b/src/main/java/ru/practicum/eventhub/controller/CategoryProjectController.java index e8b3def..4110749 100644 --- a/src/main/java/ru/practicum/eventhub/controller/CategoryProjectController.java +++ b/src/main/java/ru/practicum/eventhub/controller/CategoryProjectController.java @@ -13,8 +13,8 @@ import ru.practicum.eventhub.api.dto.response.ProjectDto; import ru.practicum.eventhub.api.model.PageOfCategories; import ru.practicum.eventhub.api.model.PageOfProjects; -import ru.practicum.eventhub.application.openapi.CategoryApplicationService; -import ru.practicum.eventhub.application.openapi.ProjectApplicationService; +import ru.practicum.eventhub.application.openapi.CategoryPageMapper; +import ru.practicum.eventhub.application.openapi.ProjectPageMapper; import ru.practicum.eventhub.domain.service.CategoryService; import ru.practicum.eventhub.domain.service.ProjectService; @@ -23,8 +23,8 @@ @RestController @RequiredArgsConstructor public class CategoryProjectController implements CategoriesApi { - private final CategoryApplicationService categoryApplicationService; - private final ProjectApplicationService projectApplicationService; + private final CategoryPageMapper categoryPageMapper; + private final ProjectPageMapper projectPageMapper; private final CategoryService categoryService; private final ProjectService projectService; @@ -54,7 +54,7 @@ public ResponseEntity deleteProjectForCategory(UUID categoryId, UUID proje @Override public ResponseEntity getCategories(Integer page, Integer size) { - PageOfCategories pageOfCategories = categoryApplicationService.getCategories(PageRequest.of(page, size)); + PageOfCategories pageOfCategories = categoryPageMapper.getCategories(PageRequest.of(page, size)); return ResponseEntity.ok(pageOfCategories); } @@ -72,7 +72,7 @@ public ResponseEntity getProjectByCategoryId(UUID categoryId, UUID p @Override public ResponseEntity getProjectsByCategoryId(UUID categoryId, Integer page, Integer size) { - PageOfProjects pageOfProjects = projectApplicationService.getProjectsByCategory(categoryId, PageRequest.of(page, size)); + PageOfProjects pageOfProjects = projectPageMapper.getProjectsByCategory(categoryId, PageRequest.of(page, size)); return ResponseEntity.ok(pageOfProjects); } diff --git a/src/main/java/ru/practicum/eventhub/controller/EventTagController.java b/src/main/java/ru/practicum/eventhub/controller/EventTagController.java index e467fd2..74d8b4c 100644 --- a/src/main/java/ru/practicum/eventhub/controller/EventTagController.java +++ b/src/main/java/ru/practicum/eventhub/controller/EventTagController.java @@ -15,8 +15,9 @@ import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; import ru.practicum.eventhub.api.model.PageOfEvents; import ru.practicum.eventhub.api.model.PageOfTags; -import ru.practicum.eventhub.application.openapi.EventApplicationService; -import ru.practicum.eventhub.application.openapi.TagApplicationService; +import ru.practicum.eventhub.application.openapi.EventPageMapper; +import ru.practicum.eventhub.application.openapi.TagPageMapper; +import ru.practicum.eventhub.application.saga.TagAnalyticsSagaOrchestrator; import ru.practicum.eventhub.domain.service.EventService; import ru.practicum.eventhub.domain.service.TagService; @@ -25,17 +26,18 @@ @RestController @RequiredArgsConstructor public class EventTagController implements EventsApi { - private final EventApplicationService eventApplicationService; - private final TagApplicationService tagApplicationService; + private final EventPageMapper eventPageMapper; + private final TagPageMapper tagPageMapper; private final EventService eventService; private final TagService tagService; + private final TagAnalyticsSagaOrchestrator tagSagaOrchestrator; @Override - public ResponseEntity addTagToEvent(UUID eventId, UUID tagId) { - TagDto tagDto = tagService.addTagToEvent(eventId, tagId); + public ResponseEntity addTagToEvent(UUID eventId, UUID tagId) { + TagWithStatsDto tagWithStatsDto = tagSagaOrchestrator.addTagToEvent(eventId, tagId); return ResponseEntity .status(HttpStatus.CREATED) - .body(tagDto); + .body(tagWithStatsDto); } @Override @@ -86,7 +88,7 @@ public ResponseEntity getEventById(UUID eventId) { @Override public ResponseEntity getEvents(Integer page, Integer size) { - PageOfEvents pageOfEvents = eventApplicationService.getEvents(PageRequest.of(page, size)); + PageOfEvents pageOfEvents = eventPageMapper.getEvents(PageRequest.of(page, size)); return ResponseEntity.ok(pageOfEvents); } @@ -98,13 +100,13 @@ public ResponseEntity getTagByEventId(UUID eventId, UUID tagId) @Override public ResponseEntity getTags(Integer page, Integer size) { - PageOfTags pageOfTags = tagApplicationService.getTags(PageRequest.of(page, size)); + PageOfTags pageOfTags = tagPageMapper.getTags(PageRequest.of(page, size)); return ResponseEntity.ok(pageOfTags); } @Override public ResponseEntity getTagsByEventId(UUID eventId, Integer page, Integer size) { - PageOfTags pageOfTags = tagApplicationService.getTagsByEvent(eventId, PageRequest.of(page, size)); + PageOfTags pageOfTags = tagPageMapper.getTagsByEvent(eventId, PageRequest.of(page, size)); return ResponseEntity.ok(pageOfTags); } diff --git a/src/main/java/ru/practicum/eventhub/controller/UserController.java b/src/main/java/ru/practicum/eventhub/controller/UserController.java index 35ab201..58000ac 100644 --- a/src/main/java/ru/practicum/eventhub/controller/UserController.java +++ b/src/main/java/ru/practicum/eventhub/controller/UserController.java @@ -12,8 +12,8 @@ import ru.practicum.eventhub.api.dto.response.UserMetadataDto; import ru.practicum.eventhub.api.model.PageOfMetadata; import ru.practicum.eventhub.api.model.PageOfUsers; -import ru.practicum.eventhub.application.openapi.MetadataApplicationService; -import ru.practicum.eventhub.application.openapi.UserApplicationService; +import ru.practicum.eventhub.application.openapi.MetadataPageMapper; +import ru.practicum.eventhub.application.openapi.UserPageMapper; import ru.practicum.eventhub.domain.service.UserMetadataService; import ru.practicum.eventhub.domain.service.UserService; @@ -22,8 +22,8 @@ @RestController @RequiredArgsConstructor public class UserController implements UsersApi { - private final UserApplicationService userApplicationService; - private final MetadataApplicationService metadataApplicationService; + private final UserPageMapper userPageMapper; + private final MetadataPageMapper metadataPageMapper; private final UserService userService; private final UserMetadataService userMetadataService; @@ -51,7 +51,7 @@ public ResponseEntity getUserById(UUID userId) { @Override public ResponseEntity getUserMetadata(Integer page, Integer size) { - PageOfMetadata pageOfUsers = metadataApplicationService.getUserMetadata(PageRequest.of(page, size)); + PageOfMetadata pageOfUsers = metadataPageMapper.getUserMetadata(PageRequest.of(page, size)); return ResponseEntity.ok(pageOfUsers); } @@ -63,7 +63,7 @@ public ResponseEntity getUserMetadataById(UUID userId) { @Override public ResponseEntity getUsers(Integer page, Integer size) { - PageOfUsers pageOfUsers = userApplicationService.getUsers(PageRequest.of(page, size)); + PageOfUsers pageOfUsers = userPageMapper.getUsers(PageRequest.of(page, size)); return ResponseEntity.ok(pageOfUsers); } diff --git a/src/main/java/ru/practicum/eventhub/controller/advice/GlobalControllerAdvice.java b/src/main/java/ru/practicum/eventhub/controller/advice/GlobalControllerAdvice.java index 160ce11..04c4c93 100644 --- a/src/main/java/ru/practicum/eventhub/controller/advice/GlobalControllerAdvice.java +++ b/src/main/java/ru/practicum/eventhub/controller/advice/GlobalControllerAdvice.java @@ -6,6 +6,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.context.MessageSourceResolvable; import org.springframework.http.HttpStatus; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -14,11 +15,14 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.HandlerMethodValidationException; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.servlet.NoHandlerFoundException; import ru.practicum.eventhub.api.exception.BadRequestException; import ru.practicum.eventhub.api.exception.ConflictException; import ru.practicum.eventhub.api.exception.ErrorResponse; import ru.practicum.eventhub.api.exception.ForbiddenException; +import ru.practicum.eventhub.infrastructure.feign.exception.TagAnalyticsClientException; +import ru.practicum.eventhub.infrastructure.feign.exception.TagNotFoundException; import java.util.stream.Collectors; @@ -29,25 +33,43 @@ public class GlobalControllerAdvice { @ExceptionHandler(EntityNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ErrorResponse handleNotFoundException(Exception exception, HttpServletRequest request) { - return buildResponse(HttpStatus.NOT_FOUND, exception, request); + return buildResponse(HttpStatus.NOT_FOUND, exception.getMessage(), exception, request); } @ExceptionHandler(BadRequestException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleBadRequestException(BadRequestException exception, HttpServletRequest request) { - return buildResponse(HttpStatus.BAD_REQUEST, exception, request); + return buildResponse(HttpStatus.BAD_REQUEST, exception.getMessage(), exception, request); } @ExceptionHandler(ForbiddenException.class) @ResponseStatus(HttpStatus.FORBIDDEN) public ErrorResponse handleForbiddenException(ForbiddenException exception, HttpServletRequest request) { - return buildResponse(HttpStatus.FORBIDDEN, exception, request); + return buildResponse(HttpStatus.FORBIDDEN, exception.getMessage(), exception, request); } @ExceptionHandler(ConflictException.class) @ResponseStatus(HttpStatus.CONFLICT) public ErrorResponse handleConflictException(ConflictException exception, HttpServletRequest request) { - return buildResponse(HttpStatus.CONFLICT, exception, request); + return buildResponse(HttpStatus.CONFLICT, exception.getMessage(), exception, request); + } + + @ExceptionHandler(TagNotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleTagNotFoundException(TagNotFoundException exception, HttpServletRequest request) { + return buildResponse(HttpStatus.NOT_FOUND, exception.getMessage(), exception, request); + } + + @ExceptionHandler(TagAnalyticsClientException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleTagAnalyticsClientException(TagAnalyticsClientException exception, HttpServletRequest request) { + return buildResponse(HttpStatus.BAD_REQUEST, exception.getMessage(), exception, request); + } + + @ExceptionHandler({IllegalArgumentException.class, MethodArgumentTypeMismatchException.class, HttpMessageNotReadableException.class}) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleBadInput(Exception exception, HttpServletRequest request) { + return buildResponse(HttpStatus.BAD_REQUEST, exception.getMessage(), exception, request); } @ExceptionHandler(ConstraintViolationException.class) @@ -58,7 +80,7 @@ public ErrorResponse handleConstraintViolation(ConstraintViolationException ex, .collect(Collectors.joining("; ")); String message = violations.isEmpty() ? "Constraint violation" : violations; - return buildResponse(HttpStatus.BAD_REQUEST, message, null, request); + return buildResponse(HttpStatus.BAD_REQUEST, message, ex, request); } @ExceptionHandler(MethodArgumentNotValidException.class) @@ -70,7 +92,7 @@ public ErrorResponse handleMethodArgumentNotValid(MethodArgumentNotValidExceptio String message = validationErrors.isEmpty() ? "Validation failed" : validationErrors; - return buildResponse(HttpStatus.BAD_REQUEST, message, null, request); + return buildResponse(HttpStatus.BAD_REQUEST, message, ex, request); } @ExceptionHandler(HandlerMethodValidationException.class) @@ -82,41 +104,37 @@ public ErrorResponse handleHandlerMethodValidation(HandlerMethodValidationExcept String message = validationErrors.isEmpty() ? "Validation failed" : validationErrors; - return buildResponse(HttpStatus.BAD_REQUEST, message, null, request); + return buildResponse(HttpStatus.BAD_REQUEST, message, ex, request); } @ExceptionHandler(ServletRequestBindingException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleServletRequestBindingException(ServletRequestBindingException ex, HttpServletRequest request) { - return buildResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), null, request); + return buildResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), ex, request); } @ExceptionHandler(HttpRequestMethodNotSupportedException.class) @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) public ErrorResponse handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpServletRequest request) { - return buildResponse(HttpStatus.METHOD_NOT_ALLOWED, ex.getMessage(), null, request); + return buildResponse(HttpStatus.METHOD_NOT_ALLOWED, ex.getMessage(), ex, request); } @ExceptionHandler(HttpMediaTypeNotSupportedException.class) @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE) public ErrorResponse handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, HttpServletRequest request) { - return buildResponse(HttpStatus.UNSUPPORTED_MEDIA_TYPE, ex.getMessage(), null, request); + return buildResponse(HttpStatus.UNSUPPORTED_MEDIA_TYPE, ex.getMessage(), ex, request); } @ExceptionHandler(NoHandlerFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public ErrorResponse handleNoHandlerFoundException(NoHandlerFoundException ex, HttpServletRequest request) { - return buildResponse(HttpStatus.NOT_FOUND, ex.getMessage(), null, request); + return buildResponse(HttpStatus.NOT_FOUND, ex.getMessage(), ex, request); } @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ErrorResponse handleUnexpected(Exception exception, HttpServletRequest request) { - return buildResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception, request); - } - - private ErrorResponse buildResponse(HttpStatus httpStatus, Exception exception, HttpServletRequest request) { - return buildResponse(httpStatus, exception.getMessage(), exception, request); + return buildResponse(HttpStatus.INTERNAL_SERVER_ERROR, "Внутренняя ошибка сервиса", exception, request); } private ErrorResponse buildResponse(HttpStatus httpStatus, String message, Exception exception, HttpServletRequest request) { diff --git a/src/main/java/ru/practicum/eventhub/domain/repository/TagRepository.java b/src/main/java/ru/practicum/eventhub/domain/repository/TagRepository.java index 82a6646..176a68f 100644 --- a/src/main/java/ru/practicum/eventhub/domain/repository/TagRepository.java +++ b/src/main/java/ru/practicum/eventhub/domain/repository/TagRepository.java @@ -7,7 +7,9 @@ import org.springframework.data.jpa.repository.JpaRepository; import ru.practicum.eventhub.domain.model.Tag; +import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.UUID; public interface TagRepository extends JpaRepository { @@ -24,4 +26,6 @@ public interface TagRepository extends JpaRepository { boolean existsByName(String name); boolean existsByIdAndEventsId(UUID tagId, UUID eventId); + + List findAllByIdInAndEventsId(Set tagIds, UUID eventId); } diff --git a/src/main/java/ru/practicum/eventhub/domain/service/TagService.java b/src/main/java/ru/practicum/eventhub/domain/service/TagService.java index 2ea3da7..ebf1e6a 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/TagService.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/TagService.java @@ -6,13 +6,14 @@ import ru.practicum.eventhub.api.dto.response.TagDto; import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; import ru.practicum.eventhub.domain.dto.PagedResponse; +import ru.practicum.eventhub.domain.model.Tag; import java.util.UUID; public interface TagService { TagDto createTag(TagCreateDto dto); - TagDto addTagToEvent(UUID eventId, UUID tagId); + Tag addTagLocal(UUID eventId, UUID tagId); PagedResponse getTags(Pageable pageable); @@ -25,4 +26,8 @@ public interface TagService { void deleteForEvent(UUID eventId, UUID tagId); void deleteTag(UUID tagId); + + void cacheRefreshAfterAddTag(UUID eventId, UUID tagId); + + void removeTagLocal(UUID eventId, UUID tagId); } diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java index b49dcfd..e4e247a 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java @@ -7,6 +7,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; import ru.practicum.eventhub.api.dto.request.EventCreateDto; import ru.practicum.eventhub.api.dto.request.EventUpdateDto; import ru.practicum.eventhub.api.dto.response.EventDto; @@ -22,6 +23,7 @@ import ru.practicum.eventhub.domain.validation.EventValidationService; import ru.practicum.eventhub.infrastructure.redis.ManyToManyCacheIndexService; +import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -39,6 +41,7 @@ public class EventServiceImpl implements EventService { private final EventCacheService eventCacheService; private final ManyToManyCacheIndexService relationIndexService; private final TagCacheService tagCacheService; + private final TransactionTemplate transactionTemplate; @Override @Transactional @@ -72,26 +75,29 @@ public EventDto getEventById(UUID id) { } @Override - @Transactional public EventDto updateEvent(UUID id, EventUpdateDto dto) { - Event event = eventReadService.findByIdForUpdate(id); + Map.Entry> result = transactionTemplate.execute(status -> { + Event eventEntity = eventReadService.findByIdForUpdate(id); - eventValidationService.validateUpdate(dto); + eventValidationService.validateUpdate(dto); - Set oldTagIds = event.getTags().stream().map(Tag::getId).collect(Collectors.toSet()); - event = eventMapper.updateEventFromDto(dto, event); - event = eventRepository.save(event); + Set oldTagIds = eventEntity.getTags().stream().map(Tag::getId).collect(Collectors.toSet()); + eventEntity = eventMapper.updateEventFromDto(dto, eventEntity); + eventEntity = eventRepository.save(eventEntity); + + Set addedTagIds = eventEntity.getTags().stream().map(Tag::getId).collect(Collectors.toSet()); + addedTagIds.removeAll(oldTagIds); + return Map.entry(eventEntity, addedTagIds); + }); - Set addedTagIds = event.getTags().stream().map(Tag::getId).collect(Collectors.toSet()); - addedTagIds.removeAll(oldTagIds); + Event event = result.getKey(); + Set addedTagIds = result.getValue(); for (UUID tagId : addedTagIds) { relationIndexService.add(EVENT_TAG_RELATION, id, tagId); } eventCacheService.refresh(id); - for (UUID tagId : addedTagIds) { - tagCacheService.refreshByEvent(id, tagId); - } + tagCacheService.refreshByEventBatch(id, addedTagIds); log.info("Обновлено событие с id={}", id); return eventMapper.toDto(event); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagReadService.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/TagReadService.java index b895e35..f798e01 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagReadService.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/TagReadService.java @@ -9,6 +9,8 @@ import ru.practicum.eventhub.domain.model.Tag; import ru.practicum.eventhub.domain.repository.TagRepository; +import java.util.List; +import java.util.Set; import java.util.UUID; @Slf4j @@ -33,6 +35,11 @@ public Tag findByIdAndEventsId(UUID tagId, UUID eventId) { }); } + @Transactional(readOnly = true) + public List findAllByIdInAndEventsId(Set tagIds, UUID eventId) { + return tagRepository.findAllByIdInAndEventsId(tagIds, eventId); + } + @Transactional(readOnly = true) public void checkExistsByName(String name) { if (tagRepository.existsByName(name)) { diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java index 0a730f6..9531a0f 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java @@ -8,6 +8,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; import ru.practicum.eventhub.api.dto.request.TagCreateDto; import ru.practicum.eventhub.api.dto.request.TagUpdateDto; import ru.practicum.eventhub.api.dto.response.TagDto; @@ -15,6 +16,7 @@ import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; import ru.practicum.eventhub.api.exception.ConflictException; import ru.practicum.eventhub.api.mapper.TagMapper; +import ru.practicum.eventhub.application.analytics.TagAnalyticsFacade; import ru.practicum.eventhub.application.cache.EventCacheService; import ru.practicum.eventhub.application.cache.TagCacheService; import ru.practicum.eventhub.domain.dto.PagedResponse; @@ -24,7 +26,6 @@ import ru.practicum.eventhub.domain.service.TagService; import ru.practicum.eventhub.domain.util.PageValidator; import ru.practicum.eventhub.domain.validation.TagValidationService; -import ru.practicum.eventhub.infrastructure.feign.TagAnalyticsClient; import ru.practicum.eventhub.infrastructure.redis.ManyToManyCacheIndexService; import java.util.Iterator; @@ -42,10 +43,11 @@ public class TagServiceImpl implements TagService { private final TagReadService tagReadService; private final EventReadService eventReadService; private final TagValidationService tagValidationService; - private final TagAnalyticsClient tagAnalyticsClient; + private final TagAnalyticsFacade tagAnalyticsFacade; private final EventCacheService eventCacheService; private final TagCacheService tagCacheService; private final ManyToManyCacheIndexService relationIndexService; + private final TransactionTemplate transactionTemplate; @Override @Transactional @@ -60,7 +62,7 @@ public TagDto createTag(TagCreateDto dto) { @Override @Transactional - public TagDto addTagToEvent(UUID eventId, UUID tagId) { + public Tag addTagLocal(UUID eventId, UUID tagId) { Event event = eventReadService.findById(eventId); Tag tag = tagReadService.findById(tagId); @@ -72,13 +74,7 @@ public TagDto addTagToEvent(UUID eventId, UUID tagId) { event.addTag(tag); relationIndexService.add(EVENT_TAG_RELATION, eventId, tagId); - eventCacheService.refresh(eventId); - tagCacheService.refresh(tagId); - - tagAnalyticsClient.createIfAbsent(tag.getId()); - - log.info("Тег с id={} добавлен к событию с id={}", tagId, eventId); - return tagMapper.toDto(tag); + return tag; } @Override @@ -104,68 +100,95 @@ public PagedResponse getTagsByEvent(UUID eventId, Pageable pageable) { } @Override - @Transactional(readOnly = true) @Cacheable(value = "tags_by_event", key = "#eventId + ':' + #tagId") public TagWithStatsDto getTagByEvent(UUID eventId, UUID tagId) { - eventReadService.findById(eventId); - Tag tag = tagReadService.findByIdAndEventsId(tagId, eventId); + this.transactionTemplate.setReadOnly(true); + Tag tag = transactionTemplate.execute(status -> { + eventReadService.findById(eventId); + return tagReadService.findByIdAndEventsId(tagId, eventId); + }); - TagStatsDto stats = tagAnalyticsClient.getStats(tagId); + TagStatsDto stats = tagAnalyticsFacade.getTagStats(tagId); log.info("Запрошен тег с id={} для события с id={}", tagId, eventId); return tagMapper.toDtoWithStats(tag, stats); } @Override - @Transactional @CachePut(value = "tags", key = "#tagId") public TagDto updateTag(UUID tagId, TagUpdateDto dto) { - Tag tag = tagReadService.findById(tagId); + Tag tag = transactionTemplate.execute(status -> { + Tag tagEntity = tagReadService.findById(tagId); - tagValidationService.validateUpdate(dto, tag); + tagValidationService.validateUpdate(dto, tagEntity); - tag = tagMapper.updateTagFromDto(dto, tag); + tagEntity = tagMapper.updateTagFromDto(dto, tagEntity); - tag = tagRepository.save(tag); + tagEntity = tagRepository.save(tagEntity); + return tagEntity; + }); Set eventIds = relationIndexService.getLeftIds(EVENT_TAG_RELATION, tagId); - tagCacheService.refreshCompositeCacheForTag(tagId, eventIds); + tagCacheService.refreshCompositeCacheForTag(tag, eventIds); log.info("Обновлен тег с id={}", tagId); return tagMapper.toDto(tag); } @Override - @Transactional public void deleteForEvent(UUID eventId, UUID tagId) { - Event event = eventReadService.findById(eventId); - Tag tag = tagReadService.findByIdAndEventsId(tagId, eventId); + transactionTemplate.executeWithoutResult(status -> { + Event event = eventReadService.findById(eventId); + Tag tag = tagReadService.findByIdAndEventsId(tagId, eventId); + + event.removeTag(tag); + }); - event.removeTag(tag); relationIndexService.remove(EVENT_TAG_RELATION, eventId, tagId); eventCacheService.refresh(eventId); - tagCacheService.refreshByEvent(eventId, tagId); + tagCacheService.refreshByEventBatch(eventId, Set.of(tagId)); log.info("Тег с id={} отвязан от события с id={}", tagId, eventId); } @Override - @Transactional public void deleteTag(UUID tagId) { - Tag tag = tagReadService.findById(tagId); + transactionTemplate.executeWithoutResult(status -> { + Tag tag = tagReadService.findById(tagId); - Iterator iterator = tag.getEvents().iterator(); - while (iterator.hasNext()) { - Event event = iterator.next(); - iterator.remove(); - event.getTags().remove(tag); - } + Iterator iterator = tag.getEvents().iterator(); + while (iterator.hasNext()) { + Event event = iterator.next(); + iterator.remove(); + event.getTags().remove(tag); + } - tagRepository.deleteById(tagId); + tagRepository.deleteById(tagId); + }); tagCacheService.evictAll(tagId); log.info("Удален тег с id={}", tagId); } + + @Override + public void cacheRefreshAfterAddTag(UUID eventId, UUID tagId) { + eventCacheService.refresh(eventId); + tagCacheService.refresh(tagId); + log.info("Кэш обновлен для события с id={} и тега с id={}", eventId, tagId); + } + + @Override + @Transactional + public void removeTagLocal(UUID eventId, UUID tagId) { + Event event = eventReadService.findById(eventId); + Tag tag = tagReadService.findById(tagId); + + event.removeTag(tag); + + relationIndexService.remove(EVENT_TAG_RELATION, eventId, tagId); + + log.info("Тег с id={} удален из события с id={}", tagId, eventId); + } } diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/feign/TagAnalyticsClient.java b/src/main/java/ru/practicum/eventhub/infrastructure/feign/TagAnalyticsClient.java index 300d2a2..64d144d 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/feign/TagAnalyticsClient.java +++ b/src/main/java/ru/practicum/eventhub/infrastructure/feign/TagAnalyticsClient.java @@ -1,26 +1,32 @@ package ru.practicum.eventhub.infrastructure.feign; import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.*; import ru.practicum.eventhub.api.dto.response.TagStatsDto; import ru.practicum.eventhub.infrastructure.feign.config.FeignConfig; -import ru.practicum.eventhub.infrastructure.feign.fallback.TagAnalyticsFallback; +import ru.practicum.eventhub.infrastructure.feign.fallback.TagAnalyticsFallbackFactory; +import java.util.Map; +import java.util.Set; import java.util.UUID; @FeignClient( name = "tag-analytics", url = "${services.tag-analytics.url}", - fallback = TagAnalyticsFallback.class, + fallbackFactory = TagAnalyticsFallbackFactory.class, configuration = FeignConfig.class ) public interface TagAnalyticsClient { @PostMapping("/api/v1/tags/{id}/used") - void createIfAbsent(@PathVariable UUID id); + TagStatsDto createIfAbsent(@PathVariable UUID id); @GetMapping("/api/v1/tags/{id}/stats") - TagStatsDto getStats(@PathVariable UUID id); + TagStatsDto getTagStats(@PathVariable UUID id); + + @PostMapping("/api/v1/tags/stats") + Map getTagStatsBatch(@RequestBody Set tagIds); + + @DeleteMapping("/api/v1/tags/{id}/used") + void deleteTagAnalytics(@PathVariable UUID id); } diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/feign/config/FeignRequestIdInterceptor.java b/src/main/java/ru/practicum/eventhub/infrastructure/feign/config/FeignRequestIdInterceptor.java new file mode 100644 index 0000000..72bac98 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/infrastructure/feign/config/FeignRequestIdInterceptor.java @@ -0,0 +1,20 @@ +package ru.practicum.eventhub.infrastructure.feign.config; + +import feign.RequestInterceptor; +import feign.RequestTemplate; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; + +@Component +public class FeignRequestIdInterceptor implements RequestInterceptor { + private static final String REQUEST_ID_HEADER = "X-Request-Id"; + private static final String REQUEST_ID_MDC_KEY = "requestId"; + + @Override + public void apply(RequestTemplate template) { + String requestId = MDC.get(REQUEST_ID_MDC_KEY); + if (requestId != null) { + template.header(REQUEST_ID_HEADER, requestId); + } + } +} diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallback.java b/src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallback.java deleted file mode 100644 index 2cb9f5c..0000000 --- a/src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallback.java +++ /dev/null @@ -1,24 +0,0 @@ -package ru.practicum.eventhub.infrastructure.feign.fallback; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import ru.practicum.eventhub.api.dto.response.TagStatsDto; -import ru.practicum.eventhub.infrastructure.feign.TagAnalyticsClient; - -import java.util.UUID; - -@Slf4j -@Component -public class TagAnalyticsFallback implements TagAnalyticsClient { - - @Override - public void createIfAbsent(UUID id) { - log.warn("Сервис Tag Analytics недоступен. Не удалось создать статистику для тега с id={}", id); - } - - @Override - public TagStatsDto getStats(UUID id) { - log.warn("Сервис Tag Analytics недоступен. Не удалось получить статистику для тега с id={}", id); - return new TagStatsDto(null); - } -} diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallbackFactory.java b/src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallbackFactory.java new file mode 100644 index 0000000..c4843df --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallbackFactory.java @@ -0,0 +1,98 @@ +package ru.practicum.eventhub.infrastructure.feign.fallback; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.stereotype.Component; +import ru.practicum.eventhub.api.dto.response.TagStatsDto; +import ru.practicum.eventhub.infrastructure.feign.TagAnalyticsClient; +import ru.practicum.eventhub.infrastructure.feign.exception.TagAnalyticsClientException; +import ru.practicum.eventhub.infrastructure.feign.exception.TagNotFoundException; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +@Slf4j +@Component +public class TagAnalyticsFallbackFactory implements FallbackFactory { + + @Override + public TagAnalyticsClient create(Throwable cause) { + return new TagAnalyticsClientFallback(cause); + } + + @Slf4j + private static class TagAnalyticsClientFallback implements TagAnalyticsClient { + private final Throwable cause; + + public TagAnalyticsClientFallback(Throwable cause) { + this.cause = cause; + } + + @Override + public TagStatsDto createIfAbsent(UUID id) { + if (cause instanceof TagNotFoundException) { + log.debug("Тег с id={} не найден в Tag Analytics (404). Пропускаем создание статистики.", id); + throw (TagNotFoundException) cause; + } + + if (cause instanceof TagAnalyticsClientException) { + log.warn("Ошибка клиента при создании статистики для тега id={}: {}", id, cause.getMessage()); + throw (TagAnalyticsClientException) cause; + } + + log.warn("Сервис Tag Analytics недоступен. Не удалось создать статистику для тега с id={}. " + + "Причина: {}", id, cause.getClass().getSimpleName(), cause); + return new TagStatsDto(null); + } + + @Override + public TagStatsDto getTagStats(UUID id) { + if (cause instanceof TagNotFoundException) { + log.debug("Тег с id={} не найден в Tag Analytics (404). Возвращаем пустую статистику.", id); + return new TagStatsDto(null); + } + + if (cause instanceof TagAnalyticsClientException) { + log.warn("Ошибка клиента при получении статистики для тега id={}: {}", id, cause.getMessage()); + throw (TagAnalyticsClientException) cause; + } + + log.warn("Сервис Tag Analytics недоступен. Не удалось получить статистику для тега с id={}. " + + "Причина: {}", id, cause.getClass().getSimpleName(), cause); + return new TagStatsDto(null); + } + + @Override + public Map getTagStatsBatch(Set tagIds) { + if (cause instanceof TagNotFoundException) { + log.debug("Некоторые теги с id={} не найдены в Tag Analytics (404).", tagIds); + throw (TagNotFoundException) cause; + } + + if (cause instanceof TagAnalyticsClientException) { + log.warn("Ошибка клиента при получении статистики для тегов id={}: {}", tagIds, cause.getMessage()); + throw (TagAnalyticsClientException) cause; + } + + log.warn("Сервис Tag Analytics недоступен. Не удалось получить статистику для тегов с id={}. " + + "Причина: {}", tagIds, cause.getClass().getSimpleName(), cause); + return Map.of(); + } + + @Override + public void deleteTagAnalytics(UUID id) { + if (cause instanceof TagNotFoundException) { + log.debug("Тег с id={} не найден в Tag Analytics (404). Возвращаем пустую статистику.", id); + } + + if (cause instanceof TagAnalyticsClientException) { + log.warn("Ошибка клиента при получении статистики для тега id={}: {}", id, cause.getMessage()); + throw (TagAnalyticsClientException) cause; + } + + log.warn("Сервис Tag Analytics недоступен. Не удалось удалить статистику для тега с id={}. " + + "Причина: {}", id, cause.getClass().getSimpleName(), cause); + } + } +} diff --git a/src/main/java/ru/practicum/eventhub/web/filter/RequestIdFilter.java b/src/main/java/ru/practicum/eventhub/web/filter/RequestIdFilter.java index 2b89a82..e8b6814 100644 --- a/src/main/java/ru/practicum/eventhub/web/filter/RequestIdFilter.java +++ b/src/main/java/ru/practicum/eventhub/web/filter/RequestIdFilter.java @@ -4,21 +4,24 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; import org.slf4j.MDC; +import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; import java.util.UUID; +@Slf4j @Component public class RequestIdFilter extends OncePerRequestFilter { private static final String REQUEST_ID_HEADER = "X-Request-Id"; + private static final String REQUEST_ID_MDC_KEY = "requestId"; @Override protected void doFilterInternal( - HttpServletRequest request, + @NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain ) throws ServletException, IOException { @@ -27,9 +30,12 @@ protected void doFilterInternal( if (requestId == null || requestId.isBlank()) { requestId = UUID.randomUUID().toString(); + log.debug("Generated new Request ID: {}", requestId); + } else { + log.debug("Received Request ID: {}", requestId); } - MDC.put("requestId", requestId); + MDC.put(REQUEST_ID_MDC_KEY, requestId); response.setHeader(REQUEST_ID_HEADER, requestId); try { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4fd954f..ece9959 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -3,17 +3,17 @@ spring: name: event-hub datasource: - driver-class-name: org.postgresql.Driver url: jdbc:postgresql://localhost:5432/postgres username: ${DB_USERNAME:postgres} password: ${DB_PASSWORD:123456} + driver-class-name: org.postgresql.Driver jpa: hibernate: ddl-auto: validate + show-sql: true properties: hibernate: - show_sql: true format_sql: true default_schema: event_hub @@ -38,7 +38,7 @@ spring: resilience4j: circuitbreaker: instances: - tagAnalytics: + tag-analytics: slidingWindowSize: 10 failureRateThreshold: 50 waitDurationInOpenState: 10s @@ -57,4 +57,4 @@ resilience4j: services: tag-analytics: - url: http://localhost:8081 \ No newline at end of file + url: http://localhost:8081 diff --git a/src/main/resources/openapi/openapi.yml b/src/main/resources/openapi/openapi.yml index 330b07d..a9841f6 100644 --- a/src/main/resources/openapi/openapi.yml +++ b/src/main/resources/openapi/openapi.yml @@ -1090,7 +1090,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TagDto' + $ref: '#/components/schemas/TagWithStatsDto' '400': description: Некорректные данные запроса content: From 879fb1413bb41f1f7a61cc5d93be75f4305884c4 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 12 Mar 2026 19:20:19 +0300 Subject: [PATCH 7/8] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D1=91?= =?UTF-8?q?=D0=BD=20=D0=BA=D1=8D=D1=88=20=D1=81=D0=BE=D0=B1=D1=8B=D1=82?= =?UTF-8?q?=D0=B8=D0=B9=20=D0=B8=20=D0=B8=D0=BD=D1=82=D0=B5=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D1=81=20Tag=20Analytics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Удалён ненужный CacheManager из EventCacheService - Добавлены аннотации @CachePut и @CacheEvict в EventServiceImpl - Переименован метод createIfAbsent в incrementUsage в TagAnalyticsClient - Обновлён метод sendAnalytics на incrementUsage в TagAnalyticsFacade - Изменён возврат TagStatsDto в TagAnalyticsFallbackFactory --- .../eventhub/api/dto/response/TagStatsDto.java | 4 +++- .../application/analytics/TagAnalyticsFacade.java | 4 ++-- .../application/cache/EventCacheService.java | 12 ------------ .../saga/TagAnalyticsSagaOrchestrator.java | 8 ++++---- .../domain/service/impl/EventServiceImpl.java | 9 ++++----- .../infrastructure/feign/TagAnalyticsClient.java | 2 +- .../feign/fallback/TagAnalyticsFallbackFactory.java | 8 ++++---- 7 files changed, 18 insertions(+), 29 deletions(-) diff --git a/src/main/java/ru/practicum/eventhub/api/dto/response/TagStatsDto.java b/src/main/java/ru/practicum/eventhub/api/dto/response/TagStatsDto.java index e3ed316..a49c50d 100644 --- a/src/main/java/ru/practicum/eventhub/api/dto/response/TagStatsDto.java +++ b/src/main/java/ru/practicum/eventhub/api/dto/response/TagStatsDto.java @@ -3,6 +3,8 @@ import java.time.OffsetDateTime; public record TagStatsDto( - OffsetDateTime createdAt + long usageCount, + OffsetDateTime createdAt, + OffsetDateTime updatedAt ) { } diff --git a/src/main/java/ru/practicum/eventhub/application/analytics/TagAnalyticsFacade.java b/src/main/java/ru/practicum/eventhub/application/analytics/TagAnalyticsFacade.java index 8e1caec..016bded 100644 --- a/src/main/java/ru/practicum/eventhub/application/analytics/TagAnalyticsFacade.java +++ b/src/main/java/ru/practicum/eventhub/application/analytics/TagAnalyticsFacade.java @@ -16,8 +16,8 @@ public class TagAnalyticsFacade { private final TagAnalyticsClient tagAnalyticsClient; @Retry(name = "tag-analytics") - public TagStatsDto sendAnalytics(UUID tagId) { - return tagAnalyticsClient.createIfAbsent(tagId); + public TagStatsDto incrementUsage(UUID tagId) { + return tagAnalyticsClient.incrementUsage(tagId); } @Retry(name = "tag-analytics") diff --git a/src/main/java/ru/practicum/eventhub/application/cache/EventCacheService.java b/src/main/java/ru/practicum/eventhub/application/cache/EventCacheService.java index 4de168d..f385577 100644 --- a/src/main/java/ru/practicum/eventhub/application/cache/EventCacheService.java +++ b/src/main/java/ru/practicum/eventhub/application/cache/EventCacheService.java @@ -2,8 +2,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachePut; import org.springframework.stereotype.Component; import ru.practicum.eventhub.api.dto.response.EventDto; @@ -19,7 +17,6 @@ public class EventCacheService { private final EventReadService eventReadService; private final EventMapper eventMapper; - private final CacheManager cacheManager; @CachePut(value = "events", key = "#eventId") public EventDto refresh(UUID eventId) { @@ -27,13 +24,4 @@ public EventDto refresh(UUID eventId) { log.info("Обновлен кэш события с id={}", eventId); return eventMapper.toDto(event); } - - public void evict(UUID eventId) { - Cache cache = cacheManager.getCache("events"); - if (cache != null) { - cache.evict(eventId); - log.info("Удален кэш события с id={}", eventId); - } - } } - diff --git a/src/main/java/ru/practicum/eventhub/application/saga/TagAnalyticsSagaOrchestrator.java b/src/main/java/ru/practicum/eventhub/application/saga/TagAnalyticsSagaOrchestrator.java index fbf1d1a..12f926f 100644 --- a/src/main/java/ru/practicum/eventhub/application/saga/TagAnalyticsSagaOrchestrator.java +++ b/src/main/java/ru/practicum/eventhub/application/saga/TagAnalyticsSagaOrchestrator.java @@ -22,14 +22,14 @@ public class TagAnalyticsSagaOrchestrator { public TagWithStatsDto addTagToEvent(UUID eventId, UUID tagId) { boolean tagAdded = false; - boolean analyticsCreated = false; + boolean incrementUsage = false; try { Tag tag = tagService.addTagLocal(eventId, tagId); tagAdded = true; - TagStatsDto tagStatsDto = analyticsFacade.sendAnalytics(tagId); - analyticsCreated = true; + TagStatsDto tagStatsDto = analyticsFacade.incrementUsage(tagId); + incrementUsage = true; tagService.cacheRefreshAfterAddTag(eventId, tagId); @@ -39,7 +39,7 @@ public TagWithStatsDto addTagToEvent(UUID eventId, UUID tagId) { } catch (Exception ex) { log.error("Ошибка при добавлении тега с id={} к событию с id={}: {}", tagId, eventId, ex.getMessage()); - if (analyticsCreated) { + if (incrementUsage) { analyticsFacade.delete(tagId); } diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java b/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java index e4e247a..89c6e99 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java @@ -2,6 +2,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -12,7 +14,6 @@ import ru.practicum.eventhub.api.dto.request.EventUpdateDto; import ru.practicum.eventhub.api.dto.response.EventDto; import ru.practicum.eventhub.api.mapper.EventMapper; -import ru.practicum.eventhub.application.cache.EventCacheService; import ru.practicum.eventhub.application.cache.TagCacheService; import ru.practicum.eventhub.domain.dto.PagedResponse; import ru.practicum.eventhub.domain.model.Event; @@ -38,7 +39,6 @@ public class EventServiceImpl implements EventService { private final EventReadService eventReadService; private final EventMapper eventMapper; private final EventValidationService eventValidationService; - private final EventCacheService eventCacheService; private final ManyToManyCacheIndexService relationIndexService; private final TagCacheService tagCacheService; private final TransactionTemplate transactionTemplate; @@ -75,6 +75,7 @@ public EventDto getEventById(UUID id) { } @Override + @CachePut(value = "events", key = "#id") public EventDto updateEvent(UUID id, EventUpdateDto dto) { Map.Entry> result = transactionTemplate.execute(status -> { Event eventEntity = eventReadService.findByIdForUpdate(id); @@ -96,7 +97,6 @@ public EventDto updateEvent(UUID id, EventUpdateDto dto) { for (UUID tagId : addedTagIds) { relationIndexService.add(EVENT_TAG_RELATION, id, tagId); } - eventCacheService.refresh(id); tagCacheService.refreshByEventBatch(id, addedTagIds); log.info("Обновлено событие с id={}", id); @@ -105,14 +105,13 @@ public EventDto updateEvent(UUID id, EventUpdateDto dto) { @Override @Transactional + @CacheEvict(value = "events", key = "#id") public void deleteEvent(UUID id) { eventReadService.findById(id); eventRepository.deleteById(id); relationIndexService.deleteLeft(EVENT_TAG_RELATION, id); - eventCacheService.evict(id); - log.info("Удалено событие с id={}", id); } } diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/feign/TagAnalyticsClient.java b/src/main/java/ru/practicum/eventhub/infrastructure/feign/TagAnalyticsClient.java index 64d144d..ff0f903 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/feign/TagAnalyticsClient.java +++ b/src/main/java/ru/practicum/eventhub/infrastructure/feign/TagAnalyticsClient.java @@ -19,7 +19,7 @@ public interface TagAnalyticsClient { @PostMapping("/api/v1/tags/{id}/used") - TagStatsDto createIfAbsent(@PathVariable UUID id); + TagStatsDto incrementUsage(@PathVariable UUID id); @GetMapping("/api/v1/tags/{id}/stats") TagStatsDto getTagStats(@PathVariable UUID id); diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallbackFactory.java b/src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallbackFactory.java index c4843df..fd66968 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallbackFactory.java +++ b/src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallbackFactory.java @@ -30,7 +30,7 @@ public TagAnalyticsClientFallback(Throwable cause) { } @Override - public TagStatsDto createIfAbsent(UUID id) { + public TagStatsDto incrementUsage(UUID id) { if (cause instanceof TagNotFoundException) { log.debug("Тег с id={} не найден в Tag Analytics (404). Пропускаем создание статистики.", id); throw (TagNotFoundException) cause; @@ -43,14 +43,14 @@ public TagStatsDto createIfAbsent(UUID id) { log.warn("Сервис Tag Analytics недоступен. Не удалось создать статистику для тега с id={}. " + "Причина: {}", id, cause.getClass().getSimpleName(), cause); - return new TagStatsDto(null); + return new TagStatsDto(0, null, null); } @Override public TagStatsDto getTagStats(UUID id) { if (cause instanceof TagNotFoundException) { log.debug("Тег с id={} не найден в Tag Analytics (404). Возвращаем пустую статистику.", id); - return new TagStatsDto(null); + return new TagStatsDto(0, null, null); } if (cause instanceof TagAnalyticsClientException) { @@ -60,7 +60,7 @@ public TagStatsDto getTagStats(UUID id) { log.warn("Сервис Tag Analytics недоступен. Не удалось получить статистику для тега с id={}. " + "Причина: {}", id, cause.getClass().getSimpleName(), cause); - return new TagStatsDto(null); + return new TagStatsDto(0, null, null); } @Override From d225c8c8486227403b42cf015d40d59811ba6d19 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 2 Apr 2026 16:01:07 +0300 Subject: [PATCH 8/8] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=B5=D0=BD=D1=81=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=BE=D0=BD=D0=BD=D1=8B=D1=85=20=D0=B4=D0=B5=D0=B9=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Создан класс CompensationAction для хранения информации о компенсационных действиях - Реализован CompensationService для обработки и сохранения компенсационных действий - Добавлен CompensationScheduler для периодической обработки компенсаций - Обновлены пакеты и импорты в нескольких классах - Исправлены пути к классам и методам в соответствии с новой структурой --- gradle/wrapper/gradle-wrapper.properties | 2 +- .../eventhub/EventHubApplication.java | 2 + .../eventhub/api/mapper/CategoryMapper.java | 4 +- .../eventhub/api/mapper/EventMapper.java | 4 +- .../eventhub/api/mapper/ProjectMapper.java | 2 +- .../eventhub/api/mapper/TagMapper.java | 2 +- .../eventhub/api/mapper/UserMapper.java | 4 +- .../api/mapper/UserMetadataMapper.java | 2 +- .../cache/CategoryCacheInvalidator.java | 35 ----- .../cache/CategoryCacheService.java | 27 ---- .../application/cache/EventCacheService.java | 27 ---- .../openapi/CategoryPageMapper.java | 31 ---- .../application/openapi/EventPageMapper.java | 31 ---- .../openapi/MetadataPageMapper.java | 31 ---- .../openapi/ProjectPageMapper.java | 33 ----- .../application/openapi/TagPageMapper.java | 38 ----- .../application/openapi/UserPageMapper.java | 31 ---- .../saga/TagAnalyticsSagaOrchestrator.java | 53 ------- .../feign/TagAnalyticsClient.java | 12 +- .../feign}/TagAnalyticsFacade.java | 9 +- .../feign/config/FeignConfig.java | 4 +- .../config/FeignRequestIdInterceptor.java | 2 +- .../decoder/TagAnalyticsErrorDecoder.java | 6 +- .../TagAnalyticsClientException.java | 2 +- .../feign/exception/TagNotFoundException.java | 2 +- .../fallback/TagAnalyticsFallbackFactory.java | 15 +- .../redis/ManyToManyCacheIndexService.java | 2 +- .../redis/ProjectCacheIndexService.java | 3 +- .../config/redis/RedisCacheService.java | 23 +++ .../redis/config/RedisCacheConfig.java | 2 +- .../controller/CategoryProjectController.java | 12 +- .../controller/EventTagController.java | 18 +-- .../eventhub/controller/UserController.java | 12 +- .../advice/GlobalControllerAdvice.java | 4 +- .../eventhub/domain/dto/PagedResponse.java | 19 --- .../domain/event/CategoryDeletedEvent.java | 6 - .../practicum/eventhub/model/ActionType.java | 5 + .../eventhub/{domain => }/model/Category.java | 2 +- .../eventhub/model/CompensationAction.java | 40 ++++++ .../eventhub/{domain => }/model/Event.java | 2 +- .../eventhub/{domain => }/model/Project.java | 2 +- .../eventhub/{domain => }/model/Tag.java | 2 +- .../eventhub/{domain => }/model/User.java | 2 +- .../{domain => }/model/UserMetadata.java | 2 +- .../repository/CategoryRepository.java | 4 +- .../CompensationActionRepository.java | 9 ++ .../repository/EventRepository.java | 4 +- .../repository/ProjectRepository.java | 4 +- .../repository/TagRepository.java | 4 +- .../repository/UserMetadataRepository.java | 4 +- .../repository/UserRepository.java | 4 +- .../{domain => }/service/CategoryService.java | 6 +- .../{domain => }/service/EventService.java | 6 +- .../{domain => }/service/ProjectService.java | 6 +- .../{domain => }/service/TagService.java | 15 +- .../service/UserMetadataService.java | 6 +- .../{domain => }/service/UserService.java | 6 +- .../cache/CategoryProjectCacheService.java | 32 +++++ .../cache/EventTagCacheService.java} | 46 +++--- .../service/impl/CategoryReadService.java | 8 +- .../service/impl/CategoryServiceImpl.java | 35 +++-- .../service/impl/CompensationScheduler.java | 45 ++++++ .../service/impl/CompensationService.java | 25 ++++ .../service/impl/EventReadService.java | 18 ++- .../service/impl/EventServiceImpl.java | 36 +++-- .../service/impl/MetadataReadService.java | 6 +- .../service/impl/ProjectReadService.java | 6 +- .../service/impl/ProjectServiceImpl.java | 36 +++-- .../service/impl/TagReadService.java | 18 ++- .../service/impl/TagServiceImpl.java | 132 ++++++++++++------ .../service/impl/UserMetadataServiceImpl.java | 26 ++-- .../service/impl/UserReadService.java | 6 +- .../service/impl/UserServiceImpl.java | 28 ++-- .../{domain => }/util/PageValidator.java | 2 +- .../validation/CategoryValidationService.java | 8 +- .../validation/EventValidationService.java | 4 +- .../validation/ProjectValidationService.java | 6 +- .../validation/TagValidationService.java | 6 +- .../validation/UserValidationService.java | 8 +- src/main/resources/application.yml | 4 +- .../db/migration/V2__add_cascade_update.sql | 10 ++ src/main/resources/openapi/openapi.yml | 4 +- 82 files changed, 557 insertions(+), 645 deletions(-) delete mode 100644 src/main/java/ru/practicum/eventhub/application/cache/CategoryCacheInvalidator.java delete mode 100644 src/main/java/ru/practicum/eventhub/application/cache/CategoryCacheService.java delete mode 100644 src/main/java/ru/practicum/eventhub/application/cache/EventCacheService.java delete mode 100644 src/main/java/ru/practicum/eventhub/application/openapi/CategoryPageMapper.java delete mode 100644 src/main/java/ru/practicum/eventhub/application/openapi/EventPageMapper.java delete mode 100644 src/main/java/ru/practicum/eventhub/application/openapi/MetadataPageMapper.java delete mode 100644 src/main/java/ru/practicum/eventhub/application/openapi/ProjectPageMapper.java delete mode 100644 src/main/java/ru/practicum/eventhub/application/openapi/TagPageMapper.java delete mode 100644 src/main/java/ru/practicum/eventhub/application/openapi/UserPageMapper.java delete mode 100644 src/main/java/ru/practicum/eventhub/application/saga/TagAnalyticsSagaOrchestrator.java rename src/main/java/ru/practicum/eventhub/{infrastructure => config}/feign/TagAnalyticsClient.java (67%) rename src/main/java/ru/practicum/eventhub/{application/analytics => config/feign}/TagAnalyticsFacade.java (76%) rename src/main/java/ru/practicum/eventhub/{infrastructure => config}/feign/config/FeignConfig.java (67%) rename src/main/java/ru/practicum/eventhub/{infrastructure => config}/feign/config/FeignRequestIdInterceptor.java (90%) rename src/main/java/ru/practicum/eventhub/{infrastructure => config}/feign/decoder/TagAnalyticsErrorDecoder.java (77%) rename src/main/java/ru/practicum/eventhub/{infrastructure => config}/feign/exception/TagAnalyticsClientException.java (71%) rename src/main/java/ru/practicum/eventhub/{infrastructure => config}/feign/exception/TagNotFoundException.java (69%) rename src/main/java/ru/practicum/eventhub/{infrastructure => config}/feign/fallback/TagAnalyticsFallbackFactory.java (86%) rename src/main/java/ru/practicum/eventhub/{infrastructure => config}/redis/ManyToManyCacheIndexService.java (97%) rename src/main/java/ru/practicum/eventhub/{infrastructure => config}/redis/ProjectCacheIndexService.java (95%) create mode 100644 src/main/java/ru/practicum/eventhub/config/redis/RedisCacheService.java rename src/main/java/ru/practicum/eventhub/{infrastructure => config}/redis/config/RedisCacheConfig.java (95%) delete mode 100644 src/main/java/ru/practicum/eventhub/domain/dto/PagedResponse.java delete mode 100644 src/main/java/ru/practicum/eventhub/domain/event/CategoryDeletedEvent.java create mode 100644 src/main/java/ru/practicum/eventhub/model/ActionType.java rename src/main/java/ru/practicum/eventhub/{domain => }/model/Category.java (96%) create mode 100644 src/main/java/ru/practicum/eventhub/model/CompensationAction.java rename src/main/java/ru/practicum/eventhub/{domain => }/model/Event.java (97%) rename src/main/java/ru/practicum/eventhub/{domain => }/model/Project.java (95%) rename src/main/java/ru/practicum/eventhub/{domain => }/model/Tag.java (95%) rename src/main/java/ru/practicum/eventhub/{domain => }/model/User.java (96%) rename src/main/java/ru/practicum/eventhub/{domain => }/model/UserMetadata.java (96%) rename src/main/java/ru/practicum/eventhub/{domain => }/repository/CategoryRepository.java (89%) create mode 100644 src/main/java/ru/practicum/eventhub/repository/CompensationActionRepository.java rename src/main/java/ru/practicum/eventhub/{domain => }/repository/EventRepository.java (88%) rename src/main/java/ru/practicum/eventhub/{domain => }/repository/ProjectRepository.java (86%) rename src/main/java/ru/practicum/eventhub/{domain => }/repository/TagRepository.java (89%) rename src/main/java/ru/practicum/eventhub/{domain => }/repository/UserMetadataRepository.java (85%) rename src/main/java/ru/practicum/eventhub/{domain => }/repository/UserRepository.java (85%) rename src/main/java/ru/practicum/eventhub/{domain => }/service/CategoryService.java (75%) rename src/main/java/ru/practicum/eventhub/{domain => }/service/EventService.java (74%) rename src/main/java/ru/practicum/eventhub/{domain => }/service/ProjectService.java (70%) rename src/main/java/ru/practicum/eventhub/{domain => }/service/TagService.java (63%) rename src/main/java/ru/practicum/eventhub/{domain => }/service/UserMetadataService.java (56%) rename src/main/java/ru/practicum/eventhub/{domain => }/service/UserService.java (75%) create mode 100644 src/main/java/ru/practicum/eventhub/service/cache/CategoryProjectCacheService.java rename src/main/java/ru/practicum/eventhub/{application/cache/TagCacheService.java => service/cache/EventTagCacheService.java} (67%) rename src/main/java/ru/practicum/eventhub/{domain => }/service/impl/CategoryReadService.java (86%) rename src/main/java/ru/practicum/eventhub/{domain => }/service/impl/CategoryServiceImpl.java (71%) create mode 100644 src/main/java/ru/practicum/eventhub/service/impl/CompensationScheduler.java create mode 100644 src/main/java/ru/practicum/eventhub/service/impl/CompensationService.java rename src/main/java/ru/practicum/eventhub/{domain => }/service/impl/EventReadService.java (63%) rename src/main/java/ru/practicum/eventhub/{domain => }/service/impl/EventServiceImpl.java (77%) rename src/main/java/ru/practicum/eventhub/{domain => }/service/impl/MetadataReadService.java (87%) rename src/main/java/ru/practicum/eventhub/{domain => }/service/impl/ProjectReadService.java (88%) rename src/main/java/ru/practicum/eventhub/{domain => }/service/impl/ProjectServiceImpl.java (74%) rename src/main/java/ru/practicum/eventhub/{domain => }/service/impl/TagReadService.java (76%) rename src/main/java/ru/practicum/eventhub/{domain => }/service/impl/TagServiceImpl.java (65%) rename src/main/java/ru/practicum/eventhub/{domain => }/service/impl/UserMetadataServiceImpl.java (64%) rename src/main/java/ru/practicum/eventhub/{domain => }/service/impl/UserReadService.java (92%) rename src/main/java/ru/practicum/eventhub/{domain => }/service/impl/UserServiceImpl.java (76%) rename src/main/java/ru/practicum/eventhub/{domain => }/util/PageValidator.java (94%) rename src/main/java/ru/practicum/eventhub/{domain => }/validation/CategoryValidationService.java (88%) rename src/main/java/ru/practicum/eventhub/{domain => }/validation/EventValidationService.java (90%) rename src/main/java/ru/practicum/eventhub/{domain => }/validation/ProjectValidationService.java (87%) rename src/main/java/ru/practicum/eventhub/{domain => }/validation/TagValidationService.java (89%) rename src/main/java/ru/practicum/eventhub/{domain => }/validation/UserValidationService.java (92%) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d4081da..aaaabb3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/java/ru/practicum/eventhub/EventHubApplication.java b/src/main/java/ru/practicum/eventhub/EventHubApplication.java index 1e71110..dc73a4e 100644 --- a/src/main/java/ru/practicum/eventhub/EventHubApplication.java +++ b/src/main/java/ru/practicum/eventhub/EventHubApplication.java @@ -4,10 +4,12 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableCaching @EnableFeignClients +@EnableScheduling public class EventHubApplication { public static void main(String[] args) { diff --git a/src/main/java/ru/practicum/eventhub/api/mapper/CategoryMapper.java b/src/main/java/ru/practicum/eventhub/api/mapper/CategoryMapper.java index 0b94470..61e2fc2 100644 --- a/src/main/java/ru/practicum/eventhub/api/mapper/CategoryMapper.java +++ b/src/main/java/ru/practicum/eventhub/api/mapper/CategoryMapper.java @@ -5,8 +5,8 @@ import ru.practicum.eventhub.api.dto.request.CategoryUpdateDto; import ru.practicum.eventhub.api.dto.request.ProjectCreateDto; import ru.practicum.eventhub.api.dto.response.CategoryDto; -import ru.practicum.eventhub.domain.model.Category; -import ru.practicum.eventhub.domain.model.Project; +import ru.practicum.eventhub.model.Category; +import ru.practicum.eventhub.model.Project; import java.util.Set; diff --git a/src/main/java/ru/practicum/eventhub/api/mapper/EventMapper.java b/src/main/java/ru/practicum/eventhub/api/mapper/EventMapper.java index 2bc52f6..3eaba0d 100644 --- a/src/main/java/ru/practicum/eventhub/api/mapper/EventMapper.java +++ b/src/main/java/ru/practicum/eventhub/api/mapper/EventMapper.java @@ -5,8 +5,8 @@ import ru.practicum.eventhub.api.dto.request.EventUpdateDto; import ru.practicum.eventhub.api.dto.request.TagCreateDto; import ru.practicum.eventhub.api.dto.response.EventDto; -import ru.practicum.eventhub.domain.model.Event; -import ru.practicum.eventhub.domain.model.Tag; +import ru.practicum.eventhub.model.Event; +import ru.practicum.eventhub.model.Tag; import java.util.Set; diff --git a/src/main/java/ru/practicum/eventhub/api/mapper/ProjectMapper.java b/src/main/java/ru/practicum/eventhub/api/mapper/ProjectMapper.java index 0713ced..a3b587e 100644 --- a/src/main/java/ru/practicum/eventhub/api/mapper/ProjectMapper.java +++ b/src/main/java/ru/practicum/eventhub/api/mapper/ProjectMapper.java @@ -6,7 +6,7 @@ import org.mapstruct.MappingTarget; import ru.practicum.eventhub.api.dto.request.ProjectUpdateDto; import ru.practicum.eventhub.api.dto.response.ProjectDto; -import ru.practicum.eventhub.domain.model.Project; +import ru.practicum.eventhub.model.Project; import static org.mapstruct.MappingConstants.ComponentModel.SPRING; import static org.mapstruct.NullValuePropertyMappingStrategy.IGNORE; diff --git a/src/main/java/ru/practicum/eventhub/api/mapper/TagMapper.java b/src/main/java/ru/practicum/eventhub/api/mapper/TagMapper.java index e908a8d..c1fe237 100644 --- a/src/main/java/ru/practicum/eventhub/api/mapper/TagMapper.java +++ b/src/main/java/ru/practicum/eventhub/api/mapper/TagMapper.java @@ -9,7 +9,7 @@ import ru.practicum.eventhub.api.dto.response.TagDto; import ru.practicum.eventhub.api.dto.response.TagStatsDto; import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; -import ru.practicum.eventhub.domain.model.Tag; +import ru.practicum.eventhub.model.Tag; import static org.mapstruct.MappingConstants.ComponentModel.SPRING; import static org.mapstruct.NullValuePropertyMappingStrategy.IGNORE; diff --git a/src/main/java/ru/practicum/eventhub/api/mapper/UserMapper.java b/src/main/java/ru/practicum/eventhub/api/mapper/UserMapper.java index 51de3de..8a9e471 100644 --- a/src/main/java/ru/practicum/eventhub/api/mapper/UserMapper.java +++ b/src/main/java/ru/practicum/eventhub/api/mapper/UserMapper.java @@ -6,8 +6,8 @@ import ru.practicum.eventhub.api.dto.request.UserMetadataUpdateDto; import ru.practicum.eventhub.api.dto.request.UserUpdateDto; import ru.practicum.eventhub.api.dto.response.UserDto; -import ru.practicum.eventhub.domain.model.User; -import ru.practicum.eventhub.domain.model.UserMetadata; +import ru.practicum.eventhub.model.User; +import ru.practicum.eventhub.model.UserMetadata; import static org.mapstruct.MappingConstants.ComponentModel.SPRING; import static org.mapstruct.NullValuePropertyMappingStrategy.IGNORE; diff --git a/src/main/java/ru/practicum/eventhub/api/mapper/UserMetadataMapper.java b/src/main/java/ru/practicum/eventhub/api/mapper/UserMetadataMapper.java index b618924..251954c 100644 --- a/src/main/java/ru/practicum/eventhub/api/mapper/UserMetadataMapper.java +++ b/src/main/java/ru/practicum/eventhub/api/mapper/UserMetadataMapper.java @@ -2,7 +2,7 @@ import org.mapstruct.Mapper; import ru.practicum.eventhub.api.dto.response.UserMetadataDto; -import ru.practicum.eventhub.domain.model.UserMetadata; +import ru.practicum.eventhub.model.UserMetadata; import static org.mapstruct.MappingConstants.ComponentModel.SPRING; diff --git a/src/main/java/ru/practicum/eventhub/application/cache/CategoryCacheInvalidator.java b/src/main/java/ru/practicum/eventhub/application/cache/CategoryCacheInvalidator.java deleted file mode 100644 index 2020f16..0000000 --- a/src/main/java/ru/practicum/eventhub/application/cache/CategoryCacheInvalidator.java +++ /dev/null @@ -1,35 +0,0 @@ -package ru.practicum.eventhub.application.cache; - -import lombok.RequiredArgsConstructor; -import org.springframework.cache.CacheManager; -import org.springframework.stereotype.Component; -import org.springframework.transaction.event.TransactionPhase; -import org.springframework.transaction.event.TransactionalEventListener; -import ru.practicum.eventhub.domain.event.CategoryDeletedEvent; -import ru.practicum.eventhub.infrastructure.redis.ProjectCacheIndexService; - -import java.util.Optional; -import java.util.Set; -import java.util.UUID; - -@Component -@RequiredArgsConstructor -public class CategoryCacheInvalidator { - private final CacheManager cacheManager; - private final ProjectCacheIndexService cacheIndexService; - - @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) - public void handle(CategoryDeletedEvent event) { - UUID categoryId = event.categoryId(); - - Optional.ofNullable(cacheManager.getCache("projects")) - .ifPresent(projectsCache -> { - Set projectIds = cacheIndexService.getProjects(categoryId); - if (projectIds != null) { - projectIds.forEach(pid -> projectsCache.evict(categoryId + ":" + pid)); - } - }); - - cacheIndexService.deleteCategoryIndex(categoryId); - } -} diff --git a/src/main/java/ru/practicum/eventhub/application/cache/CategoryCacheService.java b/src/main/java/ru/practicum/eventhub/application/cache/CategoryCacheService.java deleted file mode 100644 index 6b8d502..0000000 --- a/src/main/java/ru/practicum/eventhub/application/cache/CategoryCacheService.java +++ /dev/null @@ -1,27 +0,0 @@ -package ru.practicum.eventhub.application.cache; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.annotation.CachePut; -import org.springframework.stereotype.Component; -import ru.practicum.eventhub.api.dto.response.CategoryDto; -import ru.practicum.eventhub.api.mapper.CategoryMapper; -import ru.practicum.eventhub.domain.model.Category; -import ru.practicum.eventhub.domain.service.impl.CategoryReadService; - -import java.util.UUID; - -@Slf4j -@Component -@RequiredArgsConstructor -public class CategoryCacheService { - private final CategoryReadService categoryReadService; - private final CategoryMapper categoryMapper; - - @CachePut(value = "categories", key = "#categoryId") - public CategoryDto refresh(UUID categoryId) { - Category category = categoryReadService.findById(categoryId); - log.info("Обновлен кеш категории c id={}", categoryId); - return categoryMapper.toDto(category); - } -} diff --git a/src/main/java/ru/practicum/eventhub/application/cache/EventCacheService.java b/src/main/java/ru/practicum/eventhub/application/cache/EventCacheService.java deleted file mode 100644 index f385577..0000000 --- a/src/main/java/ru/practicum/eventhub/application/cache/EventCacheService.java +++ /dev/null @@ -1,27 +0,0 @@ -package ru.practicum.eventhub.application.cache; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.annotation.CachePut; -import org.springframework.stereotype.Component; -import ru.practicum.eventhub.api.dto.response.EventDto; -import ru.practicum.eventhub.api.mapper.EventMapper; -import ru.practicum.eventhub.domain.model.Event; -import ru.practicum.eventhub.domain.service.impl.EventReadService; - -import java.util.UUID; - -@Slf4j -@Component -@RequiredArgsConstructor -public class EventCacheService { - private final EventReadService eventReadService; - private final EventMapper eventMapper; - - @CachePut(value = "events", key = "#eventId") - public EventDto refresh(UUID eventId) { - Event event = eventReadService.findById(eventId); - log.info("Обновлен кэш события с id={}", eventId); - return eventMapper.toDto(event); - } -} diff --git a/src/main/java/ru/practicum/eventhub/application/openapi/CategoryPageMapper.java b/src/main/java/ru/practicum/eventhub/application/openapi/CategoryPageMapper.java deleted file mode 100644 index 26ca8d5..0000000 --- a/src/main/java/ru/practicum/eventhub/application/openapi/CategoryPageMapper.java +++ /dev/null @@ -1,31 +0,0 @@ -package ru.practicum.eventhub.application.openapi; - -import jakarta.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import ru.practicum.eventhub.api.dto.response.CategoryDto; -import ru.practicum.eventhub.api.model.PageOfCategories; -import ru.practicum.eventhub.domain.dto.PagedResponse; -import ru.practicum.eventhub.domain.service.CategoryService; - -@Service -@RequiredArgsConstructor -public class CategoryPageMapper { - private final CategoryService categoryService; - - public PageOfCategories getCategories(Pageable pageable) { - PagedResponse page = categoryService.getCategories(pageable); - return getPageOfCategories(page); - } - - private static @NotNull PageOfCategories getPageOfCategories(PagedResponse page) { - PageOfCategories apiPage = new PageOfCategories(); - apiPage.setPage(page.page()); - apiPage.setSize(page.size()); - apiPage.setTotalElements(page.totalElements()); - apiPage.setTotalPages(page.totalPages()); - apiPage.setContent(page.content()); - return apiPage; - } -} diff --git a/src/main/java/ru/practicum/eventhub/application/openapi/EventPageMapper.java b/src/main/java/ru/practicum/eventhub/application/openapi/EventPageMapper.java deleted file mode 100644 index 36dd328..0000000 --- a/src/main/java/ru/practicum/eventhub/application/openapi/EventPageMapper.java +++ /dev/null @@ -1,31 +0,0 @@ -package ru.practicum.eventhub.application.openapi; - -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import ru.practicum.eventhub.api.dto.response.EventDto; -import ru.practicum.eventhub.api.model.PageOfEvents; -import ru.practicum.eventhub.domain.dto.PagedResponse; -import ru.practicum.eventhub.domain.service.EventService; - -@Service -@RequiredArgsConstructor -public class EventPageMapper { - private final EventService eventService; - - public PageOfEvents getEvents(Pageable pageable) { - PagedResponse page = eventService.getEvents(pageable); - return getPageOfEvents(page); - } - - private static @NonNull PageOfEvents getPageOfEvents(PagedResponse page) { - PageOfEvents apiPage = new PageOfEvents(); - apiPage.setPage(page.page()); - apiPage.setSize(page.size()); - apiPage.setTotalElements(page.totalElements()); - apiPage.setTotalPages(page.totalPages()); - apiPage.setContent(page.content()); - return apiPage; - } -} diff --git a/src/main/java/ru/practicum/eventhub/application/openapi/MetadataPageMapper.java b/src/main/java/ru/practicum/eventhub/application/openapi/MetadataPageMapper.java deleted file mode 100644 index 08d7775..0000000 --- a/src/main/java/ru/practicum/eventhub/application/openapi/MetadataPageMapper.java +++ /dev/null @@ -1,31 +0,0 @@ -package ru.practicum.eventhub.application.openapi; - -import jakarta.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import ru.practicum.eventhub.api.dto.response.UserMetadataDto; -import ru.practicum.eventhub.api.model.PageOfMetadata; -import ru.practicum.eventhub.domain.dto.PagedResponse; -import ru.practicum.eventhub.domain.service.UserMetadataService; - -@Service -@RequiredArgsConstructor -public class MetadataPageMapper { - private final UserMetadataService userMetadataService; - - public PageOfMetadata getUserMetadata(Pageable pageable) { - PagedResponse page = userMetadataService.getUserMetadata(pageable); - return getUserMetadata(page); - } - - private static @NotNull PageOfMetadata getUserMetadata(PagedResponse page) { - PageOfMetadata pageOfMetadata = new PageOfMetadata(); - pageOfMetadata.setPage(page.page()); - pageOfMetadata.setSize(page.size()); - pageOfMetadata.setTotalElements(page.totalElements()); - pageOfMetadata.setTotalPages(page.totalPages()); - pageOfMetadata.setContent(page.content()); - return pageOfMetadata; - } -} diff --git a/src/main/java/ru/practicum/eventhub/application/openapi/ProjectPageMapper.java b/src/main/java/ru/practicum/eventhub/application/openapi/ProjectPageMapper.java deleted file mode 100644 index d6166c2..0000000 --- a/src/main/java/ru/practicum/eventhub/application/openapi/ProjectPageMapper.java +++ /dev/null @@ -1,33 +0,0 @@ -package ru.practicum.eventhub.application.openapi; - -import jakarta.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import ru.practicum.eventhub.api.dto.response.ProjectDto; -import ru.practicum.eventhub.api.model.PageOfProjects; -import ru.practicum.eventhub.domain.dto.PagedResponse; -import ru.practicum.eventhub.domain.service.ProjectService; - -import java.util.UUID; - -@Service -@RequiredArgsConstructor -public class ProjectPageMapper { - private final ProjectService projectService; - - public PageOfProjects getProjectsByCategory(UUID categoryId, Pageable pageable) { - PagedResponse page = projectService.getProjectsByCategory(categoryId, pageable); - return getPageOfProjects(page); - } - - public static @NotNull PageOfProjects getPageOfProjects(PagedResponse page) { - PageOfProjects apiPage = new PageOfProjects(); - apiPage.setPage(page.page()); - apiPage.setSize(page.size()); - apiPage.setTotalElements(page.totalElements()); - apiPage.setTotalPages(page.totalPages()); - apiPage.setContent(page.content()); - return apiPage; - } -} diff --git a/src/main/java/ru/practicum/eventhub/application/openapi/TagPageMapper.java b/src/main/java/ru/practicum/eventhub/application/openapi/TagPageMapper.java deleted file mode 100644 index ce81013..0000000 --- a/src/main/java/ru/practicum/eventhub/application/openapi/TagPageMapper.java +++ /dev/null @@ -1,38 +0,0 @@ -package ru.practicum.eventhub.application.openapi; - -import jakarta.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import ru.practicum.eventhub.api.dto.response.TagDto; -import ru.practicum.eventhub.api.model.PageOfTags; -import ru.practicum.eventhub.domain.dto.PagedResponse; -import ru.practicum.eventhub.domain.service.TagService; - -import java.util.UUID; - -@Service -@RequiredArgsConstructor -public class TagPageMapper { - private final TagService tagService; - - public PageOfTags getTags(Pageable pageable) { - PagedResponse page = tagService.getTags(pageable); - return getPageOfTags(page); - } - - public PageOfTags getTagsByEvent(UUID eventId, Pageable pageable) { - PagedResponse page = tagService.getTagsByEvent(eventId, pageable); - return getPageOfTags(page); - } - - private static @NotNull PageOfTags getPageOfTags(PagedResponse page) { - PageOfTags apiPage = new PageOfTags(); - apiPage.setPage(page.page()); - apiPage.setSize(page.size()); - apiPage.setTotalElements(page.totalElements()); - apiPage.setTotalPages(page.totalPages()); - apiPage.setContent(page.content()); - return apiPage; - } -} diff --git a/src/main/java/ru/practicum/eventhub/application/openapi/UserPageMapper.java b/src/main/java/ru/practicum/eventhub/application/openapi/UserPageMapper.java deleted file mode 100644 index a074e58..0000000 --- a/src/main/java/ru/practicum/eventhub/application/openapi/UserPageMapper.java +++ /dev/null @@ -1,31 +0,0 @@ -package ru.practicum.eventhub.application.openapi; - -import jakarta.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import ru.practicum.eventhub.api.dto.response.UserDto; -import ru.practicum.eventhub.api.model.PageOfUsers; -import ru.practicum.eventhub.domain.dto.PagedResponse; -import ru.practicum.eventhub.domain.service.UserService; - -@Service -@RequiredArgsConstructor -public class UserPageMapper { - private final UserService userService; - - public PageOfUsers getUsers(Pageable pageable) { - PagedResponse page = userService.getUsers(pageable); - return getPageOfUsers(page); - } - - private static @NotNull PageOfUsers getPageOfUsers(PagedResponse page) { - PageOfUsers pageOfUsers = new PageOfUsers(); - pageOfUsers.setPage(page.page()); - pageOfUsers.setSize(page.size()); - pageOfUsers.setTotalElements(page.totalElements()); - pageOfUsers.setTotalPages(page.totalPages()); - pageOfUsers.setContent(page.content()); - return pageOfUsers; - } -} diff --git a/src/main/java/ru/practicum/eventhub/application/saga/TagAnalyticsSagaOrchestrator.java b/src/main/java/ru/practicum/eventhub/application/saga/TagAnalyticsSagaOrchestrator.java deleted file mode 100644 index 12f926f..0000000 --- a/src/main/java/ru/practicum/eventhub/application/saga/TagAnalyticsSagaOrchestrator.java +++ /dev/null @@ -1,53 +0,0 @@ -package ru.practicum.eventhub.application.saga; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import ru.practicum.eventhub.api.dto.response.TagStatsDto; -import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; -import ru.practicum.eventhub.api.mapper.TagMapper; -import ru.practicum.eventhub.application.analytics.TagAnalyticsFacade; -import ru.practicum.eventhub.domain.model.Tag; -import ru.practicum.eventhub.domain.service.TagService; - -import java.util.UUID; - -@Slf4j -@Service -@RequiredArgsConstructor -public class TagAnalyticsSagaOrchestrator { - private final TagService tagService; - private final TagAnalyticsFacade analyticsFacade; - private final TagMapper tagMapper; - - public TagWithStatsDto addTagToEvent(UUID eventId, UUID tagId) { - boolean tagAdded = false; - boolean incrementUsage = false; - - try { - Tag tag = tagService.addTagLocal(eventId, tagId); - tagAdded = true; - - TagStatsDto tagStatsDto = analyticsFacade.incrementUsage(tagId); - incrementUsage = true; - - tagService.cacheRefreshAfterAddTag(eventId, tagId); - - log.info("Тег с id={} добавлен к событию с id={}", tagId, eventId); - return tagMapper.toDtoWithStats(tag, tagStatsDto); - - } catch (Exception ex) { - log.error("Ошибка при добавлении тега с id={} к событию с id={}: {}", tagId, eventId, ex.getMessage()); - - if (incrementUsage) { - analyticsFacade.delete(tagId); - } - - if (tagAdded) { - tagService.removeTagLocal(eventId, tagId); - } - - throw ex; - } - } -} diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/feign/TagAnalyticsClient.java b/src/main/java/ru/practicum/eventhub/config/feign/TagAnalyticsClient.java similarity index 67% rename from src/main/java/ru/practicum/eventhub/infrastructure/feign/TagAnalyticsClient.java rename to src/main/java/ru/practicum/eventhub/config/feign/TagAnalyticsClient.java index ff0f903..cd2f165 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/feign/TagAnalyticsClient.java +++ b/src/main/java/ru/practicum/eventhub/config/feign/TagAnalyticsClient.java @@ -1,10 +1,10 @@ -package ru.practicum.eventhub.infrastructure.feign; +package ru.practicum.eventhub.config.feign; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; import ru.practicum.eventhub.api.dto.response.TagStatsDto; -import ru.practicum.eventhub.infrastructure.feign.config.FeignConfig; -import ru.practicum.eventhub.infrastructure.feign.fallback.TagAnalyticsFallbackFactory; +import ru.practicum.eventhub.config.feign.config.FeignConfig; +import ru.practicum.eventhub.config.feign.fallback.TagAnalyticsFallbackFactory; import java.util.Map; import java.util.Set; @@ -18,8 +18,8 @@ ) public interface TagAnalyticsClient { - @PostMapping("/api/v1/tags/{id}/used") - TagStatsDto incrementUsage(@PathVariable UUID id); + @PostMapping("api/v1/tags/{id}") + TagStatsDto createTagAnalytics(@PathVariable UUID id); @GetMapping("/api/v1/tags/{id}/stats") TagStatsDto getTagStats(@PathVariable UUID id); @@ -27,6 +27,6 @@ public interface TagAnalyticsClient { @PostMapping("/api/v1/tags/stats") Map getTagStatsBatch(@RequestBody Set tagIds); - @DeleteMapping("/api/v1/tags/{id}/used") + @DeleteMapping("/api/v1/tags/{id}") void deleteTagAnalytics(@PathVariable UUID id); } diff --git a/src/main/java/ru/practicum/eventhub/application/analytics/TagAnalyticsFacade.java b/src/main/java/ru/practicum/eventhub/config/feign/TagAnalyticsFacade.java similarity index 76% rename from src/main/java/ru/practicum/eventhub/application/analytics/TagAnalyticsFacade.java rename to src/main/java/ru/practicum/eventhub/config/feign/TagAnalyticsFacade.java index 016bded..d3f1208 100644 --- a/src/main/java/ru/practicum/eventhub/application/analytics/TagAnalyticsFacade.java +++ b/src/main/java/ru/practicum/eventhub/config/feign/TagAnalyticsFacade.java @@ -1,10 +1,9 @@ -package ru.practicum.eventhub.application.analytics; +package ru.practicum.eventhub.config.feign; import io.github.resilience4j.retry.annotation.Retry; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import ru.practicum.eventhub.api.dto.response.TagStatsDto; -import ru.practicum.eventhub.infrastructure.feign.TagAnalyticsClient; import java.util.Map; import java.util.Set; @@ -16,8 +15,8 @@ public class TagAnalyticsFacade { private final TagAnalyticsClient tagAnalyticsClient; @Retry(name = "tag-analytics") - public TagStatsDto incrementUsage(UUID tagId) { - return tagAnalyticsClient.incrementUsage(tagId); + public TagStatsDto createTagAnalytics(UUID tagId) { + return tagAnalyticsClient.createTagAnalytics(tagId); } @Retry(name = "tag-analytics") @@ -31,7 +30,7 @@ public Map getTagStatsBatch(Set tagIds) { } @Retry(name = "tag-analytics") - public void delete(UUID tagId) { + public void deleteTagAnalytics(UUID tagId) { tagAnalyticsClient.deleteTagAnalytics(tagId); } } diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/feign/config/FeignConfig.java b/src/main/java/ru/practicum/eventhub/config/feign/config/FeignConfig.java similarity index 67% rename from src/main/java/ru/practicum/eventhub/infrastructure/feign/config/FeignConfig.java rename to src/main/java/ru/practicum/eventhub/config/feign/config/FeignConfig.java index 9e2ff99..dd3611a 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/feign/config/FeignConfig.java +++ b/src/main/java/ru/practicum/eventhub/config/feign/config/FeignConfig.java @@ -1,9 +1,9 @@ -package ru.practicum.eventhub.infrastructure.feign.config; +package ru.practicum.eventhub.config.feign.config; import feign.codec.ErrorDecoder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import ru.practicum.eventhub.infrastructure.feign.decoder.TagAnalyticsErrorDecoder; +import ru.practicum.eventhub.config.feign.decoder.TagAnalyticsErrorDecoder; @Configuration public class FeignConfig { diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/feign/config/FeignRequestIdInterceptor.java b/src/main/java/ru/practicum/eventhub/config/feign/config/FeignRequestIdInterceptor.java similarity index 90% rename from src/main/java/ru/practicum/eventhub/infrastructure/feign/config/FeignRequestIdInterceptor.java rename to src/main/java/ru/practicum/eventhub/config/feign/config/FeignRequestIdInterceptor.java index 72bac98..f1ed51c 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/feign/config/FeignRequestIdInterceptor.java +++ b/src/main/java/ru/practicum/eventhub/config/feign/config/FeignRequestIdInterceptor.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.infrastructure.feign.config; +package ru.practicum.eventhub.config.feign.config; import feign.RequestInterceptor; import feign.RequestTemplate; diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/feign/decoder/TagAnalyticsErrorDecoder.java b/src/main/java/ru/practicum/eventhub/config/feign/decoder/TagAnalyticsErrorDecoder.java similarity index 77% rename from src/main/java/ru/practicum/eventhub/infrastructure/feign/decoder/TagAnalyticsErrorDecoder.java rename to src/main/java/ru/practicum/eventhub/config/feign/decoder/TagAnalyticsErrorDecoder.java index 4251986..5a8b63a 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/feign/decoder/TagAnalyticsErrorDecoder.java +++ b/src/main/java/ru/practicum/eventhub/config/feign/decoder/TagAnalyticsErrorDecoder.java @@ -1,9 +1,9 @@ -package ru.practicum.eventhub.infrastructure.feign.decoder; +package ru.practicum.eventhub.config.feign.decoder; import feign.Response; import feign.codec.ErrorDecoder; -import ru.practicum.eventhub.infrastructure.feign.exception.TagAnalyticsClientException; -import ru.practicum.eventhub.infrastructure.feign.exception.TagNotFoundException; +import ru.practicum.eventhub.config.feign.exception.TagAnalyticsClientException; +import ru.practicum.eventhub.config.feign.exception.TagNotFoundException; public class TagAnalyticsErrorDecoder implements ErrorDecoder { private final ErrorDecoder defaultDecoder = new Default(); diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/feign/exception/TagAnalyticsClientException.java b/src/main/java/ru/practicum/eventhub/config/feign/exception/TagAnalyticsClientException.java similarity index 71% rename from src/main/java/ru/practicum/eventhub/infrastructure/feign/exception/TagAnalyticsClientException.java rename to src/main/java/ru/practicum/eventhub/config/feign/exception/TagAnalyticsClientException.java index 847c189..81cc91a 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/feign/exception/TagAnalyticsClientException.java +++ b/src/main/java/ru/practicum/eventhub/config/feign/exception/TagAnalyticsClientException.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.infrastructure.feign.exception; +package ru.practicum.eventhub.config.feign.exception; public class TagAnalyticsClientException extends RuntimeException { public TagAnalyticsClientException(String message) { diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/feign/exception/TagNotFoundException.java b/src/main/java/ru/practicum/eventhub/config/feign/exception/TagNotFoundException.java similarity index 69% rename from src/main/java/ru/practicum/eventhub/infrastructure/feign/exception/TagNotFoundException.java rename to src/main/java/ru/practicum/eventhub/config/feign/exception/TagNotFoundException.java index 380b9fc..1686665 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/feign/exception/TagNotFoundException.java +++ b/src/main/java/ru/practicum/eventhub/config/feign/exception/TagNotFoundException.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.infrastructure.feign.exception; +package ru.practicum.eventhub.config.feign.exception; public class TagNotFoundException extends RuntimeException { public TagNotFoundException(String message) { diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallbackFactory.java b/src/main/java/ru/practicum/eventhub/config/feign/fallback/TagAnalyticsFallbackFactory.java similarity index 86% rename from src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallbackFactory.java rename to src/main/java/ru/practicum/eventhub/config/feign/fallback/TagAnalyticsFallbackFactory.java index fd66968..0e7bc59 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/feign/fallback/TagAnalyticsFallbackFactory.java +++ b/src/main/java/ru/practicum/eventhub/config/feign/fallback/TagAnalyticsFallbackFactory.java @@ -1,12 +1,12 @@ -package ru.practicum.eventhub.infrastructure.feign.fallback; +package ru.practicum.eventhub.config.feign.fallback; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.openfeign.FallbackFactory; import org.springframework.stereotype.Component; import ru.practicum.eventhub.api.dto.response.TagStatsDto; -import ru.practicum.eventhub.infrastructure.feign.TagAnalyticsClient; -import ru.practicum.eventhub.infrastructure.feign.exception.TagAnalyticsClientException; -import ru.practicum.eventhub.infrastructure.feign.exception.TagNotFoundException; +import ru.practicum.eventhub.config.feign.TagAnalyticsClient; +import ru.practicum.eventhub.config.feign.exception.TagAnalyticsClientException; +import ru.practicum.eventhub.config.feign.exception.TagNotFoundException; import java.util.Map; import java.util.Set; @@ -30,12 +30,7 @@ public TagAnalyticsClientFallback(Throwable cause) { } @Override - public TagStatsDto incrementUsage(UUID id) { - if (cause instanceof TagNotFoundException) { - log.debug("Тег с id={} не найден в Tag Analytics (404). Пропускаем создание статистики.", id); - throw (TagNotFoundException) cause; - } - + public TagStatsDto createTagAnalytics(UUID id) { if (cause instanceof TagAnalyticsClientException) { log.warn("Ошибка клиента при создании статистики для тега id={}: {}", id, cause.getMessage()); throw (TagAnalyticsClientException) cause; diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/redis/ManyToManyCacheIndexService.java b/src/main/java/ru/practicum/eventhub/config/redis/ManyToManyCacheIndexService.java similarity index 97% rename from src/main/java/ru/practicum/eventhub/infrastructure/redis/ManyToManyCacheIndexService.java rename to src/main/java/ru/practicum/eventhub/config/redis/ManyToManyCacheIndexService.java index aba5ee8..e22539c 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/redis/ManyToManyCacheIndexService.java +++ b/src/main/java/ru/practicum/eventhub/config/redis/ManyToManyCacheIndexService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.infrastructure.redis; +package ru.practicum.eventhub.config.redis; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/redis/ProjectCacheIndexService.java b/src/main/java/ru/practicum/eventhub/config/redis/ProjectCacheIndexService.java similarity index 95% rename from src/main/java/ru/practicum/eventhub/infrastructure/redis/ProjectCacheIndexService.java rename to src/main/java/ru/practicum/eventhub/config/redis/ProjectCacheIndexService.java index ea34b19..3eaa25d 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/redis/ProjectCacheIndexService.java +++ b/src/main/java/ru/practicum/eventhub/config/redis/ProjectCacheIndexService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.infrastructure.redis; +package ru.practicum.eventhub.config.redis; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; @@ -36,4 +36,3 @@ public void deleteCategoryIndex(UUID categoryId) { redisTemplate.delete(buildKey(categoryId)); } } - diff --git a/src/main/java/ru/practicum/eventhub/config/redis/RedisCacheService.java b/src/main/java/ru/practicum/eventhub/config/redis/RedisCacheService.java new file mode 100644 index 0000000..10bb152 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/config/redis/RedisCacheService.java @@ -0,0 +1,23 @@ +package ru.practicum.eventhub.config.redis; + +import lombok.RequiredArgsConstructor; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Component; + +import java.util.Collection; + +@Component +@RequiredArgsConstructor +public class RedisCacheService { + private final CacheManager cacheManager; + + public void evictAll(String cacheName, Collection keys) { + Cache cache = cacheManager.getCache(cacheName); + if (cache != null) { + for (Object key : keys) { + cache.evict(key); + } + } + } +} diff --git a/src/main/java/ru/practicum/eventhub/infrastructure/redis/config/RedisCacheConfig.java b/src/main/java/ru/practicum/eventhub/config/redis/config/RedisCacheConfig.java similarity index 95% rename from src/main/java/ru/practicum/eventhub/infrastructure/redis/config/RedisCacheConfig.java rename to src/main/java/ru/practicum/eventhub/config/redis/config/RedisCacheConfig.java index 32b28e9..f971d33 100644 --- a/src/main/java/ru/practicum/eventhub/infrastructure/redis/config/RedisCacheConfig.java +++ b/src/main/java/ru/practicum/eventhub/config/redis/config/RedisCacheConfig.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.infrastructure.redis.config; +package ru.practicum.eventhub.config.redis.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/ru/practicum/eventhub/controller/CategoryProjectController.java b/src/main/java/ru/practicum/eventhub/controller/CategoryProjectController.java index 4110749..e9552c3 100644 --- a/src/main/java/ru/practicum/eventhub/controller/CategoryProjectController.java +++ b/src/main/java/ru/practicum/eventhub/controller/CategoryProjectController.java @@ -13,18 +13,14 @@ import ru.practicum.eventhub.api.dto.response.ProjectDto; import ru.practicum.eventhub.api.model.PageOfCategories; import ru.practicum.eventhub.api.model.PageOfProjects; -import ru.practicum.eventhub.application.openapi.CategoryPageMapper; -import ru.practicum.eventhub.application.openapi.ProjectPageMapper; -import ru.practicum.eventhub.domain.service.CategoryService; -import ru.practicum.eventhub.domain.service.ProjectService; +import ru.practicum.eventhub.service.CategoryService; +import ru.practicum.eventhub.service.ProjectService; import java.util.UUID; @RestController @RequiredArgsConstructor public class CategoryProjectController implements CategoriesApi { - private final CategoryPageMapper categoryPageMapper; - private final ProjectPageMapper projectPageMapper; private final CategoryService categoryService; private final ProjectService projectService; @@ -54,7 +50,7 @@ public ResponseEntity deleteProjectForCategory(UUID categoryId, UUID proje @Override public ResponseEntity getCategories(Integer page, Integer size) { - PageOfCategories pageOfCategories = categoryPageMapper.getCategories(PageRequest.of(page, size)); + PageOfCategories pageOfCategories = categoryService.getCategories(PageRequest.of(page, size)); return ResponseEntity.ok(pageOfCategories); } @@ -72,7 +68,7 @@ public ResponseEntity getProjectByCategoryId(UUID categoryId, UUID p @Override public ResponseEntity getProjectsByCategoryId(UUID categoryId, Integer page, Integer size) { - PageOfProjects pageOfProjects = projectPageMapper.getProjectsByCategory(categoryId, PageRequest.of(page, size)); + PageOfProjects pageOfProjects = projectService.getProjectsByCategory(categoryId, PageRequest.of(page, size)); return ResponseEntity.ok(pageOfProjects); } diff --git a/src/main/java/ru/practicum/eventhub/controller/EventTagController.java b/src/main/java/ru/practicum/eventhub/controller/EventTagController.java index 74d8b4c..c62d9a4 100644 --- a/src/main/java/ru/practicum/eventhub/controller/EventTagController.java +++ b/src/main/java/ru/practicum/eventhub/controller/EventTagController.java @@ -15,26 +15,20 @@ import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; import ru.practicum.eventhub.api.model.PageOfEvents; import ru.practicum.eventhub.api.model.PageOfTags; -import ru.practicum.eventhub.application.openapi.EventPageMapper; -import ru.practicum.eventhub.application.openapi.TagPageMapper; -import ru.practicum.eventhub.application.saga.TagAnalyticsSagaOrchestrator; -import ru.practicum.eventhub.domain.service.EventService; -import ru.practicum.eventhub.domain.service.TagService; +import ru.practicum.eventhub.service.EventService; +import ru.practicum.eventhub.service.TagService; import java.util.UUID; @RestController @RequiredArgsConstructor public class EventTagController implements EventsApi { - private final EventPageMapper eventPageMapper; - private final TagPageMapper tagPageMapper; private final EventService eventService; private final TagService tagService; - private final TagAnalyticsSagaOrchestrator tagSagaOrchestrator; @Override public ResponseEntity addTagToEvent(UUID eventId, UUID tagId) { - TagWithStatsDto tagWithStatsDto = tagSagaOrchestrator.addTagToEvent(eventId, tagId); + TagWithStatsDto tagWithStatsDto = tagService.addTagToEvent(eventId, tagId); return ResponseEntity .status(HttpStatus.CREATED) .body(tagWithStatsDto); @@ -88,7 +82,7 @@ public ResponseEntity getEventById(UUID eventId) { @Override public ResponseEntity getEvents(Integer page, Integer size) { - PageOfEvents pageOfEvents = eventPageMapper.getEvents(PageRequest.of(page, size)); + PageOfEvents pageOfEvents = eventService.getEvents(PageRequest.of(page, size)); return ResponseEntity.ok(pageOfEvents); } @@ -100,13 +94,13 @@ public ResponseEntity getTagByEventId(UUID eventId, UUID tagId) @Override public ResponseEntity getTags(Integer page, Integer size) { - PageOfTags pageOfTags = tagPageMapper.getTags(PageRequest.of(page, size)); + PageOfTags pageOfTags = tagService.getTags(PageRequest.of(page, size)); return ResponseEntity.ok(pageOfTags); } @Override public ResponseEntity getTagsByEventId(UUID eventId, Integer page, Integer size) { - PageOfTags pageOfTags = tagPageMapper.getTagsByEvent(eventId, PageRequest.of(page, size)); + PageOfTags pageOfTags = tagService.getTagsByEvent(eventId, PageRequest.of(page, size)); return ResponseEntity.ok(pageOfTags); } diff --git a/src/main/java/ru/practicum/eventhub/controller/UserController.java b/src/main/java/ru/practicum/eventhub/controller/UserController.java index 58000ac..adf004b 100644 --- a/src/main/java/ru/practicum/eventhub/controller/UserController.java +++ b/src/main/java/ru/practicum/eventhub/controller/UserController.java @@ -12,18 +12,14 @@ import ru.practicum.eventhub.api.dto.response.UserMetadataDto; import ru.practicum.eventhub.api.model.PageOfMetadata; import ru.practicum.eventhub.api.model.PageOfUsers; -import ru.practicum.eventhub.application.openapi.MetadataPageMapper; -import ru.practicum.eventhub.application.openapi.UserPageMapper; -import ru.practicum.eventhub.domain.service.UserMetadataService; -import ru.practicum.eventhub.domain.service.UserService; +import ru.practicum.eventhub.service.UserMetadataService; +import ru.practicum.eventhub.service.UserService; import java.util.UUID; @RestController @RequiredArgsConstructor public class UserController implements UsersApi { - private final UserPageMapper userPageMapper; - private final MetadataPageMapper metadataPageMapper; private final UserService userService; private final UserMetadataService userMetadataService; @@ -51,7 +47,7 @@ public ResponseEntity getUserById(UUID userId) { @Override public ResponseEntity getUserMetadata(Integer page, Integer size) { - PageOfMetadata pageOfUsers = metadataPageMapper.getUserMetadata(PageRequest.of(page, size)); + PageOfMetadata pageOfUsers = userMetadataService.getUserMetadata(PageRequest.of(page, size)); return ResponseEntity.ok(pageOfUsers); } @@ -63,7 +59,7 @@ public ResponseEntity getUserMetadataById(UUID userId) { @Override public ResponseEntity getUsers(Integer page, Integer size) { - PageOfUsers pageOfUsers = userPageMapper.getUsers(PageRequest.of(page, size)); + PageOfUsers pageOfUsers = userService.getUsers(PageRequest.of(page, size)); return ResponseEntity.ok(pageOfUsers); } diff --git a/src/main/java/ru/practicum/eventhub/controller/advice/GlobalControllerAdvice.java b/src/main/java/ru/practicum/eventhub/controller/advice/GlobalControllerAdvice.java index 04c4c93..f4a1822 100644 --- a/src/main/java/ru/practicum/eventhub/controller/advice/GlobalControllerAdvice.java +++ b/src/main/java/ru/practicum/eventhub/controller/advice/GlobalControllerAdvice.java @@ -21,8 +21,8 @@ import ru.practicum.eventhub.api.exception.ConflictException; import ru.practicum.eventhub.api.exception.ErrorResponse; import ru.practicum.eventhub.api.exception.ForbiddenException; -import ru.practicum.eventhub.infrastructure.feign.exception.TagAnalyticsClientException; -import ru.practicum.eventhub.infrastructure.feign.exception.TagNotFoundException; +import ru.practicum.eventhub.config.feign.exception.TagAnalyticsClientException; +import ru.practicum.eventhub.config.feign.exception.TagNotFoundException; import java.util.stream.Collectors; diff --git a/src/main/java/ru/practicum/eventhub/domain/dto/PagedResponse.java b/src/main/java/ru/practicum/eventhub/domain/dto/PagedResponse.java deleted file mode 100644 index 6be2f7e..0000000 --- a/src/main/java/ru/practicum/eventhub/domain/dto/PagedResponse.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.practicum.eventhub.domain.dto; - -import org.springframework.data.domain.Page; - -import java.util.List; -import java.util.function.Function; - -public record PagedResponse(List content, int page, int size, long totalElements, int totalPages) { - - public static PagedResponse from(Page page, Function mapper) { - return new PagedResponse<>( - page.getContent().stream().map(mapper).toList(), - page.getNumber(), - page.getSize(), - page.getTotalElements(), - page.getTotalPages() - ); - } -} diff --git a/src/main/java/ru/practicum/eventhub/domain/event/CategoryDeletedEvent.java b/src/main/java/ru/practicum/eventhub/domain/event/CategoryDeletedEvent.java deleted file mode 100644 index 5833229..0000000 --- a/src/main/java/ru/practicum/eventhub/domain/event/CategoryDeletedEvent.java +++ /dev/null @@ -1,6 +0,0 @@ -package ru.practicum.eventhub.domain.event; - -import java.util.UUID; - -public record CategoryDeletedEvent(UUID categoryId) { -} diff --git a/src/main/java/ru/practicum/eventhub/model/ActionType.java b/src/main/java/ru/practicum/eventhub/model/ActionType.java new file mode 100644 index 0000000..19a04ad --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/model/ActionType.java @@ -0,0 +1,5 @@ +package ru.practicum.eventhub.model; + +public enum ActionType { + DELETE_ANALYTICS +} diff --git a/src/main/java/ru/practicum/eventhub/domain/model/Category.java b/src/main/java/ru/practicum/eventhub/model/Category.java similarity index 96% rename from src/main/java/ru/practicum/eventhub/domain/model/Category.java rename to src/main/java/ru/practicum/eventhub/model/Category.java index d74d94a..070db6e 100644 --- a/src/main/java/ru/practicum/eventhub/domain/model/Category.java +++ b/src/main/java/ru/practicum/eventhub/model/Category.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.model; +package ru.practicum.eventhub.model; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; diff --git a/src/main/java/ru/practicum/eventhub/model/CompensationAction.java b/src/main/java/ru/practicum/eventhub/model/CompensationAction.java new file mode 100644 index 0000000..bd65c7a --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/model/CompensationAction.java @@ -0,0 +1,40 @@ +package ru.practicum.eventhub.model; + +import jakarta.persistence.*; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.OffsetDateTime; +import java.util.UUID; + +@Entity +@Table(name = "compensation_actions") +@Getter +@Setter +@EqualsAndHashCode(of = "id") +public class CompensationAction { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @Column(nullable = false, length = 50) + @Enumerated(EnumType.STRING) + private ActionType actionType; + + @Column(nullable = false) + private String payload; + + private int retries = 0; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private OffsetDateTime createdAt; + + @UpdateTimestamp + @Column(name = "last_attempt") + private OffsetDateTime lastAttempt; +} diff --git a/src/main/java/ru/practicum/eventhub/domain/model/Event.java b/src/main/java/ru/practicum/eventhub/model/Event.java similarity index 97% rename from src/main/java/ru/practicum/eventhub/domain/model/Event.java rename to src/main/java/ru/practicum/eventhub/model/Event.java index fea01cd..5f761e2 100644 --- a/src/main/java/ru/practicum/eventhub/domain/model/Event.java +++ b/src/main/java/ru/practicum/eventhub/model/Event.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.model; +package ru.practicum.eventhub.model; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; diff --git a/src/main/java/ru/practicum/eventhub/domain/model/Project.java b/src/main/java/ru/practicum/eventhub/model/Project.java similarity index 95% rename from src/main/java/ru/practicum/eventhub/domain/model/Project.java rename to src/main/java/ru/practicum/eventhub/model/Project.java index 488bdc8..f9f6f54 100644 --- a/src/main/java/ru/practicum/eventhub/domain/model/Project.java +++ b/src/main/java/ru/practicum/eventhub/model/Project.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.model; +package ru.practicum.eventhub.model; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; diff --git a/src/main/java/ru/practicum/eventhub/domain/model/Tag.java b/src/main/java/ru/practicum/eventhub/model/Tag.java similarity index 95% rename from src/main/java/ru/practicum/eventhub/domain/model/Tag.java rename to src/main/java/ru/practicum/eventhub/model/Tag.java index 76a7c72..88763ce 100644 --- a/src/main/java/ru/practicum/eventhub/domain/model/Tag.java +++ b/src/main/java/ru/practicum/eventhub/model/Tag.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.model; +package ru.practicum.eventhub.model; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; diff --git a/src/main/java/ru/practicum/eventhub/domain/model/User.java b/src/main/java/ru/practicum/eventhub/model/User.java similarity index 96% rename from src/main/java/ru/practicum/eventhub/domain/model/User.java rename to src/main/java/ru/practicum/eventhub/model/User.java index d269c9d..777a7b9 100644 --- a/src/main/java/ru/practicum/eventhub/domain/model/User.java +++ b/src/main/java/ru/practicum/eventhub/model/User.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.model; +package ru.practicum.eventhub.model; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; diff --git a/src/main/java/ru/practicum/eventhub/domain/model/UserMetadata.java b/src/main/java/ru/practicum/eventhub/model/UserMetadata.java similarity index 96% rename from src/main/java/ru/practicum/eventhub/domain/model/UserMetadata.java rename to src/main/java/ru/practicum/eventhub/model/UserMetadata.java index abb7163..b2af67d 100644 --- a/src/main/java/ru/practicum/eventhub/domain/model/UserMetadata.java +++ b/src/main/java/ru/practicum/eventhub/model/UserMetadata.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.model; +package ru.practicum.eventhub.model; import jakarta.persistence.*; import lombok.EqualsAndHashCode; diff --git a/src/main/java/ru/practicum/eventhub/domain/repository/CategoryRepository.java b/src/main/java/ru/practicum/eventhub/repository/CategoryRepository.java similarity index 89% rename from src/main/java/ru/practicum/eventhub/domain/repository/CategoryRepository.java rename to src/main/java/ru/practicum/eventhub/repository/CategoryRepository.java index 6d44c27..fd82312 100644 --- a/src/main/java/ru/practicum/eventhub/domain/repository/CategoryRepository.java +++ b/src/main/java/ru/practicum/eventhub/repository/CategoryRepository.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.repository; +package ru.practicum.eventhub.repository; import jakarta.persistence.LockModeType; import lombok.NonNull; @@ -8,7 +8,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Query; -import ru.practicum.eventhub.domain.model.Category; +import ru.practicum.eventhub.model.Category; import java.util.Optional; import java.util.UUID; diff --git a/src/main/java/ru/practicum/eventhub/repository/CompensationActionRepository.java b/src/main/java/ru/practicum/eventhub/repository/CompensationActionRepository.java new file mode 100644 index 0000000..97425f1 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/repository/CompensationActionRepository.java @@ -0,0 +1,9 @@ +package ru.practicum.eventhub.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.practicum.eventhub.model.CompensationAction; + +import java.util.UUID; + +public interface CompensationActionRepository extends JpaRepository { +} diff --git a/src/main/java/ru/practicum/eventhub/domain/repository/EventRepository.java b/src/main/java/ru/practicum/eventhub/repository/EventRepository.java similarity index 88% rename from src/main/java/ru/practicum/eventhub/domain/repository/EventRepository.java rename to src/main/java/ru/practicum/eventhub/repository/EventRepository.java index a1e6315..eb91b68 100644 --- a/src/main/java/ru/practicum/eventhub/domain/repository/EventRepository.java +++ b/src/main/java/ru/practicum/eventhub/repository/EventRepository.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.repository; +package ru.practicum.eventhub.repository; import jakarta.persistence.LockModeType; import lombok.NonNull; @@ -8,7 +8,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Query; -import ru.practicum.eventhub.domain.model.Event; +import ru.practicum.eventhub.model.Event; import java.util.Optional; import java.util.UUID; diff --git a/src/main/java/ru/practicum/eventhub/domain/repository/ProjectRepository.java b/src/main/java/ru/practicum/eventhub/repository/ProjectRepository.java similarity index 86% rename from src/main/java/ru/practicum/eventhub/domain/repository/ProjectRepository.java rename to src/main/java/ru/practicum/eventhub/repository/ProjectRepository.java index 06aa60d..f5f1e95 100644 --- a/src/main/java/ru/practicum/eventhub/domain/repository/ProjectRepository.java +++ b/src/main/java/ru/practicum/eventhub/repository/ProjectRepository.java @@ -1,10 +1,10 @@ -package ru.practicum.eventhub.domain.repository; +package ru.practicum.eventhub.repository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; -import ru.practicum.eventhub.domain.model.Project; +import ru.practicum.eventhub.model.Project; import java.util.Optional; import java.util.UUID; diff --git a/src/main/java/ru/practicum/eventhub/domain/repository/TagRepository.java b/src/main/java/ru/practicum/eventhub/repository/TagRepository.java similarity index 89% rename from src/main/java/ru/practicum/eventhub/domain/repository/TagRepository.java rename to src/main/java/ru/practicum/eventhub/repository/TagRepository.java index 176a68f..deb0457 100644 --- a/src/main/java/ru/practicum/eventhub/domain/repository/TagRepository.java +++ b/src/main/java/ru/practicum/eventhub/repository/TagRepository.java @@ -1,11 +1,11 @@ -package ru.practicum.eventhub.domain.repository; +package ru.practicum.eventhub.repository; import lombok.NonNull; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; -import ru.practicum.eventhub.domain.model.Tag; +import ru.practicum.eventhub.model.Tag; import java.util.List; import java.util.Optional; diff --git a/src/main/java/ru/practicum/eventhub/domain/repository/UserMetadataRepository.java b/src/main/java/ru/practicum/eventhub/repository/UserMetadataRepository.java similarity index 85% rename from src/main/java/ru/practicum/eventhub/domain/repository/UserMetadataRepository.java rename to src/main/java/ru/practicum/eventhub/repository/UserMetadataRepository.java index 808c959..97669c2 100644 --- a/src/main/java/ru/practicum/eventhub/domain/repository/UserMetadataRepository.java +++ b/src/main/java/ru/practicum/eventhub/repository/UserMetadataRepository.java @@ -1,11 +1,11 @@ -package ru.practicum.eventhub.domain.repository; +package ru.practicum.eventhub.repository; import lombok.NonNull; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; -import ru.practicum.eventhub.domain.model.UserMetadata; +import ru.practicum.eventhub.model.UserMetadata; import java.util.Optional; import java.util.UUID; diff --git a/src/main/java/ru/practicum/eventhub/domain/repository/UserRepository.java b/src/main/java/ru/practicum/eventhub/repository/UserRepository.java similarity index 85% rename from src/main/java/ru/practicum/eventhub/domain/repository/UserRepository.java rename to src/main/java/ru/practicum/eventhub/repository/UserRepository.java index 7cbba61..6ad044d 100644 --- a/src/main/java/ru/practicum/eventhub/domain/repository/UserRepository.java +++ b/src/main/java/ru/practicum/eventhub/repository/UserRepository.java @@ -1,10 +1,10 @@ -package ru.practicum.eventhub.domain.repository; +package ru.practicum.eventhub.repository; import jakarta.persistence.LockModeType; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Query; -import ru.practicum.eventhub.domain.model.User; +import ru.practicum.eventhub.model.User; import java.util.Optional; import java.util.UUID; diff --git a/src/main/java/ru/practicum/eventhub/domain/service/CategoryService.java b/src/main/java/ru/practicum/eventhub/service/CategoryService.java similarity index 75% rename from src/main/java/ru/practicum/eventhub/domain/service/CategoryService.java rename to src/main/java/ru/practicum/eventhub/service/CategoryService.java index b78d201..5bd1d55 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/CategoryService.java +++ b/src/main/java/ru/practicum/eventhub/service/CategoryService.java @@ -1,17 +1,17 @@ -package ru.practicum.eventhub.domain.service; +package ru.practicum.eventhub.service; import org.springframework.data.domain.Pageable; import ru.practicum.eventhub.api.dto.request.CategoryCreateDto; import ru.practicum.eventhub.api.dto.request.CategoryUpdateDto; import ru.practicum.eventhub.api.dto.response.CategoryDto; -import ru.practicum.eventhub.domain.dto.PagedResponse; +import ru.practicum.eventhub.api.model.PageOfCategories; import java.util.UUID; public interface CategoryService { CategoryDto createCategory(CategoryCreateDto dto); - PagedResponse getCategories(Pageable pageable); + PageOfCategories getCategories(Pageable pageable); CategoryDto getCategoryById(UUID id); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/EventService.java b/src/main/java/ru/practicum/eventhub/service/EventService.java similarity index 74% rename from src/main/java/ru/practicum/eventhub/domain/service/EventService.java rename to src/main/java/ru/practicum/eventhub/service/EventService.java index 6611971..4833197 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/EventService.java +++ b/src/main/java/ru/practicum/eventhub/service/EventService.java @@ -1,17 +1,17 @@ -package ru.practicum.eventhub.domain.service; +package ru.practicum.eventhub.service; import org.springframework.data.domain.Pageable; import ru.practicum.eventhub.api.dto.request.EventCreateDto; import ru.practicum.eventhub.api.dto.request.EventUpdateDto; import ru.practicum.eventhub.api.dto.response.EventDto; -import ru.practicum.eventhub.domain.dto.PagedResponse; +import ru.practicum.eventhub.api.model.PageOfEvents; import java.util.UUID; public interface EventService { EventDto createEvent(EventCreateDto dto); - PagedResponse getEvents(Pageable pageable); + PageOfEvents getEvents(Pageable pageable); EventDto getEventById(UUID id); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/ProjectService.java b/src/main/java/ru/practicum/eventhub/service/ProjectService.java similarity index 70% rename from src/main/java/ru/practicum/eventhub/domain/service/ProjectService.java rename to src/main/java/ru/practicum/eventhub/service/ProjectService.java index d1efb5b..16e345c 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/ProjectService.java +++ b/src/main/java/ru/practicum/eventhub/service/ProjectService.java @@ -1,14 +1,14 @@ -package ru.practicum.eventhub.domain.service; +package ru.practicum.eventhub.service; import org.springframework.data.domain.Pageable; import ru.practicum.eventhub.api.dto.request.ProjectUpdateDto; import ru.practicum.eventhub.api.dto.response.ProjectDto; -import ru.practicum.eventhub.domain.dto.PagedResponse; +import ru.practicum.eventhub.api.model.PageOfProjects; import java.util.UUID; public interface ProjectService { - PagedResponse getProjectsByCategory(UUID categoryId, Pageable pageable); + PageOfProjects getProjectsByCategory(UUID categoryId, Pageable pageable); ProjectDto getProjectByCategory(UUID categoryId, UUID projectId); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/TagService.java b/src/main/java/ru/practicum/eventhub/service/TagService.java similarity index 63% rename from src/main/java/ru/practicum/eventhub/domain/service/TagService.java rename to src/main/java/ru/practicum/eventhub/service/TagService.java index ebf1e6a..4be32b6 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/TagService.java +++ b/src/main/java/ru/practicum/eventhub/service/TagService.java @@ -1,23 +1,22 @@ -package ru.practicum.eventhub.domain.service; +package ru.practicum.eventhub.service; import org.springframework.data.domain.Pageable; import ru.practicum.eventhub.api.dto.request.TagCreateDto; import ru.practicum.eventhub.api.dto.request.TagUpdateDto; import ru.practicum.eventhub.api.dto.response.TagDto; import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; -import ru.practicum.eventhub.domain.dto.PagedResponse; -import ru.practicum.eventhub.domain.model.Tag; +import ru.practicum.eventhub.api.model.PageOfTags; import java.util.UUID; public interface TagService { - TagDto createTag(TagCreateDto dto); + TagWithStatsDto addTagToEvent(UUID eventId, UUID tagId); - Tag addTagLocal(UUID eventId, UUID tagId); + TagDto createTag(TagCreateDto dto); - PagedResponse getTags(Pageable pageable); + PageOfTags getTags(Pageable pageable); - PagedResponse getTagsByEvent(UUID eventId, Pageable pageable); + PageOfTags getTagsByEvent(UUID eventId, Pageable pageable); TagWithStatsDto getTagByEvent(UUID eventId, UUID tagId); @@ -28,6 +27,4 @@ public interface TagService { void deleteTag(UUID tagId); void cacheRefreshAfterAddTag(UUID eventId, UUID tagId); - - void removeTagLocal(UUID eventId, UUID tagId); } diff --git a/src/main/java/ru/practicum/eventhub/domain/service/UserMetadataService.java b/src/main/java/ru/practicum/eventhub/service/UserMetadataService.java similarity index 56% rename from src/main/java/ru/practicum/eventhub/domain/service/UserMetadataService.java rename to src/main/java/ru/practicum/eventhub/service/UserMetadataService.java index 297efa4..65edca1 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/UserMetadataService.java +++ b/src/main/java/ru/practicum/eventhub/service/UserMetadataService.java @@ -1,13 +1,13 @@ -package ru.practicum.eventhub.domain.service; +package ru.practicum.eventhub.service; import org.springframework.data.domain.Pageable; import ru.practicum.eventhub.api.dto.response.UserMetadataDto; -import ru.practicum.eventhub.domain.dto.PagedResponse; +import ru.practicum.eventhub.api.model.PageOfMetadata; import java.util.UUID; public interface UserMetadataService { - PagedResponse getUserMetadata(Pageable pageable); + PageOfMetadata getUserMetadata(Pageable pageable); UserMetadataDto getByUserId(UUID userId); } diff --git a/src/main/java/ru/practicum/eventhub/domain/service/UserService.java b/src/main/java/ru/practicum/eventhub/service/UserService.java similarity index 75% rename from src/main/java/ru/practicum/eventhub/domain/service/UserService.java rename to src/main/java/ru/practicum/eventhub/service/UserService.java index b144f20..eb98cb2 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/UserService.java +++ b/src/main/java/ru/practicum/eventhub/service/UserService.java @@ -1,18 +1,18 @@ -package ru.practicum.eventhub.domain.service; +package ru.practicum.eventhub.service; import jakarta.validation.Valid; import org.springframework.data.domain.Pageable; import ru.practicum.eventhub.api.dto.request.UserCreateDto; import ru.practicum.eventhub.api.dto.request.UserUpdateDto; import ru.practicum.eventhub.api.dto.response.UserDto; -import ru.practicum.eventhub.domain.dto.PagedResponse; +import ru.practicum.eventhub.api.model.PageOfUsers; import java.util.UUID; public interface UserService { UserDto createUser(@Valid UserCreateDto dto); - PagedResponse getUsers(Pageable pageable); + PageOfUsers getUsers(Pageable pageable); UserDto getUserById(UUID id); diff --git a/src/main/java/ru/practicum/eventhub/service/cache/CategoryProjectCacheService.java b/src/main/java/ru/practicum/eventhub/service/cache/CategoryProjectCacheService.java new file mode 100644 index 0000000..9fa50ff --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/service/cache/CategoryProjectCacheService.java @@ -0,0 +1,32 @@ +package ru.practicum.eventhub.service.cache; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import ru.practicum.eventhub.config.redis.ProjectCacheIndexService; +import ru.practicum.eventhub.config.redis.RedisCacheService; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CategoryProjectCacheService { + private final ProjectCacheIndexService cacheIndexService; + private final RedisCacheService cacheService; + + public void evictProjectsByCategoryId(UUID categoryId) { + Set projectIds = cacheIndexService.getProjects(categoryId); + + List keys = projectIds.stream() + .map(projectId -> categoryId + ":" + projectId) + .toList(); + + cacheService.evictAll("projects", keys); + + cacheIndexService.deleteCategoryIndex(categoryId); + log.info("Полностью очищен кэш и индекс для категории id={}", categoryId); + } +} diff --git a/src/main/java/ru/practicum/eventhub/application/cache/TagCacheService.java b/src/main/java/ru/practicum/eventhub/service/cache/EventTagCacheService.java similarity index 67% rename from src/main/java/ru/practicum/eventhub/application/cache/TagCacheService.java rename to src/main/java/ru/practicum/eventhub/service/cache/EventTagCacheService.java index 3391c22..8729340 100644 --- a/src/main/java/ru/practicum/eventhub/application/cache/TagCacheService.java +++ b/src/main/java/ru/practicum/eventhub/service/cache/EventTagCacheService.java @@ -1,19 +1,18 @@ -package ru.practicum.eventhub.application.cache; +package ru.practicum.eventhub.service.cache; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; -import org.springframework.cache.annotation.CachePut; -import org.springframework.stereotype.Component; -import ru.practicum.eventhub.api.dto.response.TagDto; +import org.springframework.stereotype.Service; import ru.practicum.eventhub.api.dto.response.TagStatsDto; import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; import ru.practicum.eventhub.api.mapper.TagMapper; -import ru.practicum.eventhub.application.analytics.TagAnalyticsFacade; -import ru.practicum.eventhub.domain.model.Tag; -import ru.practicum.eventhub.domain.service.impl.TagReadService; -import ru.practicum.eventhub.infrastructure.redis.ManyToManyCacheIndexService; +import ru.practicum.eventhub.config.feign.TagAnalyticsFacade; +import ru.practicum.eventhub.config.redis.ManyToManyCacheIndexService; +import ru.practicum.eventhub.config.redis.RedisCacheService; +import ru.practicum.eventhub.model.Tag; +import ru.practicum.eventhub.service.impl.TagReadService; import java.util.List; import java.util.Map; @@ -21,9 +20,9 @@ import java.util.UUID; @Slf4j -@Component +@Service @RequiredArgsConstructor -public class TagCacheService { +public class EventTagCacheService { private static final String EVENT_TAG_RELATION = "event_tag"; private final TagReadService tagReadService; @@ -31,13 +30,7 @@ public class TagCacheService { private final ManyToManyCacheIndexService relationIndexService; private final CacheManager cacheManager; private final TagAnalyticsFacade tagAnalyticsFacade; - - @CachePut(value = "tags", key = "#tagId") - public TagDto refresh(UUID tagId) { - Tag tag = tagReadService.findById(tagId); - log.info("Обновлен кеш тега с id={}", tagId); - return tagMapper.toDto(tag); - } + private final RedisCacheService cacheService; public void refreshByEventBatch(UUID eventId, Set tagIds) { if (tagIds == null || tagIds.isEmpty()) return; @@ -78,21 +71,14 @@ public void refreshCompositeCacheForTag(Tag tag, Set eventIds) { log.info("Обновлен кэш тега с id={} для всех связанных событий", tagId); } - public void evictAll(UUID tagId) { - Cache mainCache = cacheManager.getCache("tags"); - if (mainCache != null) { - mainCache.evict(tagId); - } - + public void evictEventTagCache(UUID tagId) { Set eventIds = relationIndexService.getLeftIds(EVENT_TAG_RELATION, tagId); - Cache eventCache = cacheManager.getCache("tags_by_event"); - if (eventCache != null) { - for (UUID eventId : eventIds) { - eventCache.evict(eventId + ":" + tagId); - log.info("Удален кэш тега с id={} для события с id={}", tagId, eventId); - } - } + List keys = eventIds.stream() + .map(eventId -> eventId + ":" + tagId) + .toList(); + + cacheService.evictAll("tags_by_event", keys); relationIndexService.deleteRight(EVENT_TAG_RELATION, tagId); diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryReadService.java b/src/main/java/ru/practicum/eventhub/service/impl/CategoryReadService.java similarity index 86% rename from src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryReadService.java rename to src/main/java/ru/practicum/eventhub/service/impl/CategoryReadService.java index 5ace37a..da5d5fd 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryReadService.java +++ b/src/main/java/ru/practicum/eventhub/service/impl/CategoryReadService.java @@ -1,13 +1,14 @@ -package ru.practicum.eventhub.domain.service.impl; +package ru.practicum.eventhub.service.impl; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CachePut; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.eventhub.api.exception.ConflictException; -import ru.practicum.eventhub.domain.model.Category; -import ru.practicum.eventhub.domain.repository.CategoryRepository; +import ru.practicum.eventhub.model.Category; +import ru.practicum.eventhub.repository.CategoryRepository; import java.util.UUID; @@ -17,6 +18,7 @@ public class CategoryReadService { private final CategoryRepository categoryRepository; + @CachePut(value = "categories", key = "#id") @Transactional(readOnly = true) public Category findById(UUID id) { return categoryRepository.findById(id).orElseThrow(() -> { diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryServiceImpl.java b/src/main/java/ru/practicum/eventhub/service/impl/CategoryServiceImpl.java similarity index 71% rename from src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryServiceImpl.java rename to src/main/java/ru/practicum/eventhub/service/impl/CategoryServiceImpl.java index 932b9a6..38b68d0 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/CategoryServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/service/impl/CategoryServiceImpl.java @@ -1,11 +1,10 @@ -package ru.practicum.eventhub.domain.service.impl; +package ru.practicum.eventhub.service.impl; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -14,13 +13,13 @@ import ru.practicum.eventhub.api.dto.request.CategoryUpdateDto; import ru.practicum.eventhub.api.dto.response.CategoryDto; import ru.practicum.eventhub.api.mapper.CategoryMapper; -import ru.practicum.eventhub.domain.dto.PagedResponse; -import ru.practicum.eventhub.domain.event.CategoryDeletedEvent; -import ru.practicum.eventhub.domain.model.Category; -import ru.practicum.eventhub.domain.repository.CategoryRepository; -import ru.practicum.eventhub.domain.service.CategoryService; -import ru.practicum.eventhub.domain.util.PageValidator; -import ru.practicum.eventhub.domain.validation.CategoryValidationService; +import ru.practicum.eventhub.api.model.PageOfCategories; +import ru.practicum.eventhub.model.Category; +import ru.practicum.eventhub.repository.CategoryRepository; +import ru.practicum.eventhub.service.CategoryService; +import ru.practicum.eventhub.service.cache.CategoryProjectCacheService; +import ru.practicum.eventhub.util.PageValidator; +import ru.practicum.eventhub.validation.CategoryValidationService; import java.util.UUID; @@ -32,7 +31,7 @@ public class CategoryServiceImpl implements CategoryService { private final CategoryMapper categoryMapper; private final CategoryReadService categoryReadService; private final CategoryValidationService categoryValidationService; - private final ApplicationEventPublisher eventPublisher; + private final CategoryProjectCacheService categoryProjectCacheService; @Override @Transactional @@ -48,13 +47,13 @@ public CategoryDto createCategory(CategoryCreateDto dto) { @Override @Transactional(readOnly = true) - public PagedResponse getCategories(Pageable pageable) { + public PageOfCategories getCategories(Pageable pageable) { Page page = categoryRepository.findAll(pageable); PageValidator.validatePage(page); log.info("Отправлена страница категорий: страница={}, размер={}", pageable.getPageNumber(), pageable.getPageSize()); - return PagedResponse.from(page, categoryMapper::toDto); + return toApiPage(page, categoryMapper); } @Override @@ -88,7 +87,17 @@ public void deleteCategory(UUID id) { categoryReadService.findById(id); categoryRepository.deleteById(id); - eventPublisher.publishEvent(new CategoryDeletedEvent(id)); + categoryProjectCacheService.evictProjectsByCategoryId(id); log.info("Удалена категория с id={}", id); } + + private PageOfCategories toApiPage(Page page, CategoryMapper mapper) { + PageOfCategories apiPage = new PageOfCategories(); + apiPage.setContent(page.getContent().stream().map(mapper::toDto).toList()); + apiPage.setPageNumber(page.getNumber()); + apiPage.setPageSize(page.getSize()); + apiPage.setTotalElements(page.getTotalElements()); + apiPage.setTotalPages(page.getTotalPages()); + return apiPage; + } } diff --git a/src/main/java/ru/practicum/eventhub/service/impl/CompensationScheduler.java b/src/main/java/ru/practicum/eventhub/service/impl/CompensationScheduler.java new file mode 100644 index 0000000..8e108d9 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/service/impl/CompensationScheduler.java @@ -0,0 +1,45 @@ +package ru.practicum.eventhub.service.impl; + +import io.swagger.v3.oas.annotations.servers.Server; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.eventhub.config.feign.TagAnalyticsFacade; +import ru.practicum.eventhub.model.ActionType; +import ru.practicum.eventhub.model.CompensationAction; +import ru.practicum.eventhub.repository.CompensationActionRepository; + +import java.util.List; +import java.util.UUID; + +@Slf4j +@Server +@RequiredArgsConstructor +public class CompensationScheduler { + private final CompensationActionRepository repository; + private final TagAnalyticsFacade analyticsFacade; + + @Scheduled(fixedDelay = 180000) + @Transactional + public void processCompensations() { + List actions = repository.findAll(); + + for (CompensationAction action : actions) { + try { + if (action.getActionType() == ActionType.DELETE_ANALYTICS) { + UUID tagId = UUID.fromString(action.getPayload()); + analyticsFacade.deleteTagAnalytics(tagId); + } + + repository.delete(action); + log.info("Успешно выполнена компенсация для id={}", action.getId()); + + } catch (Exception exception) { + action.setRetries(action.getRetries() + 1); + repository.save(action); + log.warn("Повторная ошибка компенсации для id={}. Попытка №{}", action.getId(), action.getRetries()); + } + } + } +} diff --git a/src/main/java/ru/practicum/eventhub/service/impl/CompensationService.java b/src/main/java/ru/practicum/eventhub/service/impl/CompensationService.java new file mode 100644 index 0000000..0fef565 --- /dev/null +++ b/src/main/java/ru/practicum/eventhub/service/impl/CompensationService.java @@ -0,0 +1,25 @@ +package ru.practicum.eventhub.service.impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import ru.practicum.eventhub.model.ActionType; +import ru.practicum.eventhub.model.CompensationAction; +import ru.practicum.eventhub.repository.CompensationActionRepository; + +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class CompensationService { + private final CompensationActionRepository repository; + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void saveForLater(UUID tagId, ActionType type) { + CompensationAction action = new CompensationAction(); + action.setPayload(tagId.toString()); + action.setActionType(type); + repository.save(action); + } +} diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventReadService.java b/src/main/java/ru/practicum/eventhub/service/impl/EventReadService.java similarity index 63% rename from src/main/java/ru/practicum/eventhub/domain/service/impl/EventReadService.java rename to src/main/java/ru/practicum/eventhub/service/impl/EventReadService.java index 893309d..1ff4091 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventReadService.java +++ b/src/main/java/ru/practicum/eventhub/service/impl/EventReadService.java @@ -1,12 +1,15 @@ -package ru.practicum.eventhub.domain.service.impl; +package ru.practicum.eventhub.service.impl; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CachePut; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import ru.practicum.eventhub.domain.model.Event; -import ru.practicum.eventhub.domain.repository.EventRepository; +import ru.practicum.eventhub.api.dto.response.EventDto; +import ru.practicum.eventhub.api.mapper.EventMapper; +import ru.practicum.eventhub.model.Event; +import ru.practicum.eventhub.repository.EventRepository; import java.util.UUID; @@ -15,6 +18,7 @@ @RequiredArgsConstructor public class EventReadService { private final EventRepository eventRepository; + private final EventMapper eventMapper; @Transactional(readOnly = true) public Event findById(UUID id) { @@ -24,6 +28,14 @@ public Event findById(UUID id) { }); } + @CachePut(value = "events", key = "#id") + @Transactional(readOnly = true) + public EventDto findDtoById(UUID id) { + Event event = findById(id); + log.info("Обновлен кеш события с id={}", id); + return eventMapper.toDto(event); + } + @Transactional(readOnly = true) public Event findByIdForUpdate(UUID id) { return eventRepository.findByIdForUpdate(id).orElseThrow(() -> { diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java b/src/main/java/ru/practicum/eventhub/service/impl/EventServiceImpl.java similarity index 77% rename from src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java rename to src/main/java/ru/practicum/eventhub/service/impl/EventServiceImpl.java index 89c6e99..561c9ac 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/EventServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/service/impl/EventServiceImpl.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.service.impl; +package ru.practicum.eventhub.service.impl; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -14,15 +14,15 @@ import ru.practicum.eventhub.api.dto.request.EventUpdateDto; import ru.practicum.eventhub.api.dto.response.EventDto; import ru.practicum.eventhub.api.mapper.EventMapper; -import ru.practicum.eventhub.application.cache.TagCacheService; -import ru.practicum.eventhub.domain.dto.PagedResponse; -import ru.practicum.eventhub.domain.model.Event; -import ru.practicum.eventhub.domain.model.Tag; -import ru.practicum.eventhub.domain.repository.EventRepository; -import ru.practicum.eventhub.domain.service.EventService; -import ru.practicum.eventhub.domain.util.PageValidator; -import ru.practicum.eventhub.domain.validation.EventValidationService; -import ru.practicum.eventhub.infrastructure.redis.ManyToManyCacheIndexService; +import ru.practicum.eventhub.api.model.PageOfEvents; +import ru.practicum.eventhub.config.redis.ManyToManyCacheIndexService; +import ru.practicum.eventhub.model.Event; +import ru.practicum.eventhub.model.Tag; +import ru.practicum.eventhub.repository.EventRepository; +import ru.practicum.eventhub.service.EventService; +import ru.practicum.eventhub.service.cache.EventTagCacheService; +import ru.practicum.eventhub.util.PageValidator; +import ru.practicum.eventhub.validation.EventValidationService; import java.util.Map; import java.util.Set; @@ -40,7 +40,7 @@ public class EventServiceImpl implements EventService { private final EventMapper eventMapper; private final EventValidationService eventValidationService; private final ManyToManyCacheIndexService relationIndexService; - private final TagCacheService tagCacheService; + private final EventTagCacheService tagCacheService; private final TransactionTemplate transactionTemplate; @Override @@ -57,12 +57,12 @@ public EventDto createEvent(EventCreateDto dto) { @Override @Transactional(readOnly = true) - public PagedResponse getEvents(Pageable pageable) { + public PageOfEvents getEvents(Pageable pageable) { Page page = eventRepository.findAll(pageable); PageValidator.validatePage(page); log.info("Запрошены события: страница {}, размер {}", pageable.getPageNumber(), pageable.getPageSize()); - return PagedResponse.from(page, eventMapper::toDto); + return toApiPage(page, eventMapper); } @Override @@ -114,4 +114,14 @@ public void deleteEvent(UUID id) { log.info("Удалено событие с id={}", id); } + + private PageOfEvents toApiPage(Page page, EventMapper mapper) { + PageOfEvents apiPage = new PageOfEvents(); + apiPage.setContent(page.getContent().stream().map(mapper::toDto).toList()); + apiPage.setPageNumber(page.getNumber()); + apiPage.setPageSize(page.getSize()); + apiPage.setTotalElements(page.getTotalElements()); + apiPage.setTotalPages(page.getTotalPages()); + return apiPage; + } } diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/MetadataReadService.java b/src/main/java/ru/practicum/eventhub/service/impl/MetadataReadService.java similarity index 87% rename from src/main/java/ru/practicum/eventhub/domain/service/impl/MetadataReadService.java rename to src/main/java/ru/practicum/eventhub/service/impl/MetadataReadService.java index 033eab8..62a9818 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/MetadataReadService.java +++ b/src/main/java/ru/practicum/eventhub/service/impl/MetadataReadService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.service.impl; +package ru.practicum.eventhub.service.impl; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; @@ -6,8 +6,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.eventhub.api.exception.ConflictException; -import ru.practicum.eventhub.domain.model.UserMetadata; -import ru.practicum.eventhub.domain.repository.UserMetadataRepository; +import ru.practicum.eventhub.model.UserMetadata; +import ru.practicum.eventhub.repository.UserMetadataRepository; import java.util.UUID; diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectReadService.java b/src/main/java/ru/practicum/eventhub/service/impl/ProjectReadService.java similarity index 88% rename from src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectReadService.java rename to src/main/java/ru/practicum/eventhub/service/impl/ProjectReadService.java index 4a2db42..1c6d301 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectReadService.java +++ b/src/main/java/ru/practicum/eventhub/service/impl/ProjectReadService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.service.impl; +package ru.practicum.eventhub.service.impl; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; @@ -6,8 +6,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.eventhub.api.exception.ConflictException; -import ru.practicum.eventhub.domain.model.Project; -import ru.practicum.eventhub.domain.repository.ProjectRepository; +import ru.practicum.eventhub.model.Project; +import ru.practicum.eventhub.repository.ProjectRepository; import java.util.UUID; diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectServiceImpl.java b/src/main/java/ru/practicum/eventhub/service/impl/ProjectServiceImpl.java similarity index 74% rename from src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectServiceImpl.java rename to src/main/java/ru/practicum/eventhub/service/impl/ProjectServiceImpl.java index 3ca9038..012c50d 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/ProjectServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/service/impl/ProjectServiceImpl.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.service.impl; +package ru.practicum.eventhub.service.impl; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -12,15 +12,14 @@ import ru.practicum.eventhub.api.dto.request.ProjectUpdateDto; import ru.practicum.eventhub.api.dto.response.ProjectDto; import ru.practicum.eventhub.api.mapper.ProjectMapper; -import ru.practicum.eventhub.application.cache.CategoryCacheService; -import ru.practicum.eventhub.domain.dto.PagedResponse; -import ru.practicum.eventhub.domain.model.Category; -import ru.practicum.eventhub.domain.model.Project; -import ru.practicum.eventhub.domain.repository.ProjectRepository; -import ru.practicum.eventhub.domain.service.ProjectService; -import ru.practicum.eventhub.domain.util.PageValidator; -import ru.practicum.eventhub.domain.validation.ProjectValidationService; -import ru.practicum.eventhub.infrastructure.redis.ProjectCacheIndexService; +import ru.practicum.eventhub.api.model.PageOfProjects; +import ru.practicum.eventhub.config.redis.ProjectCacheIndexService; +import ru.practicum.eventhub.model.Category; +import ru.practicum.eventhub.model.Project; +import ru.practicum.eventhub.repository.ProjectRepository; +import ru.practicum.eventhub.service.ProjectService; +import ru.practicum.eventhub.util.PageValidator; +import ru.practicum.eventhub.validation.ProjectValidationService; import java.util.UUID; @@ -34,18 +33,17 @@ public class ProjectServiceImpl implements ProjectService { private final CategoryReadService categoryReadService; private final ProjectValidationService projectValidationService; private final ProjectCacheIndexService cacheIndexService; - private final CategoryCacheService categoryCacheService; @Override @Transactional(readOnly = true) - public PagedResponse getProjectsByCategory(UUID categoryId, Pageable pageable) { + public PageOfProjects getProjectsByCategory(UUID categoryId, Pageable pageable) { categoryReadService.findById(categoryId); Page page = projectRepository.findAllByCategoryId(categoryId, pageable); PageValidator.validatePage(page); log.info("Получены проекты: страница {}, размер {}", pageable.getPageNumber(), pageable.getPageSize()); - return PagedResponse.from(page, projectMapper::toDto); + return toApiPage(page, projectMapper); } @Override @@ -86,8 +84,18 @@ public void deleteForCategory(UUID categoryId, UUID projectId) { category.removeProject(project); cacheIndexService.removeProject(categoryId, projectId); - categoryCacheService.refresh(categoryId); + categoryReadService.findById(categoryId); log.info("Удален проект с id={} в категории с id={}", projectId, categoryId); } + + private PageOfProjects toApiPage(Page page, ProjectMapper mapper) { + PageOfProjects apiPage = new PageOfProjects(); + apiPage.setContent(page.getContent().stream().map(mapper::toDto).toList()); + apiPage.setPageNumber(page.getNumber()); + apiPage.setPageSize(page.getSize()); + apiPage.setTotalElements(page.getTotalElements()); + apiPage.setTotalPages(page.getTotalPages()); + return apiPage; + } } diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagReadService.java b/src/main/java/ru/practicum/eventhub/service/impl/TagReadService.java similarity index 76% rename from src/main/java/ru/practicum/eventhub/domain/service/impl/TagReadService.java rename to src/main/java/ru/practicum/eventhub/service/impl/TagReadService.java index f798e01..a77a09e 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagReadService.java +++ b/src/main/java/ru/practicum/eventhub/service/impl/TagReadService.java @@ -1,13 +1,16 @@ -package ru.practicum.eventhub.domain.service.impl; +package ru.practicum.eventhub.service.impl; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CachePut; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import ru.practicum.eventhub.api.dto.response.TagDto; import ru.practicum.eventhub.api.exception.ConflictException; -import ru.practicum.eventhub.domain.model.Tag; -import ru.practicum.eventhub.domain.repository.TagRepository; +import ru.practicum.eventhub.api.mapper.TagMapper; +import ru.practicum.eventhub.model.Tag; +import ru.practicum.eventhub.repository.TagRepository; import java.util.List; import java.util.Set; @@ -18,6 +21,7 @@ @RequiredArgsConstructor public class TagReadService { private final TagRepository tagRepository; + private final TagMapper tagMapper; @Transactional(readOnly = true) public Tag findById(UUID tagId) { @@ -27,6 +31,14 @@ public Tag findById(UUID tagId) { }); } + @CachePut(value = "tags", key = "#tagId") + @Transactional(readOnly = true) + public TagDto findDtoById(UUID tagId) { + Tag tag = findById(tagId); + log.info("Обновлен кеш тега с id={}", tagId); + return tagMapper.toDto(tag); + } + @Transactional(readOnly = true) public Tag findByIdAndEventsId(UUID tagId, UUID eventId) { return tagRepository.findByIdAndEventsId(tagId, eventId).orElseThrow(() -> { diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java b/src/main/java/ru/practicum/eventhub/service/impl/TagServiceImpl.java similarity index 65% rename from src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java rename to src/main/java/ru/practicum/eventhub/service/impl/TagServiceImpl.java index 9531a0f..335fcb0 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/TagServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/service/impl/TagServiceImpl.java @@ -1,7 +1,8 @@ -package ru.practicum.eventhub.domain.service.impl; +package ru.practicum.eventhub.service.impl; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; @@ -16,17 +17,17 @@ import ru.practicum.eventhub.api.dto.response.TagWithStatsDto; import ru.practicum.eventhub.api.exception.ConflictException; import ru.practicum.eventhub.api.mapper.TagMapper; -import ru.practicum.eventhub.application.analytics.TagAnalyticsFacade; -import ru.practicum.eventhub.application.cache.EventCacheService; -import ru.practicum.eventhub.application.cache.TagCacheService; -import ru.practicum.eventhub.domain.dto.PagedResponse; -import ru.practicum.eventhub.domain.model.Event; -import ru.practicum.eventhub.domain.model.Tag; -import ru.practicum.eventhub.domain.repository.TagRepository; -import ru.practicum.eventhub.domain.service.TagService; -import ru.practicum.eventhub.domain.util.PageValidator; -import ru.practicum.eventhub.domain.validation.TagValidationService; -import ru.practicum.eventhub.infrastructure.redis.ManyToManyCacheIndexService; +import ru.practicum.eventhub.api.model.PageOfTags; +import ru.practicum.eventhub.config.feign.TagAnalyticsFacade; +import ru.practicum.eventhub.config.redis.ManyToManyCacheIndexService; +import ru.practicum.eventhub.model.ActionType; +import ru.practicum.eventhub.model.Event; +import ru.practicum.eventhub.model.Tag; +import ru.practicum.eventhub.repository.TagRepository; +import ru.practicum.eventhub.service.TagService; +import ru.practicum.eventhub.service.cache.EventTagCacheService; +import ru.practicum.eventhub.util.PageValidator; +import ru.practicum.eventhub.validation.TagValidationService; import java.util.Iterator; import java.util.Set; @@ -44,10 +45,48 @@ public class TagServiceImpl implements TagService { private final EventReadService eventReadService; private final TagValidationService tagValidationService; private final TagAnalyticsFacade tagAnalyticsFacade; - private final EventCacheService eventCacheService; - private final TagCacheService tagCacheService; + private final EventTagCacheService eventTagCacheService; private final ManyToManyCacheIndexService relationIndexService; private final TransactionTemplate transactionTemplate; + private final CompensationService compensationService; + + @Override + @Transactional + public TagWithStatsDto addTagToEvent(UUID eventId, UUID tagId) { + boolean tagAdded = false; + boolean incrementUsage = false; + + try { + Tag tag = addTagLocal(eventId, tagId); + tagAdded = true; + + TagStatsDto tagStatsDto = tagAnalyticsFacade.createTagAnalytics(tagId); + incrementUsage = true; + + cacheRefreshAfterAddTag(eventId, tagId); + + log.info("Тег с id={} добавлен к событию с id={}", tagId, eventId); + return tagMapper.toDtoWithStats(tag, tagStatsDto); + + } catch (Exception ex) { + log.error("Ошибка при добавлении тега с id={} к событию с id={}: {}", tagId, eventId, ex.getMessage()); + + if (incrementUsage) { + try { + tagAnalyticsFacade.deleteTagAnalytics(tagId); + } catch (Exception exception) { + log.error("Не удалось откатить аналитику сразу, сохраняем в Scheduler"); + compensationService.saveForLater(tagId, ActionType.DELETE_ANALYTICS); + } + } + + if (tagAdded) { + removeTagLocal(eventId, tagId); + } + + throw ex; + } + } @Override @Transactional @@ -60,43 +99,26 @@ public TagDto createTag(TagCreateDto dto) { return tagMapper.toDto(tag); } - @Override - @Transactional - public Tag addTagLocal(UUID eventId, UUID tagId) { - Event event = eventReadService.findById(eventId); - Tag tag = tagReadService.findById(tagId); - - if (tagReadService.existsByIdAndEventsId(tagId, eventId)) { - log.error("Тег с id={} уже добавлен к событию с id={}", tagId, eventId); - throw new ConflictException("Тег с id=" + tagId + " уже добавлен к событию с id=" + eventId); - } - - event.addTag(tag); - relationIndexService.add(EVENT_TAG_RELATION, eventId, tagId); - - return tag; - } - @Override @Transactional(readOnly = true) - public PagedResponse getTags(Pageable pageable) { + public PageOfTags getTags(Pageable pageable) { Page page = tagRepository.findAll(pageable); PageValidator.validatePage(page); log.info("Запрошены теги: страница {}, размер {}", pageable.getPageNumber(), pageable.getPageSize()); - return PagedResponse.from(page, tagMapper::toDto); + return toApiPage(page, tagMapper); } @Override @Transactional(readOnly = true) - public PagedResponse getTagsByEvent(UUID eventId, Pageable pageable) { + public PageOfTags getTagsByEvent(UUID eventId, Pageable pageable) { eventReadService.findById(eventId); Page page = tagRepository.findAllByEventsId(eventId, pageable); PageValidator.validatePage(page); log.info("Запрошены теги для события с id={}: страница {}, размер {}", eventId, pageable.getPageNumber(), pageable.getPageSize()); - return PagedResponse.from(page, tagMapper::toDto); + return toApiPage(page, tagMapper); } @Override @@ -129,7 +151,7 @@ public TagDto updateTag(UUID tagId, TagUpdateDto dto) { }); Set eventIds = relationIndexService.getLeftIds(EVENT_TAG_RELATION, tagId); - tagCacheService.refreshCompositeCacheForTag(tag, eventIds); + eventTagCacheService.refreshCompositeCacheForTag(tag, eventIds); log.info("Обновлен тег с id={}", tagId); return tagMapper.toDto(tag); @@ -146,13 +168,14 @@ public void deleteForEvent(UUID eventId, UUID tagId) { relationIndexService.remove(EVENT_TAG_RELATION, eventId, tagId); - eventCacheService.refresh(eventId); - tagCacheService.refreshByEventBatch(eventId, Set.of(tagId)); + eventReadService.findDtoById(eventId); + eventTagCacheService.refreshByEventBatch(eventId, Set.of(tagId)); log.info("Тег с id={} отвязан от события с id={}", tagId, eventId); } @Override + @CacheEvict(value = "tags", key = "#tagId") public void deleteTag(UUID tagId) { transactionTemplate.executeWithoutResult(status -> { Tag tag = tagReadService.findById(tagId); @@ -167,19 +190,34 @@ public void deleteTag(UUID tagId) { tagRepository.deleteById(tagId); }); - tagCacheService.evictAll(tagId); + eventTagCacheService.evictEventTagCache(tagId); log.info("Удален тег с id={}", tagId); } @Override public void cacheRefreshAfterAddTag(UUID eventId, UUID tagId) { - eventCacheService.refresh(eventId); - tagCacheService.refresh(tagId); + eventReadService.findDtoById(eventId); + tagReadService.findDtoById(tagId); log.info("Кэш обновлен для события с id={} и тега с id={}", eventId, tagId); } - @Override + @Transactional + public Tag addTagLocal(UUID eventId, UUID tagId) { + Event event = eventReadService.findById(eventId); + Tag tag = tagReadService.findById(tagId); + + if (tagReadService.existsByIdAndEventsId(tagId, eventId)) { + log.error("Тег с id={} уже добавлен к событию с id={}", tagId, eventId); + throw new ConflictException("Тег с id=" + tagId + " уже добавлен к событию с id=" + eventId); + } + + event.addTag(tag); + relationIndexService.add(EVENT_TAG_RELATION, eventId, tagId); + + return tag; + } + @Transactional public void removeTagLocal(UUID eventId, UUID tagId) { Event event = eventReadService.findById(eventId); @@ -191,4 +229,14 @@ public void removeTagLocal(UUID eventId, UUID tagId) { log.info("Тег с id={} удален из события с id={}", tagId, eventId); } + + private PageOfTags toApiPage(Page page, TagMapper mapper) { + PageOfTags apiPage = new PageOfTags(); + apiPage.setContent(page.getContent().stream().map(mapper::toDto).toList()); + apiPage.setPageNumber(page.getNumber()); + apiPage.setPageSize(page.getSize()); + apiPage.setTotalElements(page.getTotalElements()); + apiPage.setTotalPages(page.getTotalPages()); + return apiPage; + } } diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/UserMetadataServiceImpl.java b/src/main/java/ru/practicum/eventhub/service/impl/UserMetadataServiceImpl.java similarity index 64% rename from src/main/java/ru/practicum/eventhub/domain/service/impl/UserMetadataServiceImpl.java rename to src/main/java/ru/practicum/eventhub/service/impl/UserMetadataServiceImpl.java index b303b28..9fdbb4e 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/UserMetadataServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/service/impl/UserMetadataServiceImpl.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.service.impl; +package ru.practicum.eventhub.service.impl; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -9,11 +9,11 @@ import org.springframework.transaction.annotation.Transactional; import ru.practicum.eventhub.api.dto.response.UserMetadataDto; import ru.practicum.eventhub.api.mapper.UserMetadataMapper; -import ru.practicum.eventhub.domain.dto.PagedResponse; -import ru.practicum.eventhub.domain.model.UserMetadata; -import ru.practicum.eventhub.domain.repository.UserMetadataRepository; -import ru.practicum.eventhub.domain.service.UserMetadataService; -import ru.practicum.eventhub.domain.util.PageValidator; +import ru.practicum.eventhub.api.model.PageOfMetadata; +import ru.practicum.eventhub.model.UserMetadata; +import ru.practicum.eventhub.repository.UserMetadataRepository; +import ru.practicum.eventhub.service.UserMetadataService; +import ru.practicum.eventhub.util.PageValidator; import java.util.UUID; @@ -28,13 +28,13 @@ public class UserMetadataServiceImpl implements UserMetadataService { @Override @Transactional(readOnly = true) - public PagedResponse getUserMetadata(Pageable pageable) { + public PageOfMetadata getUserMetadata(Pageable pageable) { Page page = userMetadataRepository.findAll(pageable); PageValidator.validatePage(page); log.info("Получена страница user-metadata: страница={}, размер={}", pageable.getPageNumber(), pageable.getPageSize()); - return PagedResponse.from(page, userMetadataMapper::toDto); + return toApiPage(page, userMetadataMapper); } @Override @@ -48,4 +48,14 @@ public UserMetadataDto getByUserId(UUID userId) { log.info("Запрошена user-metadata с id={}, userId={}", metadataId, userId); return userMetadataMapper.toDto(userMetadata); } + + private PageOfMetadata toApiPage(Page page, UserMetadataMapper mapper) { + PageOfMetadata apiPage = new PageOfMetadata(); + apiPage.setContent(page.getContent().stream().map(mapper::toDto).toList()); + apiPage.setPageNumber(page.getNumber()); + apiPage.setPageSize(page.getSize()); + apiPage.setTotalElements(page.getTotalElements()); + apiPage.setTotalPages(page.getTotalPages()); + return apiPage; + } } diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/UserReadService.java b/src/main/java/ru/practicum/eventhub/service/impl/UserReadService.java similarity index 92% rename from src/main/java/ru/practicum/eventhub/domain/service/impl/UserReadService.java rename to src/main/java/ru/practicum/eventhub/service/impl/UserReadService.java index 387730c..622b5a3 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/UserReadService.java +++ b/src/main/java/ru/practicum/eventhub/service/impl/UserReadService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.service.impl; +package ru.practicum.eventhub.service.impl; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; @@ -6,8 +6,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.practicum.eventhub.api.exception.ConflictException; -import ru.practicum.eventhub.domain.model.User; -import ru.practicum.eventhub.domain.repository.UserRepository; +import ru.practicum.eventhub.model.User; +import ru.practicum.eventhub.repository.UserRepository; import java.util.UUID; diff --git a/src/main/java/ru/practicum/eventhub/domain/service/impl/UserServiceImpl.java b/src/main/java/ru/practicum/eventhub/service/impl/UserServiceImpl.java similarity index 76% rename from src/main/java/ru/practicum/eventhub/domain/service/impl/UserServiceImpl.java rename to src/main/java/ru/practicum/eventhub/service/impl/UserServiceImpl.java index f9e2759..0a1c2af 100644 --- a/src/main/java/ru/practicum/eventhub/domain/service/impl/UserServiceImpl.java +++ b/src/main/java/ru/practicum/eventhub/service/impl/UserServiceImpl.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.service.impl; +package ru.practicum.eventhub.service.impl; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -14,12 +14,12 @@ import ru.practicum.eventhub.api.dto.request.UserUpdateDto; import ru.practicum.eventhub.api.dto.response.UserDto; import ru.practicum.eventhub.api.mapper.UserMapper; -import ru.practicum.eventhub.domain.dto.PagedResponse; -import ru.practicum.eventhub.domain.model.User; -import ru.practicum.eventhub.domain.repository.UserRepository; -import ru.practicum.eventhub.domain.service.UserService; -import ru.practicum.eventhub.domain.util.PageValidator; -import ru.practicum.eventhub.domain.validation.UserValidationService; +import ru.practicum.eventhub.api.model.PageOfUsers; +import ru.practicum.eventhub.model.User; +import ru.practicum.eventhub.repository.UserRepository; +import ru.practicum.eventhub.service.UserService; +import ru.practicum.eventhub.util.PageValidator; +import ru.practicum.eventhub.validation.UserValidationService; import java.util.UUID; @@ -48,13 +48,13 @@ public UserDto createUser(UserCreateDto dto) { @Override @Transactional(readOnly = true) - public PagedResponse getUsers(Pageable pageable) { + public PageOfUsers getUsers(Pageable pageable) { Page page = userRepository.findAll(pageable); PageValidator.validatePage(page); log.info("Получена страница пользователей: страница={}, размер={}", pageable.getPageNumber(), pageable.getPageSize()); - return PagedResponse.from(page, userMapper::toDto); + return toApiPage(page, userMapper); } @Override @@ -92,4 +92,14 @@ public void deleteUser(UUID id) { userRepository.deleteById(id); log.info("Удален пользователь с id={}", id); } + + private PageOfUsers toApiPage(Page page, UserMapper mapper) { + PageOfUsers apiPage = new PageOfUsers(); + apiPage.setContent(page.getContent().stream().map(mapper::toDto).toList()); + apiPage.setPageNumber(page.getNumber()); + apiPage.setPageSize(page.getSize()); + apiPage.setTotalElements(page.getTotalElements()); + apiPage.setTotalPages(page.getTotalPages()); + return apiPage; + } } diff --git a/src/main/java/ru/practicum/eventhub/domain/util/PageValidator.java b/src/main/java/ru/practicum/eventhub/util/PageValidator.java similarity index 94% rename from src/main/java/ru/practicum/eventhub/domain/util/PageValidator.java rename to src/main/java/ru/practicum/eventhub/util/PageValidator.java index 85dea4f..86025cf 100644 --- a/src/main/java/ru/practicum/eventhub/domain/util/PageValidator.java +++ b/src/main/java/ru/practicum/eventhub/util/PageValidator.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.util; +package ru.practicum.eventhub.util; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; diff --git a/src/main/java/ru/practicum/eventhub/domain/validation/CategoryValidationService.java b/src/main/java/ru/practicum/eventhub/validation/CategoryValidationService.java similarity index 88% rename from src/main/java/ru/practicum/eventhub/domain/validation/CategoryValidationService.java rename to src/main/java/ru/practicum/eventhub/validation/CategoryValidationService.java index c373874..64cb869 100644 --- a/src/main/java/ru/practicum/eventhub/domain/validation/CategoryValidationService.java +++ b/src/main/java/ru/practicum/eventhub/validation/CategoryValidationService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.validation; +package ru.practicum.eventhub.validation; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -8,9 +8,9 @@ import ru.practicum.eventhub.api.dto.request.CategoryUpdateDto; import ru.practicum.eventhub.api.dto.request.ProjectCreateDto; import ru.practicum.eventhub.api.exception.ConflictException; -import ru.practicum.eventhub.domain.model.Category; -import ru.practicum.eventhub.domain.service.impl.CategoryReadService; -import ru.practicum.eventhub.domain.service.impl.ProjectReadService; +import ru.practicum.eventhub.model.Category; +import ru.practicum.eventhub.service.impl.CategoryReadService; +import ru.practicum.eventhub.service.impl.ProjectReadService; import java.util.Set; diff --git a/src/main/java/ru/practicum/eventhub/domain/validation/EventValidationService.java b/src/main/java/ru/practicum/eventhub/validation/EventValidationService.java similarity index 90% rename from src/main/java/ru/practicum/eventhub/domain/validation/EventValidationService.java rename to src/main/java/ru/practicum/eventhub/validation/EventValidationService.java index d1d5723..9c551d9 100644 --- a/src/main/java/ru/practicum/eventhub/domain/validation/EventValidationService.java +++ b/src/main/java/ru/practicum/eventhub/validation/EventValidationService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.validation; +package ru.practicum.eventhub.validation; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -7,7 +7,7 @@ import ru.practicum.eventhub.api.dto.request.EventCreateDto; import ru.practicum.eventhub.api.dto.request.EventUpdateDto; import ru.practicum.eventhub.api.dto.request.TagCreateDto; -import ru.practicum.eventhub.domain.service.impl.TagReadService; +import ru.practicum.eventhub.service.impl.TagReadService; import java.util.Set; diff --git a/src/main/java/ru/practicum/eventhub/domain/validation/ProjectValidationService.java b/src/main/java/ru/practicum/eventhub/validation/ProjectValidationService.java similarity index 87% rename from src/main/java/ru/practicum/eventhub/domain/validation/ProjectValidationService.java rename to src/main/java/ru/practicum/eventhub/validation/ProjectValidationService.java index 45f207b..d8ce37d 100644 --- a/src/main/java/ru/practicum/eventhub/domain/validation/ProjectValidationService.java +++ b/src/main/java/ru/practicum/eventhub/validation/ProjectValidationService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.validation; +package ru.practicum.eventhub.validation; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -6,8 +6,8 @@ import org.springframework.transaction.annotation.Transactional; import ru.practicum.eventhub.api.dto.request.ProjectUpdateDto; import ru.practicum.eventhub.api.exception.ConflictException; -import ru.practicum.eventhub.domain.model.Project; -import ru.practicum.eventhub.domain.service.impl.ProjectReadService; +import ru.practicum.eventhub.model.Project; +import ru.practicum.eventhub.service.impl.ProjectReadService; import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW; diff --git a/src/main/java/ru/practicum/eventhub/domain/validation/TagValidationService.java b/src/main/java/ru/practicum/eventhub/validation/TagValidationService.java similarity index 89% rename from src/main/java/ru/practicum/eventhub/domain/validation/TagValidationService.java rename to src/main/java/ru/practicum/eventhub/validation/TagValidationService.java index e448e2b..6deb22e 100644 --- a/src/main/java/ru/practicum/eventhub/domain/validation/TagValidationService.java +++ b/src/main/java/ru/practicum/eventhub/validation/TagValidationService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.validation; +package ru.practicum.eventhub.validation; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -7,8 +7,8 @@ import ru.practicum.eventhub.api.dto.request.TagCreateDto; import ru.practicum.eventhub.api.dto.request.TagUpdateDto; import ru.practicum.eventhub.api.exception.ConflictException; -import ru.practicum.eventhub.domain.model.Tag; -import ru.practicum.eventhub.domain.service.impl.TagReadService; +import ru.practicum.eventhub.model.Tag; +import ru.practicum.eventhub.service.impl.TagReadService; import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW; diff --git a/src/main/java/ru/practicum/eventhub/domain/validation/UserValidationService.java b/src/main/java/ru/practicum/eventhub/validation/UserValidationService.java similarity index 92% rename from src/main/java/ru/practicum/eventhub/domain/validation/UserValidationService.java rename to src/main/java/ru/practicum/eventhub/validation/UserValidationService.java index 31afe7f..284e966 100644 --- a/src/main/java/ru/practicum/eventhub/domain/validation/UserValidationService.java +++ b/src/main/java/ru/practicum/eventhub/validation/UserValidationService.java @@ -1,4 +1,4 @@ -package ru.practicum.eventhub.domain.validation; +package ru.practicum.eventhub.validation; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -7,9 +7,9 @@ import ru.practicum.eventhub.api.dto.request.UserCreateDto; import ru.practicum.eventhub.api.dto.request.UserUpdateDto; import ru.practicum.eventhub.api.exception.ConflictException; -import ru.practicum.eventhub.domain.model.User; -import ru.practicum.eventhub.domain.service.impl.MetadataReadService; -import ru.practicum.eventhub.domain.service.impl.UserReadService; +import ru.practicum.eventhub.model.User; +import ru.practicum.eventhub.service.impl.MetadataReadService; +import ru.practicum.eventhub.service.impl.UserReadService; import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ece9959..3704ff7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -52,8 +52,8 @@ resilience4j: enableRandomizedWait: true randomizedWaitFactor: 0.5 ignoreExceptions: - - ru.practicum.eventhub.infrastructure.feign.exception.TagAnalyticsClientException - - ru.practicum.eventhub.infrastructure.feign.exception.TagNotFoundException + - ru.practicum.eventhub.config.feign.exception.TagAnalyticsClientException + - ru.practicum.eventhub.config.feign.exception.TagNotFoundException services: tag-analytics: diff --git a/src/main/resources/db/migration/V2__add_cascade_update.sql b/src/main/resources/db/migration/V2__add_cascade_update.sql index e2f01e3..0e220ca 100644 --- a/src/main/resources/db/migration/V2__add_cascade_update.sql +++ b/src/main/resources/db/migration/V2__add_cascade_update.sql @@ -1,4 +1,5 @@ -- Добавление ON UPDATE CASCADE для всех внешних ключей с ON DELETE CASCADE +-- Создание таблицы compensation_actions для хранения информации о компенсационных действиях -- 1. projects.category_id: изменение с RESTRICT на CASCADE с обновлением alter table projects drop constraint if exists projects_category_id_fkey; @@ -35,3 +36,12 @@ alter table event_tags references tags(id) on delete cascade on update cascade; + +CREATE TABLE compensation_actions ( + id UUID PRIMARY KEY default gen_random_uuid(), + action_type VARCHAR(50) NOT NULL, + payload TEXT NOT NULL, + retries INT DEFAULT 0, + last_attempt timestamptz, + created_at timestamptz not null default now() +); diff --git a/src/main/resources/openapi/openapi.yml b/src/main/resources/openapi/openapi.yml index a9841f6..238041f 100644 --- a/src/main/resources/openapi/openapi.yml +++ b/src/main/resources/openapi/openapi.yml @@ -1722,9 +1722,9 @@ components: PageMeta: type: object properties: - page: + pageNumber: type: integer - size: + pageSize: type: integer totalElements: type: integer