From 29dacde870b46023f3d568a963e96ce63f145ac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 15:32:27 +0900 Subject: [PATCH 01/29] =?UTF-8?q?[FEAT]=20redis=EB=A5=BC=20=ED=86=B5?= =?UTF-8?q?=ED=95=9C=20api=20=EC=BA=90=EC=8B=B1=EA=B3=BC=20lock=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/build.gradle | 1 + .../backend/aspect/DistributedLockAspect.java | 65 ++++++++++++++++++ .../backend/aspect/TransactionAspect.java | 14 ++++ .../backend/config/redis/RedisConfig.java | 23 +++++-- .../friend/controller/FriendController.java | 17 +++++ .../friend/dto/response/FriendResponse.java | 2 + .../repository/FriendQueryRepository.java | 2 + .../repository/FriendQueryRepositoryImpl.java | 28 ++++++-- .../domain/friend/service/FriendService.java | 67 ++++++++++++++++++- .../domain/image/service/ImageService.java | 2 + .../domain/location/service/GeoService.java | 4 -- .../backend/domain/member/entity/Member.java | 4 ++ .../domain/member/service/AuthService.java | 8 ++- .../domain/path/service/PathService.java | 4 +- .../domain/place/service/PlaceService.java | 2 +- .../domain/plan/service/PlanService.java | 2 +- .../global/advice/GlobalErrorAdvice.java | 5 ++ .../backend/global/lock/DistributedLock.java | 19 ++++++ .../twtw/backend/utils/SpringELParser.java | 23 +++++++ 19 files changed, 271 insertions(+), 21 deletions(-) create mode 100644 backend/src/main/java/com/twtw/backend/aspect/DistributedLockAspect.java create mode 100644 backend/src/main/java/com/twtw/backend/aspect/TransactionAspect.java create mode 100644 backend/src/main/java/com/twtw/backend/global/lock/DistributedLock.java create mode 100644 backend/src/main/java/com/twtw/backend/utils/SpringELParser.java diff --git a/backend/build.gradle b/backend/build.gradle index 1e52bc65..006477de 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -62,6 +62,7 @@ dependencies { implementation 'org.springframework.cloud:spring-cloud-gcp-storage:1.2.5.RELEASE' implementation 'com.google.firebase:firebase-admin:6.8.1' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' + implementation 'org.redisson:redisson-spring-boot-starter:3.18.0' annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" diff --git a/backend/src/main/java/com/twtw/backend/aspect/DistributedLockAspect.java b/backend/src/main/java/com/twtw/backend/aspect/DistributedLockAspect.java new file mode 100644 index 00000000..d848aab5 --- /dev/null +++ b/backend/src/main/java/com/twtw/backend/aspect/DistributedLockAspect.java @@ -0,0 +1,65 @@ +package com.twtw.backend.aspect; + +import com.twtw.backend.global.lock.DistributedLock; +import com.twtw.backend.utils.SpringELParser; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; + +@Slf4j +@Aspect +@Component +@RequiredArgsConstructor +public class DistributedLockAspect { + private static final String REDISSON_LOCK_PREFIX = "LOCK:"; + private static final String INTERRUPTED_EXCEPTION = "INTERRUPTED EXCEPTION"; + private static final String UNLOCK_EXCEPTION = "UNLOCK EXCEPTION"; + + private final RedissonClient redissonClient; + private final TransactionAspect transactionAspect; + private final SpringELParser springELParser; + + @Around("@annotation(com.twtw.backend.global.lock.DistributedLock)") + public Object lock(final ProceedingJoinPoint joinPoint) throws Throwable { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + DistributedLock distributedLock = method.getAnnotation(DistributedLock.class); + + String key = + REDISSON_LOCK_PREFIX + + springELParser.getDynamicValue( + signature.getParameterNames(), + joinPoint.getArgs(), + distributedLock.name()); + RLock rLock = redissonClient.getLock(key); + + try { + boolean available = + rLock.tryLock( + distributedLock.waitTime(), + distributedLock.leaseTime(), + distributedLock.timeUnit()); + if (!available) { + return false; + } + return transactionAspect.proceed(joinPoint); + } catch (final InterruptedException e) { + log.error(INTERRUPTED_EXCEPTION, e); + throw e; + } finally { + try { + rLock.unlock(); + } catch (final Exception e) { + log.error(UNLOCK_EXCEPTION, e); + } + } + } +} diff --git a/backend/src/main/java/com/twtw/backend/aspect/TransactionAspect.java b/backend/src/main/java/com/twtw/backend/aspect/TransactionAspect.java new file mode 100644 index 00000000..6f33feed --- /dev/null +++ b/backend/src/main/java/com/twtw/backend/aspect/TransactionAspect.java @@ -0,0 +1,14 @@ +package com.twtw.backend.aspect; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Component +public class TransactionAspect { + @Transactional(propagation = Propagation.REQUIRES_NEW) + public Object proceed(final ProceedingJoinPoint joinPoint) throws Throwable { + return joinPoint.proceed(); + } +} diff --git a/backend/src/main/java/com/twtw/backend/config/redis/RedisConfig.java b/backend/src/main/java/com/twtw/backend/config/redis/RedisConfig.java index f2d1d082..b2ac8cd5 100644 --- a/backend/src/main/java/com/twtw/backend/config/redis/RedisConfig.java +++ b/backend/src/main/java/com/twtw/backend/config/redis/RedisConfig.java @@ -1,9 +1,11 @@ package com.twtw.backend.config.redis; import com.twtw.backend.global.properties.RedisProperties; - import lombok.RequiredArgsConstructor; - +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.spring.data.connection.RedissonConnectionFactory; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; @@ -11,7 +13,6 @@ 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.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; @@ -26,11 +27,25 @@ @RequiredArgsConstructor public class RedisConfig { private static final Long TIME_TO_LIVE = 1L; + private static final String REDISSON_HOST_PREFIX = "redis://"; + private static final String URL_DELIMITER = ":"; private final RedisProperties redisProperties; + @Bean + public RedissonClient redissonClient() { + final Config config = new Config(); + config.useSingleServer() + .setAddress( + REDISSON_HOST_PREFIX + + redisProperties.getHost() + + URL_DELIMITER + + redisProperties.getPort()); + return Redisson.create(config); + } + @Bean public RedisConnectionFactory redisConnectionFactory() { - return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort()); + return new RedissonConnectionFactory(redissonClient()); } @Bean diff --git a/backend/src/main/java/com/twtw/backend/domain/friend/controller/FriendController.java b/backend/src/main/java/com/twtw/backend/domain/friend/controller/FriendController.java index f2ade410..9adfd387 100644 --- a/backend/src/main/java/com/twtw/backend/domain/friend/controller/FriendController.java +++ b/backend/src/main/java/com/twtw/backend/domain/friend/controller/FriendController.java @@ -25,12 +25,23 @@ public ResponseEntity> getFriends() { return ResponseEntity.ok(friendService.getFriends()); } + @GetMapping("all/cache") + public ResponseEntity> getFriendsWithCache() { + return ResponseEntity.ok(friendService.getFriendsWithCache()); + } + @GetMapping public ResponseEntity> getFriendsByStatus( @RequestParam final FriendStatus friendStatus) { return ResponseEntity.ok(friendService.getFriendsByStatus(friendStatus)); } + @GetMapping("cache") + public ResponseEntity> getFriendsByStatusWithCache( + @RequestParam final FriendStatus friendStatus) { + return ResponseEntity.ok(friendService.getFriendsByStatusWithCache(friendStatus)); + } + @PostMapping("request") public ResponseEntity addRequest(@RequestBody final FriendRequest friendRequest) { friendService.addRequest(friendRequest); @@ -49,4 +60,10 @@ public ResponseEntity> getFriendByName( @RequestParam final String nickname) { return ResponseEntity.ok(friendService.getFriendByNickname(nickname)); } + + @GetMapping("search/cache") + public ResponseEntity> getFriendByNameWithCache( + @RequestParam final String nickname) { + return ResponseEntity.ok(friendService.getFriendByNicknameWithCache(nickname)); + } } diff --git a/backend/src/main/java/com/twtw/backend/domain/friend/dto/response/FriendResponse.java b/backend/src/main/java/com/twtw/backend/domain/friend/dto/response/FriendResponse.java index 765ed09b..26dff467 100644 --- a/backend/src/main/java/com/twtw/backend/domain/friend/dto/response/FriendResponse.java +++ b/backend/src/main/java/com/twtw/backend/domain/friend/dto/response/FriendResponse.java @@ -3,11 +3,13 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import lombok.ToString; import java.util.UUID; @Getter @Builder +@ToString @AllArgsConstructor public class FriendResponse { private UUID memberId; diff --git a/backend/src/main/java/com/twtw/backend/domain/friend/repository/FriendQueryRepository.java b/backend/src/main/java/com/twtw/backend/domain/friend/repository/FriendQueryRepository.java index ec439135..d3ac0bb2 100644 --- a/backend/src/main/java/com/twtw/backend/domain/friend/repository/FriendQueryRepository.java +++ b/backend/src/main/java/com/twtw/backend/domain/friend/repository/FriendQueryRepository.java @@ -16,4 +16,6 @@ public interface FriendQueryRepository { List findByMemberAndFriendStatus(final Member member, final FriendStatus friendStatus); List findByMemberAndMemberNickname(final Member member, final String nickname); + + boolean existsByTwoMemberId(final UUID loginMemberId, final UUID memberId); } diff --git a/backend/src/main/java/com/twtw/backend/domain/friend/repository/FriendQueryRepositoryImpl.java b/backend/src/main/java/com/twtw/backend/domain/friend/repository/FriendQueryRepositoryImpl.java index c4923af3..bf032f27 100644 --- a/backend/src/main/java/com/twtw/backend/domain/friend/repository/FriendQueryRepositoryImpl.java +++ b/backend/src/main/java/com/twtw/backend/domain/friend/repository/FriendQueryRepositoryImpl.java @@ -1,20 +1,19 @@ package com.twtw.backend.domain.friend.repository; -import static com.twtw.backend.domain.friend.entity.QFriend.friend; - import com.querydsl.jpa.impl.JPAQueryFactory; import com.twtw.backend.domain.friend.entity.Friend; import com.twtw.backend.domain.friend.entity.FriendStatus; import com.twtw.backend.domain.member.entity.Member; - +import jakarta.persistence.LockModeType; import lombok.RequiredArgsConstructor; - import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; import java.util.UUID; +import static com.twtw.backend.domain.friend.entity.QFriend.friend; + @Repository @RequiredArgsConstructor public class FriendQueryRepositoryImpl implements FriendQueryRepository { @@ -82,4 +81,25 @@ public List findByMemberAndMemberNickname(final Member member, final Str nickname))))) .fetch(); } + + @Override + public boolean existsByTwoMemberId(final UUID loginMemberId, final UUID memberId) { + return Optional.ofNullable( + jpaQueryFactory + .selectFrom(friend) + .setLockMode(LockModeType.PESSIMISTIC_READ) + .setHint("javax.persistence.lock.timeout", 3) + .where( + (friend.toMember + .id + .eq(loginMemberId) + .and(friend.fromMember.id.eq(memberId)) + .or( + friend.fromMember + .id + .eq(loginMemberId) + .and(friend.toMember.id.eq(memberId))))) + .fetchFirst()) + .isPresent(); + } } diff --git a/backend/src/main/java/com/twtw/backend/domain/friend/service/FriendService.java b/backend/src/main/java/com/twtw/backend/domain/friend/service/FriendService.java index 3ca9ce80..1a34bbb6 100644 --- a/backend/src/main/java/com/twtw/backend/domain/friend/service/FriendService.java +++ b/backend/src/main/java/com/twtw/backend/domain/friend/service/FriendService.java @@ -15,9 +15,9 @@ import com.twtw.backend.global.constant.NotificationBody; import com.twtw.backend.global.constant.NotificationTitle; import com.twtw.backend.global.exception.EntityNotFoundException; - +import com.twtw.backend.global.lock.DistributedLock; import lombok.RequiredArgsConstructor; - +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -33,15 +33,30 @@ public class FriendService { private final AuthService authService; private final FcmProducer fcmProducer; + @Transactional public void addRequest(final FriendRequest friendRequest) { final Member loginMember = authService.getMemberByJwt(); final Member member = memberService.getMemberById(friendRequest.getMemberId()); - friendRepository.save(friendMapper.toEntity(loginMember, member)); + + createFriendRequestByNicknameOrder(loginMember, member); sendNotification( member.getDeviceTokenValue(), loginMember.getNickname(), loginMember.getId()); } + private void createFriendRequestByNicknameOrder(final Member loginMember, final Member member) { + if (loginMember.hasFasterNickname(member)) { + createFriendRequest(loginMember, member); + return; + } + createFriendRequest(member, loginMember); + } + + @DistributedLock(name = "#fromMember.getNickname().concat(#toMember.getNickname())") + public void createFriendRequest(final Member fromMember, final Member toMember) { + friendRepository.save(friendMapper.toEntity(fromMember, toMember)); + } + private void sendNotification(final String deviceToken, final String nickname, final UUID id) { fcmProducer.sendNotification( new NotificationRequest( @@ -55,6 +70,7 @@ private void sendNotification(final String deviceToken, final String nickname, f public void updateStatus(final FriendUpdateRequest friendUpdateRequest) { final UUID loginMemberId = authService.getMemberByJwt().getId(); final Friend friend = getFriendById(loginMemberId, friendUpdateRequest.getMemberId()); + friend.updateStatus(friendUpdateRequest.getFriendStatus()); } @@ -66,31 +82,76 @@ private Friend getFriendById(final UUID loginMemberId, final UUID memberId) { @Transactional(readOnly = true) public List getFriends() { + return getFriendResponses(); + } + + @Cacheable( + value = "getFriendsWithCache", + key = "'getFriendsWithCache'.concat(#root.target.authService.getMemberIdValue())", + cacheManager = "cacheManager", + unless = "#result.size() <= 0") + @Transactional(readOnly = true) + public List getFriendsWithCache() { + return getFriendResponses(); + } + + private List getFriendResponses() { final Member loginMember = authService.getMemberByJwt(); final List friends = friendRepository.findByMember(loginMember).stream() .map(friend -> friend.getFriendMember(loginMember)) .toList(); + return friendMapper.toResponses(friends); } @Transactional(readOnly = true) public List getFriendsByStatus(final FriendStatus friendStatus) { + return getFriendResponsesByStatus(friendStatus); + } + + @Cacheable( + value = "getFriendsByStatusWithCache", + key = "'getFriendsWithCache'.concat(#root.target.authService.getMemberIdValue()).concat(#friendStatus.name())", + cacheManager = "cacheManager", + unless = "#result.size() <= 0") + @Transactional(readOnly = true) + public List getFriendsByStatusWithCache(final FriendStatus friendStatus) { + return getFriendResponsesByStatus(friendStatus); + } + + private List getFriendResponsesByStatus(final FriendStatus friendStatus) { final Member loginMember = authService.getMemberByJwt(); final List friends = friendRepository.findByMemberAndFriendStatus(loginMember, friendStatus).stream() .map(friend -> friend.getFriendMember(loginMember)) .toList(); + return friendMapper.toResponses(friends); } @Transactional(readOnly = true) public List getFriendByNickname(final String nickname) { + return getFriendResponsesByNickname(nickname); + } + + @Cacheable( + value = "getFriendsByNicknameWithCache", + key = "'getFriendsWithCache'.concat(#root.target.authService.getMemberIdValue()).concat(#nickname)", + cacheManager = "cacheManager", + unless = "#result.size() <= 0") + @Transactional(readOnly = true) + public List getFriendByNicknameWithCache(final String nickname) { + return getFriendResponsesByNickname(nickname); + } + + private List getFriendResponsesByNickname(final String nickname) { final Member loginMember = authService.getMemberByJwt(); final List friends = friendRepository.findByMemberAndMemberNickname(loginMember, nickname).stream() .map(friend -> friend.getFriendMember(loginMember)) .toList(); + return friendMapper.toResponses(friends); } } diff --git a/backend/src/main/java/com/twtw/backend/domain/image/service/ImageService.java b/backend/src/main/java/com/twtw/backend/domain/image/service/ImageService.java index 1988f68d..4bd52dbb 100644 --- a/backend/src/main/java/com/twtw/backend/domain/image/service/ImageService.java +++ b/backend/src/main/java/com/twtw/backend/domain/image/service/ImageService.java @@ -11,6 +11,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; @@ -25,6 +26,7 @@ public class ImageService { private final Storage storage; private final StorageProperties storageProperties; + @Transactional public ImageResponse uploadImage(final MultipartFile image) throws IOException { final Member member = authService.getMemberByJwt(); final String contentType = image.getContentType(); diff --git a/backend/src/main/java/com/twtw/backend/domain/location/service/GeoService.java b/backend/src/main/java/com/twtw/backend/domain/location/service/GeoService.java index e47f8721..a8d8ce17 100644 --- a/backend/src/main/java/com/twtw/backend/domain/location/service/GeoService.java +++ b/backend/src/main/java/com/twtw/backend/domain/location/service/GeoService.java @@ -5,15 +5,12 @@ import com.twtw.backend.domain.location.dto.request.LocationRequest; import com.twtw.backend.domain.location.dto.response.AverageCoordinate; import com.twtw.backend.domain.member.entity.Member; - import lombok.RequiredArgsConstructor; - import org.springframework.data.geo.Distance; import org.springframework.data.geo.Metrics; import org.springframework.data.geo.Point; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -24,7 +21,6 @@ public class GeoService { private static final Distance DEFAULT_DISTANCE = new Distance(0, Metrics.KILOMETERS); private final RedisTemplate redisTemplate; - @Transactional public AverageCoordinate saveLocation( final Group group, final Member member, final LocationRequest locationRequest) { final String groupId = group.getId().toString(); diff --git a/backend/src/main/java/com/twtw/backend/domain/member/entity/Member.java b/backend/src/main/java/com/twtw/backend/domain/member/entity/Member.java index 80f7d46a..f33fa8f6 100644 --- a/backend/src/main/java/com/twtw/backend/domain/member/entity/Member.java +++ b/backend/src/main/java/com/twtw/backend/domain/member/entity/Member.java @@ -86,4 +86,8 @@ public List getGroupMembers() { public void removeGroupMember(final GroupMember groupMember) { this.groupMembers.remove(groupMember); } + + public boolean hasFasterNickname(final Member member) { + return this.nickname.compareTo(member.nickname) < 0; + } } diff --git a/backend/src/main/java/com/twtw/backend/domain/member/service/AuthService.java b/backend/src/main/java/com/twtw/backend/domain/member/service/AuthService.java index c13d4719..02d482e7 100644 --- a/backend/src/main/java/com/twtw/backend/domain/member/service/AuthService.java +++ b/backend/src/main/java/com/twtw/backend/domain/member/service/AuthService.java @@ -133,13 +133,17 @@ public TokenDto saveRefreshToken(Authentication authentication, String userName) } public Member getMemberByJwt() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String memberIdValue = getMemberIdValue(); - UUID id = UUID.fromString(authentication.getName()); + UUID id = UUID.fromString(memberIdValue); return memberRepository.findById(id).orElseThrow(EntityNotFoundException::new); } + public String getMemberIdValue() { + return SecurityContextHolder.getContext().getAuthentication().getName(); + } + @Transactional public void updateDeviceToken(DeviceTokenRequest request) { Member member = getMemberByJwt(); diff --git a/backend/src/main/java/com/twtw/backend/domain/path/service/PathService.java b/backend/src/main/java/com/twtw/backend/domain/path/service/PathService.java index a17d29cc..fea101eb 100644 --- a/backend/src/main/java/com/twtw/backend/domain/path/service/PathService.java +++ b/backend/src/main/java/com/twtw/backend/domain/path/service/PathService.java @@ -23,7 +23,7 @@ public PathService( @Cacheable( value = "carPath", - key = "{#request}", + key = "'searchCarPath'.concat(#request)", cacheManager = "cacheManager", unless = "#result.code != 0") public SearchCarPathResponse searchCarPath(final SearchCarPathRequest request) { @@ -32,7 +32,7 @@ public SearchCarPathResponse searchCarPath(final SearchCarPathRequest request) { @Cacheable( value = "pedPath", - key = "{#request}", + key = "'searchPedPath'.concat(#request)", cacheManager = "cacheManager", unless = "#result.features.size() <= 0") public SearchPedPathResponse searchPedPath(final SearchPedPathRequest request) { diff --git a/backend/src/main/java/com/twtw/backend/domain/place/service/PlaceService.java b/backend/src/main/java/com/twtw/backend/domain/place/service/PlaceService.java index 634cab76..2422cd4c 100644 --- a/backend/src/main/java/com/twtw/backend/domain/place/service/PlaceService.java +++ b/backend/src/main/java/com/twtw/backend/domain/place/service/PlaceService.java @@ -21,7 +21,7 @@ public class PlaceService { @Cacheable( value = "surroundPlace", - key = "{#surroundPlaceRequest}", + key = "'searchSurroundPlace'.concat(#surroundPlaceRequest)", cacheManager = "cacheManager", unless = "#result.results.size() <= 0") public PlaceResponse searchSurroundPlace(final SurroundPlaceRequest surroundPlaceRequest) { diff --git a/backend/src/main/java/com/twtw/backend/domain/plan/service/PlanService.java b/backend/src/main/java/com/twtw/backend/domain/plan/service/PlanService.java index f1c5b9ff..5d6ab3ba 100644 --- a/backend/src/main/java/com/twtw/backend/domain/plan/service/PlanService.java +++ b/backend/src/main/java/com/twtw/backend/domain/plan/service/PlanService.java @@ -56,7 +56,7 @@ public class PlanService { @Cacheable( value = "planDestination", - key = "{#request}", + key = "'searchPlanDestination'.concat(#request)", cacheManager = "cacheManager", unless = "#result.results.size() <= 0") public PlanDestinationResponse searchPlanDestination(final SearchDestinationRequest request) { diff --git a/backend/src/main/java/com/twtw/backend/global/advice/GlobalErrorAdvice.java b/backend/src/main/java/com/twtw/backend/global/advice/GlobalErrorAdvice.java index 50631494..e7deb1ac 100644 --- a/backend/src/main/java/com/twtw/backend/global/advice/GlobalErrorAdvice.java +++ b/backend/src/main/java/com/twtw/backend/global/advice/GlobalErrorAdvice.java @@ -25,4 +25,9 @@ public ResponseEntity entityNotFound(final EntityNotFoundExceptio public ResponseEntity authority(final AuthorityException e) { return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); } + + @ExceptionHandler(InterruptedException.class) + public ResponseEntity interrupted(final InterruptedException e) { + return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); + } } diff --git a/backend/src/main/java/com/twtw/backend/global/lock/DistributedLock.java b/backend/src/main/java/com/twtw/backend/global/lock/DistributedLock.java new file mode 100644 index 00000000..832ca366 --- /dev/null +++ b/backend/src/main/java/com/twtw/backend/global/lock/DistributedLock.java @@ -0,0 +1,19 @@ +package com.twtw.backend.global.lock; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.TimeUnit; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface DistributedLock { + String name(); + + TimeUnit timeUnit() default TimeUnit.SECONDS; + + long waitTime() default 3L; + + long leaseTime() default 5L; +} diff --git a/backend/src/main/java/com/twtw/backend/utils/SpringELParser.java b/backend/src/main/java/com/twtw/backend/utils/SpringELParser.java new file mode 100644 index 00000000..66c0656e --- /dev/null +++ b/backend/src/main/java/com/twtw/backend/utils/SpringELParser.java @@ -0,0 +1,23 @@ +package com.twtw.backend.utils; + +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.stereotype.Component; + +@Component +public class SpringELParser { + + private static final ExpressionParser PARSER = new SpelExpressionParser(); + private static final Integer START_INDEX = 0; + + public Object getDynamicValue(final String[] parameterNames, final Object[] args, final String key) { + final StandardEvaluationContext context = new StandardEvaluationContext(); + + for (int i = START_INDEX; i < parameterNames.length; i++) { + context.setVariable(parameterNames[i], args[i]); + } + + return PARSER.parseExpression(key).getValue(context, Object.class); + } +} From d97565cc66d2726ed3137e6f5757d03e02cab314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 18:06:51 +0900 Subject: [PATCH 02/29] =?UTF-8?q?[TEST]=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BB=A8=ED=85=8C=EC=9D=B4=EB=84=88=20redis=EC=99=80=20rabbitm?= =?UTF-8?q?q=EC=97=90=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/backend-cd.yml | 14 +- backend/build.gradle | 7 + backend/src/docs/friend.adoc | 9 ++ .../config/rabbitmq/RabbitMQConfig.java | 6 +- .../src/main/resources/application-dev.yml | 13 +- .../src/main/resources/application-prod.yml | 9 +- .../main/resources/db/migration/V1__init.sql | 129 ++++++++++++++++++ .../twtw/backend/BackendApplicationTests.java | 7 +- .../controller/FriendControllerTest.java | 113 +++++++++++++-- .../repository/MemberRepositoryTest.java | 8 +- .../member/service/AuthServiceTest.java | 7 +- .../support/database/ResetDatabase.java | 17 +++ .../backend/support/exclude/ExcludeTest.java | 34 ++--- .../support/repository/RepositoryTest.java | 16 ++- .../backend/support/service/LoginTest.java | 25 +++- .../support/testcontainer/ContainerTest.java | 37 +++++ .../{application-test.yml => application.yml} | 29 ++-- 17 files changed, 384 insertions(+), 96 deletions(-) create mode 100644 backend/src/main/resources/db/migration/V1__init.sql create mode 100644 backend/src/test/java/com/twtw/backend/support/database/ResetDatabase.java create mode 100644 backend/src/test/java/com/twtw/backend/support/testcontainer/ContainerTest.java rename backend/src/test/resources/{application-test.yml => application.yml} (54%) diff --git a/.github/workflows/backend-cd.yml b/.github/workflows/backend-cd.yml index 8fe98480..f7271193 100644 --- a/.github/workflows/backend-cd.yml +++ b/.github/workflows/backend-cd.yml @@ -1,11 +1,11 @@ name: TWTW Backend CD -on: - workflow_run: - branches: - - "master" - workflows: ["TWTW Backend CI", "TWTW Nginx Build"] - types: - - completed +# on: +# workflow_run: +# branches: +# - "master" +# workflows: ["TWTW Backend CI", "TWTW Nginx Build"] +# types: +# - completed jobs: server-deploy: diff --git a/backend/build.gradle b/backend/build.gradle index 006477de..7767ca5c 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -57,6 +57,9 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + implementation 'org.flywaydb:flyway-core' + implementation 'org.flywaydb:flyway-mysql' + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' implementation 'org.springframework.cloud:spring-cloud-gcp-starter:1.2.5.RELEASE' implementation 'org.springframework.cloud:spring-cloud-gcp-storage:1.2.5.RELEASE' @@ -83,6 +86,10 @@ dependencies { testImplementation 'org.springframework.amqp:spring-rabbit-test' testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' testImplementation 'org.springframework.security:spring-security-test' + testImplementation "org.testcontainers:junit-jupiter:1.19.7" + testImplementation "org.testcontainers:junit-jupiter:1.19.7" + testImplementation "com.redis:testcontainers-redis:2.0.1" + testImplementation "org.testcontainers:rabbitmq:1.19.7" testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' } diff --git a/backend/src/docs/friend.adoc b/backend/src/docs/friend.adoc index f6049e48..bb323193 100644 --- a/backend/src/docs/friend.adoc +++ b/backend/src/docs/friend.adoc @@ -14,8 +14,17 @@ operation::post update friend status[snippets='http-request,http-response'] === 친구 검색 operation::get search friend[snippets='http-request,http-response'] +=== 친구 검색 cache +operation::get search friend with cache[snippets='http-request,http-response'] + === 친구 전체 조회 operation::get all friends[snippets='http-request,http-response'] +=== 친구 전체 조회 cache +operation::get all friends with cache[snippets='http-request,http-response'] + === 친구 상태별 조회 operation::get friends by status[snippets='http-request,http-response'] + +=== 친구 상태별 조회 cache +operation::get friends by status with cache[snippets='http-request,http-response'] diff --git a/backend/src/main/java/com/twtw/backend/config/rabbitmq/RabbitMQConfig.java b/backend/src/main/java/com/twtw/backend/config/rabbitmq/RabbitMQConfig.java index 07e3b5be..0cfc5360 100644 --- a/backend/src/main/java/com/twtw/backend/config/rabbitmq/RabbitMQConfig.java +++ b/backend/src/main/java/com/twtw/backend/config/rabbitmq/RabbitMQConfig.java @@ -3,9 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.twtw.backend.global.constant.RabbitMQConstant; import com.twtw.backend.global.properties.RabbitMQProperties; - import lombok.RequiredArgsConstructor; - import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.Queue; @@ -18,9 +16,7 @@ import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -@Profile("!test") @EnableRabbit @Configuration @RequiredArgsConstructor @@ -80,7 +76,7 @@ public Jackson2JsonMessageConverter jsonMessageConverter() { @Bean public RabbitTemplate rabbitTemplate() { - RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory()); + final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory()); rabbitTemplate.setMessageConverter(jsonMessageConverter()); return rabbitTemplate; } diff --git a/backend/src/main/resources/application-dev.yml b/backend/src/main/resources/application-dev.yml index 18685b7c..f3679413 100644 --- a/backend/src/main/resources/application-dev.yml +++ b/backend/src/main/resources/application-dev.yml @@ -6,13 +6,12 @@ spring: driver-class-name: org.mariadb.jdbc.Driver url: jdbc:mariadb://localhost:3306/TWTW username: root - password: root! + password: 1234 jpa: database: mysql - generate-ddl: true show-sql: true hibernate: - ddl-auto: create + ddl-auto: none properties: hibernate: auto_quote_keyword: true @@ -33,4 +32,10 @@ spring: host: localhost port: 6379 jackson: - default-property-inclusion: non_null \ No newline at end of file + default-property-inclusion: non_null + flyway: + url: jdbc:mariadb://localhost:3306/TWTW + enabled: true + baseline-on-migrate: false + user: root + password: 1234 diff --git a/backend/src/main/resources/application-prod.yml b/backend/src/main/resources/application-prod.yml index d93d8060..238a9581 100644 --- a/backend/src/main/resources/application-prod.yml +++ b/backend/src/main/resources/application-prod.yml @@ -9,10 +9,9 @@ spring: password: ${MYSQL_PASSWORD} jpa: database: mysql - generate-ddl: true show-sql: true hibernate: - ddl-auto: update + ddl-auto: none properties: hibernate: auto_quote_keyword: true @@ -43,6 +42,12 @@ spring: project-id: ${PROJECT_ID} bucket: ${BUCKET_NAME} storage-url: ${BUCKET_URL} + flyway: + url: jdbc:mariadb://${MYSQL_HOST}:${MYSQL_PORT}/${MYSQL_DATABASE}?serverTimezone=Asia/Seoul + enabled: true + baseline-on-migrate: false + user: ${MYSQL_USER} + password: ${MYSQL_PASSWORD} management: endpoints: web: diff --git a/backend/src/main/resources/db/migration/V1__init.sql b/backend/src/main/resources/db/migration/V1__init.sql new file mode 100644 index 00000000..d6f7c524 --- /dev/null +++ b/backend/src/main/resources/db/migration/V1__init.sql @@ -0,0 +1,129 @@ +create table `group` +( + created_at datetime(6), + deleted_at datetime(6), + updated_at datetime(6), + id BINARY(16) not null, + leader_id binary(16), + group_image varchar(255), + name varchar(255), + primary key (id) +); + +create table `member` +( + created_at datetime(6), + deleted_at datetime(6), + updated_at datetime(6), + device_token_id BINARY(16), + id BINARY(16) not null, + auth_type enum ('APPLE','KAKAO'), + nickname varchar(255) not null, + profile_image varchar(255), + role enum ('ROLE_ADMIN','ROLE_USER'), + client_id tinytext not null, + primary key (id) +); +create table device_token +( + created_at datetime(6), + deleted_at datetime(6), + updated_at datetime(6), + id BINARY(16) not null, + device_token varchar(255), + primary key (id) +); +create table friend +( + created_at datetime(6), + deleted_at datetime(6), + updated_at datetime(6), + `from_member_id` BINARY(16), + id BINARY(16) not null, + `to_member_id` BINARY(16), + friend_status enum ('ACCEPTED','BLOCKED','DENIED','EXPIRED','REQUESTED'), + primary key (id) +); +create table group_member +( + is_share bit, + is_shared_once bit, + latitude float(53), + longitude float(53), + created_at datetime(6), + deleted_at datetime(6), + updated_at datetime(6), + group_id BINARY(16), + id BINARY(16) not null, + member_id BINARY(16), + group_invite_code enum ('ACCEPTED','BLOCKED','DENIED','EXPIRED','REQUESTED'), + primary key (id) +); +create table place +( + latitude float(53), + longitude float(53), + created_at datetime(6), + deleted_at datetime(6), + updated_at datetime(6), + id BINARY(16) not null, + place_name varchar(255) not null, + place_url varchar(255), + road_address_name varchar(255), + primary key (id) +); +create table plan +( + created_at datetime(6), + deleted_at datetime(6), + plan_day datetime(6), + updated_at datetime(6), + group_id BINARY(16), + id BINARY(16) not null, + place_id BINARY(16), + name varchar(255) not null, + primary key (id) +); +create table plan_member +( + is_plan_maker bit, + created_at datetime(6), + deleted_at datetime(6), + updated_at datetime(6), + id BINARY(16) not null, + `member_id` BINARY(16), + plan_id BINARY(16), + plan_invite_code enum ('ACCEPTED','DENIED','EXPIRED','REQUESTED'), + primary key (id) +); +create table refresh_token +( + id BINARY(16) not null, + token_key varchar(255), + token_value varchar(255), + primary key (id) +); +alter table `member` + add constraint unique_device_token_id unique (device_token_id); +alter table `member` + add constraint unique_member_nickname unique (nickname); +alter table plan + add constraint unique_plan_place_id unique (place_id); +alter table `member` + add constraint fk_member_device_token_id foreign key (device_token_id) references device_token (id); +alter table friend + add constraint fk_friend_from_member_id foreign key (`from_member_id`) references `member` (id); +alter table friend + add constraint fk_friend_to_member_id foreign key (`to_member_id`) references `member` (id); +alter table group_member + add constraint fk_group_member_group_id foreign key (group_id) references `group` (id); +alter table group_member + add constraint fk_group_member_member_id foreign key (member_id) references `member` (id); +alter table plan + add constraint fk_plan_group_id foreign key (group_id) references `group` (id); +alter table plan + add constraint fk_plan_place_id foreign key (place_id) references place (id); +alter table plan_member + add constraint fk_plan_member_member_id foreign key (`member_id`) references `member` (id); +alter table plan_member + add constraint fk_plan_member_plan_id foreign key (plan_id) references plan (id); diff --git a/backend/src/test/java/com/twtw/backend/BackendApplicationTests.java b/backend/src/test/java/com/twtw/backend/BackendApplicationTests.java index 1490f64e..e768581a 100644 --- a/backend/src/test/java/com/twtw/backend/BackendApplicationTests.java +++ b/backend/src/test/java/com/twtw/backend/BackendApplicationTests.java @@ -1,12 +1,13 @@ package com.twtw.backend; -import com.twtw.backend.support.exclude.ExcludeTest; - +import com.twtw.backend.support.testcontainer.ContainerTest; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; @SpringBootTest -class BackendApplicationTests extends ExcludeTest { +@ContextConfiguration(initializers = ContainerTest.class) +class BackendApplicationTests { @Test void contextLoads() {} diff --git a/backend/src/test/java/com/twtw/backend/domain/friend/controller/FriendControllerTest.java b/backend/src/test/java/com/twtw/backend/domain/friend/controller/FriendControllerTest.java index 82a550e6..ec82b4d5 100644 --- a/backend/src/test/java/com/twtw/backend/domain/friend/controller/FriendControllerTest.java +++ b/backend/src/test/java/com/twtw/backend/domain/friend/controller/FriendControllerTest.java @@ -1,25 +1,11 @@ package com.twtw.backend.domain.friend.controller; -import static com.twtw.backend.support.docs.ApiDocsUtils.getDocumentRequest; -import static com.twtw.backend.support.docs.ApiDocsUtils.getDocumentResponse; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willDoNothing; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - import com.twtw.backend.domain.friend.dto.request.FriendRequest; import com.twtw.backend.domain.friend.dto.request.FriendUpdateRequest; import com.twtw.backend.domain.friend.dto.response.FriendResponse; import com.twtw.backend.domain.friend.entity.FriendStatus; import com.twtw.backend.domain.friend.service.FriendService; import com.twtw.backend.support.docs.RestDocsTest; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; @@ -30,6 +16,18 @@ import java.util.List; import java.util.UUID; +import static com.twtw.backend.support.docs.ApiDocsUtils.getDocumentRequest; +import static com.twtw.backend.support.docs.ApiDocsUtils.getDocumentResponse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + @DisplayName("FriendController의") @WebMvcTest(FriendController.class) class FriendControllerTest extends RestDocsTest { @@ -62,6 +60,33 @@ void getFriends() throws Exception { .andDo(document("get all friends", getDocumentRequest(), getDocumentResponse())); } + @Test + @DisplayName("친구 전체 조회 캐시 API가 수행되는가") + void getFriendsWithCache() throws Exception { + // given + final List expected = + List.of( + new FriendResponse(UUID.randomUUID(), "정해진", "http://hojiniSelfie"), + new FriendResponse(UUID.randomUUID(), "주어진", "http://hojiniSelfCamera")); + given(friendService.getFriendsWithCache()).willReturn(expected); + + // when + final ResultActions perform = + mockMvc.perform( + get("/friends/all/cache") + .contentType(MediaType.APPLICATION_JSON) + .header( + "Authorization", + "Bearer wefa3fsdczf32.gaoiuergf92.gb5hsa2jgh")); + + // then + perform.andExpect(status().isOk()).andExpect(jsonPath("$").isArray()); + + // docs + perform.andDo(print()) + .andDo(document("get all friends with cache", getDocumentRequest(), getDocumentResponse())); + } + @Test @DisplayName("친구 상태별 조회 API가 수행되는가") void getFriendsByStatus() throws Exception { @@ -93,6 +118,37 @@ void getFriendsByStatus() throws Exception { getDocumentResponse())); } + @Test + @DisplayName("친구 상태별 조회 캐시 API가 수행되는가") + void getFriendsByStatusWithCache() throws Exception { + // given + final List expected = + List.of( + new FriendResponse(UUID.randomUUID(), "호전", "http://HJ39Selfie"), + new FriendResponse(UUID.randomUUID(), "후진", "http://HJ39SelfCamera")); + given(friendService.getFriendsByStatusWithCache(any())).willReturn(expected); + + // when + final ResultActions perform = + mockMvc.perform( + get("/friends/cache?friendStatus=REQUESTED") + .contentType(MediaType.APPLICATION_JSON) + .header( + "Authorization", + "Bearer wefa3fsdczf32.gaoiuergf92.gb5hsa2jgh")); + + // then + perform.andExpect(status().isOk()).andExpect(jsonPath("$").isArray()); + + // docs + perform.andDo(print()) + .andDo( + document( + "get friends by status with cache", + getDocumentRequest(), + getDocumentResponse())); + } + @Test @DisplayName("친구 신청 API가 수행되는가") void addRequest() throws Exception { @@ -153,7 +209,7 @@ void updateStatus() throws Exception { } @Test - @DisplayName("친구 이름으로 검색 API가 수행되는가") + @DisplayName("친구 닉네임으로 검색 API가 수행되는가") void getFriendByName() throws Exception { // given final List expected = @@ -178,4 +234,31 @@ void getFriendByName() throws Exception { perform.andDo(print()) .andDo(document("get search friend", getDocumentRequest(), getDocumentResponse())); } + + @Test + @DisplayName("친구 닉네임으로 검색 캐시 API가 수행되는가") + void getFriendByNameWithCache() throws Exception { + // given + final List expected = + List.of( + new FriendResponse(UUID.randomUUID(), "호진정", "http://hojiniSelfie"), + new FriendResponse(UUID.randomUUID(), "진정해", "http://hojiniSelfCamera")); + given(friendService.getFriendByNicknameWithCache(any())).willReturn(expected); + + // when + final ResultActions perform = + mockMvc.perform( + get("/friends/search/cache?nickname=hojin") + .contentType(MediaType.APPLICATION_JSON) + .header( + "Authorization", + "Bearer wefa3fsdczf32.gaoiuergf92.gb5hsa2jgh")); + + // then + perform.andExpect(status().isOk()).andExpect(jsonPath("$").isArray()); + + // docs + perform.andDo(print()) + .andDo(document("get search friend with cache", getDocumentRequest(), getDocumentResponse())); + } } diff --git a/backend/src/test/java/com/twtw/backend/domain/member/repository/MemberRepositoryTest.java b/backend/src/test/java/com/twtw/backend/domain/member/repository/MemberRepositoryTest.java index fe16e0d8..4d7a2843 100644 --- a/backend/src/test/java/com/twtw/backend/domain/member/repository/MemberRepositoryTest.java +++ b/backend/src/test/java/com/twtw/backend/domain/member/repository/MemberRepositoryTest.java @@ -1,21 +1,19 @@ package com.twtw.backend.domain.member.repository; -import static org.assertj.core.api.Assertions.assertThat; - import com.twtw.backend.domain.member.dto.request.DeviceTokenRequest; import com.twtw.backend.domain.member.entity.DeviceToken; import com.twtw.backend.domain.member.entity.Member; import com.twtw.backend.fixture.member.MemberEntityFixture; import com.twtw.backend.support.repository.RepositoryTest; - import jakarta.persistence.EntityManager; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import java.util.UUID; +import static org.assertj.core.api.Assertions.assertThat; + @DisplayName("MemberRepository의") class MemberRepositoryTest extends RepositoryTest { @@ -46,8 +44,6 @@ void softDelete() { // when memberRepository.deleteById(memberId); - em.flush(); - em.clear(); // then assertThat(memberRepository.findById(memberId)).isEmpty(); diff --git a/backend/src/test/java/com/twtw/backend/domain/member/service/AuthServiceTest.java b/backend/src/test/java/com/twtw/backend/domain/member/service/AuthServiceTest.java index 1e5cb141..2c6fa5bc 100644 --- a/backend/src/test/java/com/twtw/backend/domain/member/service/AuthServiceTest.java +++ b/backend/src/test/java/com/twtw/backend/domain/member/service/AuthServiceTest.java @@ -1,7 +1,5 @@ package com.twtw.backend.domain.member.service; -import static org.assertj.core.api.Assertions.assertThat; - import com.twtw.backend.domain.member.dto.request.MemberSaveRequest; import com.twtw.backend.domain.member.dto.request.OAuthRequest; import com.twtw.backend.domain.member.dto.response.AfterLoginResponse; @@ -12,14 +10,15 @@ import com.twtw.backend.fixture.member.MemberEntityFixture; import com.twtw.backend.support.database.DatabaseTest; import com.twtw.backend.support.exclude.ExcludeTest; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import static org.assertj.core.api.Assertions.assertThat; + @DatabaseTest @DisplayName("AuthService의 ") -public class AuthServiceTest extends ExcludeTest { +class AuthServiceTest extends ExcludeTest { @Autowired private AuthService authService; diff --git a/backend/src/test/java/com/twtw/backend/support/database/ResetDatabase.java b/backend/src/test/java/com/twtw/backend/support/database/ResetDatabase.java new file mode 100644 index 00000000..6a699938 --- /dev/null +++ b/backend/src/test/java/com/twtw/backend/support/database/ResetDatabase.java @@ -0,0 +1,17 @@ +package com.twtw.backend.support.database; + +import org.flywaydb.core.Flyway; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +public class ResetDatabase { + + @Autowired + private Flyway flyway; + + public void reset() { + flyway.clean(); + flyway.migrate(); + } +} diff --git a/backend/src/test/java/com/twtw/backend/support/exclude/ExcludeTest.java b/backend/src/test/java/com/twtw/backend/support/exclude/ExcludeTest.java index 854f8bb2..29a8811d 100644 --- a/backend/src/test/java/com/twtw/backend/support/exclude/ExcludeTest.java +++ b/backend/src/test/java/com/twtw/backend/support/exclude/ExcludeTest.java @@ -1,37 +1,27 @@ package com.twtw.backend.support.exclude; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; - -import com.twtw.backend.config.rabbitmq.RabbitMQConfig; -import com.twtw.backend.domain.location.controller.LocationController; import com.twtw.backend.domain.notification.messagequeue.FcmProducer; - +import com.twtw.backend.support.database.ResetDatabase; +import com.twtw.backend.support.testcontainer.ContainerTest; import org.junit.jupiter.api.BeforeEach; -import org.springframework.amqp.rabbit.core.RabbitAdmin; -import org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthContributorAutoConfiguration; -import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.test.context.ActiveProfiles; - -@ActiveProfiles("test") -public abstract class ExcludeTest { +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ContextConfiguration; - @MockBean private RabbitAutoConfiguration rabbitAutoConfiguration; - - @MockBean private RabbitMQConfig rabbitMQConfig; - - @MockBean private RabbitAdmin rabbitAdmin; - - @MockBean - private RabbitHealthContributorAutoConfiguration rabbitHealthContributorAutoConfiguration; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; - @MockBean private LocationController locationController; +@Import(ResetDatabase.class) +@ContextConfiguration(initializers = ContainerTest.class) +public abstract class ExcludeTest { @MockBean private FcmProducer fcmProducer; + @Autowired private ResetDatabase resetDatabase; @BeforeEach void setUp() { doNothing().when(fcmProducer).sendNotification(any()); + resetDatabase.reset(); } } diff --git a/backend/src/test/java/com/twtw/backend/support/repository/RepositoryTest.java b/backend/src/test/java/com/twtw/backend/support/repository/RepositoryTest.java index 5ddbfa85..794b3228 100644 --- a/backend/src/test/java/com/twtw/backend/support/repository/RepositoryTest.java +++ b/backend/src/test/java/com/twtw/backend/support/repository/RepositoryTest.java @@ -1,6 +1,18 @@ package com.twtw.backend.support.repository; -import com.twtw.backend.support.exclude.ExcludeTest; +import com.twtw.backend.support.database.ResetDatabase; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; @EnableDataJpa -public abstract class RepositoryTest extends ExcludeTest {} +@Import(ResetDatabase.class) +public abstract class RepositoryTest { + + @Autowired private ResetDatabase resetDatabase; + + @BeforeEach + public void tearDown() { + resetDatabase.reset(); + } +} diff --git a/backend/src/test/java/com/twtw/backend/support/service/LoginTest.java b/backend/src/test/java/com/twtw/backend/support/service/LoginTest.java index 34ea8390..5722bbe8 100644 --- a/backend/src/test/java/com/twtw/backend/support/service/LoginTest.java +++ b/backend/src/test/java/com/twtw/backend/support/service/LoginTest.java @@ -1,30 +1,45 @@ package com.twtw.backend.support.service; -import static org.mockito.Mockito.when; - import com.twtw.backend.domain.member.entity.Member; import com.twtw.backend.domain.member.repository.MemberRepository; import com.twtw.backend.domain.member.service.AuthService; +import com.twtw.backend.domain.notification.messagequeue.FcmProducer; import com.twtw.backend.fixture.member.MemberEntityFixture; import com.twtw.backend.support.database.DatabaseTest; -import com.twtw.backend.support.exclude.ExcludeTest; - +import com.twtw.backend.support.database.ResetDatabase; +import com.twtw.backend.support.testcontainer.ContainerTest; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ContextConfiguration; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; @DatabaseTest -public abstract class LoginTest extends ExcludeTest { +@Import(ResetDatabase.class) +@ContextConfiguration(initializers = ContainerTest.class) +public abstract class LoginTest { @MockBean protected AuthService authService; + @MockBean protected FcmProducer fcmProducer; @Autowired protected MemberRepository memberRepository; + @Autowired private ResetDatabase resetDatabase; protected Member loginUser; @BeforeEach public void setup() { + resetDatabase.reset(); final Member member = MemberEntityFixture.LOGIN_MEMBER.toEntity(); loginUser = memberRepository.save(member); when(authService.getMemberByJwt()).thenReturn(loginUser); } + + @BeforeEach + void setUp() { + doNothing().when(fcmProducer).sendNotification(any()); + } } diff --git a/backend/src/test/java/com/twtw/backend/support/testcontainer/ContainerTest.java b/backend/src/test/java/com/twtw/backend/support/testcontainer/ContainerTest.java new file mode 100644 index 00000000..da452958 --- /dev/null +++ b/backend/src/test/java/com/twtw/backend/support/testcontainer/ContainerTest.java @@ -0,0 +1,37 @@ +package com.twtw.backend.support.testcontainer; + +import com.redis.testcontainers.RedisContainer; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.testcontainers.containers.RabbitMQContainer; +import org.testcontainers.utility.DockerImageName; + +public class ContainerTest implements + ApplicationContextInitializer { + private static final RedisContainer REDIS_CONTAINER = + new RedisContainer(DockerImageName.parse("redis:latest")) + .withExposedPorts(6379); + + private static final RabbitMQContainer RABBIT_MQ_CONTAINER = + new RabbitMQContainer(DockerImageName.parse("rabbitmq:3-management-alpine")) + .withExposedPorts(15672, 5672, 61613) + .withPluginsEnabled("rabbitmq_web_stomp"); + + static { + REDIS_CONTAINER.start(); + RABBIT_MQ_CONTAINER.start(); + } + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + TestPropertyValues.of( + "spring.data.redis.host=" + REDIS_CONTAINER.getHost(), + "spring.data.redis.port=" + REDIS_CONTAINER.getFirstMappedPort(), + "spring.rabbitmq.host=" + RABBIT_MQ_CONTAINER.getHost(), + "spring.rabbitmq.port=" + RABBIT_MQ_CONTAINER.getAmqpPort(), + "spring.rabbitmq.username=guest", + "spring.rabbitmq.password=guest" + ).applyTo(applicationContext.getEnvironment()); + } +} \ No newline at end of file diff --git a/backend/src/test/resources/application-test.yml b/backend/src/test/resources/application.yml similarity index 54% rename from backend/src/test/resources/application-test.yml rename to backend/src/test/resources/application.yml index 28a9cc74..3148e2c7 100644 --- a/backend/src/test/resources/application-test.yml +++ b/backend/src/test/resources/application.yml @@ -24,7 +24,7 @@ spring: jpa: hibernate: - ddl-auto: create + ddl-auto: none properties: hibernate: auto_quote_keyword: true @@ -32,26 +32,13 @@ spring: storage_engine: innodb format_sql: true show-sql: true - generate-ddl: true h2: console: enabled: true - rabbitmq: - listener: - simple: - auto-startup: false - direct: - auto-startup: false - stream: - auto-startup: false - port: 5672 - host: localhost - autoconfigure: - exclude: - - org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration - - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration - - org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration - data: - redis: - host: redis - port: 6379 + flyway: + url: jdbc:h2:mem:test;DATABASE_TO_UPPER=FALSE;MODE=MySQL; + baseline-on-migrate: true + enabled: true + user: sa + password: + clean-disabled: false From c93aed7800689adb8c383fa4cbe637ba4c6dc480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 18:08:01 +0900 Subject: [PATCH 03/29] =?UTF-8?q?[CHORE]=20github=20actions=20=EB=8B=A8?= =?UTF-8?q?=EA=B3=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/backend-cd.yml | 52 ++++++++++++++++---------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/.github/workflows/backend-cd.yml b/.github/workflows/backend-cd.yml index f7271193..a913b57f 100644 --- a/.github/workflows/backend-cd.yml +++ b/.github/workflows/backend-cd.yml @@ -1,11 +1,11 @@ name: TWTW Backend CD -# on: -# workflow_run: -# branches: -# - "master" -# workflows: ["TWTW Backend CI", "TWTW Nginx Build"] -# types: -# - completed +on: + workflow_run: + branches: + - "master" + workflows: ["TWTW Backend CI", "TWTW Nginx Build"] + types: + - completed jobs: server-deploy: @@ -14,23 +14,23 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 - - name: Copy docker-compose file to server - uses: appleboy/scp-action@master - with: - host: ${{ secrets.AWS_IP }} - username: ubuntu - key: ${{ secrets.SSH_PRIVATE_KEY }} - source: "./docker-compose.prod.yml" - target: "/var/www/TWTW" + # - name: Copy docker-compose file to server + # uses: appleboy/scp-action@master + # with: + # host: ${{ secrets.AWS_IP }} + # username: ubuntu + # key: ${{ secrets.SSH_PRIVATE_KEY }} + # source: "./docker-compose.prod.yml" + # target: "/var/www/TWTW" - - name: Deploy using docker-compose - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.AWS_IP }} - username: ubuntu - key: ${{ secrets.SSH_PRIVATE_KEY }} - script: | - cd /var/www/TWTW - sudo docker-compose down - sudo docker-compose pull - sudo docker-compose up -d + # - name: Deploy using docker-compose + # uses: appleboy/ssh-action@master + # with: + # host: ${{ secrets.AWS_IP }} + # username: ubuntu + # key: ${{ secrets.SSH_PRIVATE_KEY }} + # script: | + # cd /var/www/TWTW + # sudo docker-compose down + # sudo docker-compose pull + # sudo docker-compose up -d From 0f89fc21299d0822b0623accce363e60321689e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 18:18:53 +0900 Subject: [PATCH 04/29] =?UTF-8?q?[FEAT]=20=EC=88=98=EB=8F=99=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20api=EC=97=90=20cache=20evict=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/friend/service/FriendService.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/backend/src/main/java/com/twtw/backend/domain/friend/service/FriendService.java b/backend/src/main/java/com/twtw/backend/domain/friend/service/FriendService.java index 1a34bbb6..7bd05e2a 100644 --- a/backend/src/main/java/com/twtw/backend/domain/friend/service/FriendService.java +++ b/backend/src/main/java/com/twtw/backend/domain/friend/service/FriendService.java @@ -17,6 +17,7 @@ import com.twtw.backend.global.exception.EntityNotFoundException; import com.twtw.backend.global.lock.DistributedLock; import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -80,6 +81,10 @@ private Friend getFriendById(final UUID loginMemberId, final UUID memberId) { .orElseThrow(EntityNotFoundException::new); } + @CacheEvict( + value = "getFriendsWithCache", + key = "'getFriendsWithCache'.concat(#root.target.authService.getMemberIdValue())", + cacheManager = "cacheManager") @Transactional(readOnly = true) public List getFriends() { return getFriendResponses(); @@ -105,6 +110,10 @@ private List getFriendResponses() { return friendMapper.toResponses(friends); } + @CacheEvict( + value = "getFriendsByStatusWithCache", + key = "'getFriendsWithCache'.concat(#root.target.authService.getMemberIdValue()).concat(#friendStatus.name())", + cacheManager = "cacheManager") @Transactional(readOnly = true) public List getFriendsByStatus(final FriendStatus friendStatus) { return getFriendResponsesByStatus(friendStatus); @@ -130,6 +139,10 @@ private List getFriendResponsesByStatus(final FriendStatus frien return friendMapper.toResponses(friends); } + @CacheEvict( + value = "getFriendsByNicknameWithCache", + key = "'getFriendsWithCache'.concat(#root.target.authService.getMemberIdValue()).concat(#nickname)", + cacheManager = "cacheManager") @Transactional(readOnly = true) public List getFriendByNickname(final String nickname) { return getFriendResponsesByNickname(nickname); From ae95f684e4f821092862de3c458ca89feb31276e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 18:27:25 +0900 Subject: [PATCH 05/29] [HOTFIX] rename github actions env variables --- .github/workflows/backend-ci.yml | 4 ++-- .github/workflows/nginx-build.yml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 3e8d59a7..564488ad 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -26,8 +26,8 @@ jobs: - name: Login to Docker Hub uses: docker/login-action@v1 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_PASSWORD }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} - name: Apply Environment Variables run: | diff --git a/.github/workflows/nginx-build.yml b/.github/workflows/nginx-build.yml index 67d54393..08b93cf8 100644 --- a/.github/workflows/nginx-build.yml +++ b/.github/workflows/nginx-build.yml @@ -16,10 +16,10 @@ jobs: - name: Login to Docker Hub uses: docker/login-action@v1 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_PASSWORD }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} - name: Nginx Build and Push run: | - docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/twtw-nginx:latest ./nginx - docker push ${{ secrets.DOCKERHUB_USERNAME }}/twtw-nginx:latest + docker build -t ${{ secrets.DOCKER_USERNAME }}/twtw-nginx:latest ./nginx + docker push ${{ secrets.DOCKER_USERNAME }}/twtw-nginx:latest From 57104f876abf93b79dc07e431acb284f6abcc3e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 18:34:32 +0900 Subject: [PATCH 06/29] [HOTFIX] fix github actions backend directory --- .github/workflows/backend-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 564488ad..9e552d73 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -43,7 +43,7 @@ jobs: id: jacoco uses: madrapps/jacoco-report@v1.2 with: - paths: ${{ github.workspace }}/build/reports/jacoco/test/jacocoTestReport.xml + paths: ${{ github.workspace }}/backend/build/reports/jacoco/test/jacocoTestReport.xml token: ${{ secrets.GITHUB_TOKEN }} title: "test coverage" min-coverage-overall: 60 From 063b523f98adc0efc10ec4171c9c5f3f1a4d7693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 19:29:17 +0900 Subject: [PATCH 07/29] =?UTF-8?q?[TEST]=20test=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/build.gradle | 2 +- .../domain/friend/service/FriendService.java | 16 ++++++++++------ .../domain/friend/service/FriendServiceTest.java | 8 +++++--- .../domain/group/service/GroupServiceTest.java | 7 ++++--- .../domain/plan/service/PlanServiceTest.java | 9 ++++++--- .../backend/support/database/DatabaseTest.java | 1 - .../support/repository/EnableDataJpa.java | 1 - .../twtw/backend/support/service/LoginTest.java | 1 + .../support/testcontainer/ContainerTest.java | 7 ++++--- 9 files changed, 31 insertions(+), 21 deletions(-) diff --git a/backend/build.gradle b/backend/build.gradle index 7767ca5c..ac55d9c0 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -65,7 +65,7 @@ dependencies { implementation 'org.springframework.cloud:spring-cloud-gcp-storage:1.2.5.RELEASE' implementation 'com.google.firebase:firebase-admin:6.8.1' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' - implementation 'org.redisson:redisson-spring-boot-starter:3.18.0' + implementation 'org.redisson:redisson-spring-data-30:3.27.1' annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" diff --git a/backend/src/main/java/com/twtw/backend/domain/friend/service/FriendService.java b/backend/src/main/java/com/twtw/backend/domain/friend/service/FriendService.java index 7bd05e2a..c777623a 100644 --- a/backend/src/main/java/com/twtw/backend/domain/friend/service/FriendService.java +++ b/backend/src/main/java/com/twtw/backend/domain/friend/service/FriendService.java @@ -81,9 +81,13 @@ private Friend getFriendById(final UUID loginMemberId, final UUID memberId) { .orElseThrow(EntityNotFoundException::new); } + public String getMemberIdValue() { + return authService.getMemberIdValue(); + } + @CacheEvict( value = "getFriendsWithCache", - key = "'getFriendsWithCache'.concat(#root.target.authService.getMemberIdValue())", + key = "'getFriendsWithCache'.concat(#root.target.getMemberIdValue())", cacheManager = "cacheManager") @Transactional(readOnly = true) public List getFriends() { @@ -92,7 +96,7 @@ public List getFriends() { @Cacheable( value = "getFriendsWithCache", - key = "'getFriendsWithCache'.concat(#root.target.authService.getMemberIdValue())", + key = "'getFriendsWithCache'.concat(#root.target.getMemberIdValue())", cacheManager = "cacheManager", unless = "#result.size() <= 0") @Transactional(readOnly = true) @@ -112,7 +116,7 @@ private List getFriendResponses() { @CacheEvict( value = "getFriendsByStatusWithCache", - key = "'getFriendsWithCache'.concat(#root.target.authService.getMemberIdValue()).concat(#friendStatus.name())", + key = "'getFriendsWithCache'.concat(#root.target.getMemberIdValue()).concat(#friendStatus.name())", cacheManager = "cacheManager") @Transactional(readOnly = true) public List getFriendsByStatus(final FriendStatus friendStatus) { @@ -121,7 +125,7 @@ public List getFriendsByStatus(final FriendStatus friendStatus) @Cacheable( value = "getFriendsByStatusWithCache", - key = "'getFriendsWithCache'.concat(#root.target.authService.getMemberIdValue()).concat(#friendStatus.name())", + key = "'getFriendsWithCache'.concat(#root.target.getMemberIdValue()).concat(#friendStatus.name())", cacheManager = "cacheManager", unless = "#result.size() <= 0") @Transactional(readOnly = true) @@ -141,7 +145,7 @@ private List getFriendResponsesByStatus(final FriendStatus frien @CacheEvict( value = "getFriendsByNicknameWithCache", - key = "'getFriendsWithCache'.concat(#root.target.authService.getMemberIdValue()).concat(#nickname)", + key = "'getFriendsWithCache'.concat(#root.target.getMemberIdValue()).concat(#nickname)", cacheManager = "cacheManager") @Transactional(readOnly = true) public List getFriendByNickname(final String nickname) { @@ -150,7 +154,7 @@ public List getFriendByNickname(final String nickname) { @Cacheable( value = "getFriendsByNicknameWithCache", - key = "'getFriendsWithCache'.concat(#root.target.authService.getMemberIdValue()).concat(#nickname)", + key = "'getFriendsWithCache'.concat(#root.target.getMemberIdValue()).concat(#nickname)", cacheManager = "cacheManager", unless = "#result.size() <= 0") @Transactional(readOnly = true) diff --git a/backend/src/test/java/com/twtw/backend/domain/friend/service/FriendServiceTest.java b/backend/src/test/java/com/twtw/backend/domain/friend/service/FriendServiceTest.java index 1f9b35e9..e554702b 100644 --- a/backend/src/test/java/com/twtw/backend/domain/friend/service/FriendServiceTest.java +++ b/backend/src/test/java/com/twtw/backend/domain/friend/service/FriendServiceTest.java @@ -1,7 +1,5 @@ package com.twtw.backend.domain.friend.service; -import static org.assertj.core.api.Assertions.assertThat; - import com.twtw.backend.domain.friend.dto.request.FriendRequest; import com.twtw.backend.domain.friend.dto.request.FriendUpdateRequest; import com.twtw.backend.domain.friend.dto.response.FriendResponse; @@ -12,14 +10,16 @@ import com.twtw.backend.domain.member.entity.Member; import com.twtw.backend.domain.member.entity.OAuth2Info; import com.twtw.backend.support.service.LoginTest; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.UUID; +import static org.assertj.core.api.Assertions.assertThat; + @DisplayName("FriendService의") class FriendServiceTest extends LoginTest { @@ -69,6 +69,7 @@ void updateStatus() { } @Test + @Transactional @DisplayName("친구 목록 조회가 수행되는가") void getFriends() { // given @@ -106,6 +107,7 @@ void getFriendsByStatus() { } @Test + @Transactional @DisplayName("닉네임을 통한 친구 조회가 수행되는가") void getFriendByNickname() { // given diff --git a/backend/src/test/java/com/twtw/backend/domain/group/service/GroupServiceTest.java b/backend/src/test/java/com/twtw/backend/domain/group/service/GroupServiceTest.java index 0609651c..1ebc169c 100644 --- a/backend/src/test/java/com/twtw/backend/domain/group/service/GroupServiceTest.java +++ b/backend/src/test/java/com/twtw/backend/domain/group/service/GroupServiceTest.java @@ -1,7 +1,5 @@ package com.twtw.backend.domain.group.service; -import static org.assertj.core.api.Assertions.assertThat; - import com.twtw.backend.domain.group.dto.request.InviteGroupRequest; import com.twtw.backend.domain.group.dto.request.JoinGroupRequest; import com.twtw.backend.domain.group.dto.request.MakeGroupRequest; @@ -14,13 +12,15 @@ import com.twtw.backend.domain.member.entity.Member; import com.twtw.backend.fixture.member.MemberEntityFixture; import com.twtw.backend.support.service.LoginTest; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; + @DisplayName("GroupService의") class GroupServiceTest extends LoginTest { @Autowired private GroupService groupService; @@ -87,6 +87,7 @@ void inviteGroup() { } @Test + @Transactional @DisplayName("위치 공유를 공개 -> 비공개 변경이 가능한가") void changeShare() { // given diff --git a/backend/src/test/java/com/twtw/backend/domain/plan/service/PlanServiceTest.java b/backend/src/test/java/com/twtw/backend/domain/plan/service/PlanServiceTest.java index ed3e4563..112b8594 100644 --- a/backend/src/test/java/com/twtw/backend/domain/plan/service/PlanServiceTest.java +++ b/backend/src/test/java/com/twtw/backend/domain/plan/service/PlanServiceTest.java @@ -1,7 +1,5 @@ package com.twtw.backend.domain.plan.service; -import static org.assertj.core.api.Assertions.assertThat; - import com.twtw.backend.domain.group.repository.GroupRepository; import com.twtw.backend.domain.member.entity.Member; import com.twtw.backend.domain.place.entity.CategoryGroupCode; @@ -19,16 +17,18 @@ import com.twtw.backend.fixture.place.PlaceEntityFixture; import com.twtw.backend.fixture.plan.PlanEntityFixture; import com.twtw.backend.support.service.LoginTest; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import java.util.UUID; +import static org.assertj.core.api.Assertions.assertThat; + @DisplayName("PlanService의") class PlanServiceTest extends LoginTest { @@ -73,6 +73,7 @@ void savePlan() { } @Test + @Transactional @DisplayName("계획 참여가 수행되는가") void joinPlan() { // given @@ -98,6 +99,7 @@ void joinPlan() { } @Test + @Transactional @DisplayName("계획 나가기가 수행되는가") // TODO: 계획에 1명 있는데 나가는 경우 생각해보기 void outPlan() { // given @@ -161,6 +163,7 @@ void deletePlan() { } @Test + @Transactional @DisplayName("수정이 수행되는가") void updatePlan() { // given diff --git a/backend/src/test/java/com/twtw/backend/support/database/DatabaseTest.java b/backend/src/test/java/com/twtw/backend/support/database/DatabaseTest.java index 3f92d380..12533092 100644 --- a/backend/src/test/java/com/twtw/backend/support/database/DatabaseTest.java +++ b/backend/src/test/java/com/twtw/backend/support/database/DatabaseTest.java @@ -12,7 +12,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Transactional @ActiveProfiles("test") @Target(ElementType.TYPE) @Import(QuerydslConfig.class) diff --git a/backend/src/test/java/com/twtw/backend/support/repository/EnableDataJpa.java b/backend/src/test/java/com/twtw/backend/support/repository/EnableDataJpa.java index d5a2e6af..78f3751b 100644 --- a/backend/src/test/java/com/twtw/backend/support/repository/EnableDataJpa.java +++ b/backend/src/test/java/com/twtw/backend/support/repository/EnableDataJpa.java @@ -13,7 +13,6 @@ import java.lang.annotation.Target; @DataJpaTest -@ActiveProfiles("test") @Target(ElementType.TYPE) @Import(QuerydslConfig.class) @Retention(RetentionPolicy.RUNTIME) diff --git a/backend/src/test/java/com/twtw/backend/support/service/LoginTest.java b/backend/src/test/java/com/twtw/backend/support/service/LoginTest.java index 5722bbe8..292665d1 100644 --- a/backend/src/test/java/com/twtw/backend/support/service/LoginTest.java +++ b/backend/src/test/java/com/twtw/backend/support/service/LoginTest.java @@ -36,6 +36,7 @@ public void setup() { final Member member = MemberEntityFixture.LOGIN_MEMBER.toEntity(); loginUser = memberRepository.save(member); when(authService.getMemberByJwt()).thenReturn(loginUser); + when(authService.getMemberIdValue()).thenReturn("123123123.123123123.123123123"); } @BeforeEach diff --git a/backend/src/test/java/com/twtw/backend/support/testcontainer/ContainerTest.java b/backend/src/test/java/com/twtw/backend/support/testcontainer/ContainerTest.java index da452958..79237a1d 100644 --- a/backend/src/test/java/com/twtw/backend/support/testcontainer/ContainerTest.java +++ b/backend/src/test/java/com/twtw/backend/support/testcontainer/ContainerTest.java @@ -24,14 +24,15 @@ public class ContainerTest implements } @Override - public void initialize(ConfigurableApplicationContext applicationContext) { + public void initialize(final ConfigurableApplicationContext applicationContext) { TestPropertyValues.of( "spring.data.redis.host=" + REDIS_CONTAINER.getHost(), "spring.data.redis.port=" + REDIS_CONTAINER.getFirstMappedPort(), "spring.rabbitmq.host=" + RABBIT_MQ_CONTAINER.getHost(), - "spring.rabbitmq.port=" + RABBIT_MQ_CONTAINER.getAmqpPort(), + "spring.rabbitmq.port=" + RABBIT_MQ_CONTAINER.getMappedPort(5672), + "spring.rabbitmq.stomp.port=" + RABBIT_MQ_CONTAINER.getMappedPort(61613), "spring.rabbitmq.username=guest", "spring.rabbitmq.password=guest" ).applyTo(applicationContext.getEnvironment()); } -} \ No newline at end of file +} From 299cdf78de1e3872497f9742ba9a2169aa2d4d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 19:37:21 +0900 Subject: [PATCH 08/29] [TEST] add jacoco test command --- .github/workflows/backend-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 9e552d73..9e130e81 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -37,6 +37,7 @@ jobs: - run: chmod +x gradlew - run: ./gradlew build -x test + - run: ./gradlew test jacocoTestReport - run: ./gradlew jib - name: Test Coverage Report From 9e2c761e75e8491a24889b49077c5bb5ff45a48a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 19:43:00 +0900 Subject: [PATCH 09/29] [FIX] fix jacoco test error --- .github/workflows/backend-ci.yml | 1 + backend/build.gradle | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 9e130e81..9bb2ac1f 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -32,6 +32,7 @@ jobs: - name: Apply Environment Variables run: | mkdir -p src/test/resources + echo "${{ secrets.ENVIRONMENT }}" | base64 --decode > src/test/resources/application-env.yml echo "${{ secrets.ENVIRONMENT }}" | base64 --decode > src/main/resources/application-env.yml find src diff --git a/backend/build.gradle b/backend/build.gradle index ac55d9c0..9a1e30ad 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -109,6 +109,7 @@ tasks.named('test') { "com/twtw/backend/config/**", "com/twtw/backend/global/**", "com/twtw/backend/aspect/**", + "com/twtw/backend/domain/**/exception/**", "com/twtw/backend/domain/**/controller/advice/**", "com/twtw/backend/domain/**/dto/**"] } @@ -131,6 +132,7 @@ jacocoTestReport { "com/twtw/backend/config/**", "com/twtw/backend/global/**", "com/twtw/backend/aspect/**", + "com/twtw/backend/domain/**/exception/**", "com/twtw/backend/domain/**/controller/advice/**", "com/twtw/backend/domain/**/dto/**"]) })) From 9f428ae3dc947cb076dc37f3c04ed6e4309ff428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 19:59:18 +0900 Subject: [PATCH 10/29] [FIX] add missing env files --- .github/workflows/backend-ci.yml | 9 +++++++-- backend/.gitignore | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 9bb2ac1f..72a1685f 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -32,8 +32,13 @@ jobs: - name: Apply Environment Variables run: | mkdir -p src/test/resources + mkdir -p src/main/resources echo "${{ secrets.ENVIRONMENT }}" | base64 --decode > src/test/resources/application-env.yml echo "${{ secrets.ENVIRONMENT }}" | base64 --decode > src/main/resources/application-env.yml + echo "${{ secrets.FIREBASE_JSON }}" | base64 --decode > src/test/resources/twtw_firebase_key.json + echo "${{ secrets.FIREBASE_JSON }}" | base64 --decode > src/main/resources/twtw_firebase_key.json + echo "${{ secrets.STORAGE_JSON }}" | base64 --decode > src/test/resources/engaged-shade-405207-b8ba9bb8a30f.json + echo "${{ secrets.STORAGE_JSON }}" | base64 --decode > src/main/resources/engaged-shade-405207-b8ba9bb8a30f.json find src - run: chmod +x gradlew @@ -48,5 +53,5 @@ jobs: paths: ${{ github.workspace }}/backend/build/reports/jacoco/test/jacocoTestReport.xml token: ${{ secrets.GITHUB_TOKEN }} title: "test coverage" - min-coverage-overall: 60 - min-coverage-changed-files: 60 + min-coverage-overall: 50 + min-coverage-changed-files: 50 diff --git a/backend/.gitignore b/backend/.gitignore index 86c53694..5e139036 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -44,3 +44,4 @@ out/ *.html application-env.yml +*.json From e151892e16fb7f451397db796f3578d767941e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 20:06:48 +0900 Subject: [PATCH 11/29] [FIX] github actions jacoco test error --- .github/workflows/backend-ci.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 72a1685f..208d3cbf 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -33,13 +33,12 @@ jobs: run: | mkdir -p src/test/resources mkdir -p src/main/resources - echo "${{ secrets.ENVIRONMENT }}" | base64 --decode > src/test/resources/application-env.yml - echo "${{ secrets.ENVIRONMENT }}" | base64 --decode > src/main/resources/application-env.yml - echo "${{ secrets.FIREBASE_JSON }}" | base64 --decode > src/test/resources/twtw_firebase_key.json - echo "${{ secrets.FIREBASE_JSON }}" | base64 --decode > src/main/resources/twtw_firebase_key.json - echo "${{ secrets.STORAGE_JSON }}" | base64 --decode > src/test/resources/engaged-shade-405207-b8ba9bb8a30f.json - echo "${{ secrets.STORAGE_JSON }}" | base64 --decode > src/main/resources/engaged-shade-405207-b8ba9bb8a30f.json - find src + echo "${{ secrets.ENVIRONMENT_YML }}" > src/test/resources/application-env.yml + echo "${{ secrets.ENVIRONMENT_YML }}" > src/main/resources/application-env.yml + echo "${{ secrets.FIREBASE_JSON }}" > src/test/resources/twtw_firebase_key.json + echo "${{ secrets.FIREBASE_JSON }}" > src/main/resources/twtw_firebase_key.json + echo "${{ secrets.STORAGE_JSON }}" > src/test/resources/engaged-shade-405207-b8ba9bb8a30f.json + echo "${{ secrets.STORAGE_JSON }}" > src/main/resources/engaged-shade-405207-b8ba9bb8a30f.json - run: chmod +x gradlew - run: ./gradlew build -x test From ae2f90837b5476011046d80020e5c11222c55058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 20:23:41 +0900 Subject: [PATCH 12/29] [FIX] gcloud env error --- .../backend/config/firebase/FcmConfig.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java b/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java index 5ec1e045..f2c4632e 100644 --- a/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java +++ b/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java @@ -21,23 +21,24 @@ public class FcmConfig { private final FirebaseProperties firebaseProperties; @Bean - public FirebaseApp firebaseApp() throws IOException { - FirebaseOptions options = - new FirebaseOptions.Builder() - .setCredentials( - GoogleCredentials.fromStream( - new ClassPathResource(firebaseProperties.getLocation()) - .getInputStream())) - .build(); - - if (FirebaseApp.getApps().isEmpty()) { - return FirebaseApp.initializeApp(options); - } + public FirebaseApp firebaseApp() { + try { + final FirebaseOptions options = + new FirebaseOptions.Builder() + .setCredentials( + GoogleCredentials.fromStream( + new ClassPathResource(firebaseProperties.getLocation()) + .getInputStream())) + .build(); + if (FirebaseApp.getApps().isEmpty()) { + return FirebaseApp.initializeApp(options); + } + } catch (IOException ignored) {} return FirebaseApp.getInstance(); } @Bean - public FirebaseMessaging firebaseMessaging() throws IOException { + public FirebaseMessaging firebaseMessaging() { return FirebaseMessaging.getInstance(firebaseApp()); } } From 80c492eb42e26b930d9fa1f3515709b036c7c07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 20:26:48 +0900 Subject: [PATCH 13/29] [FIX] firebase app init error --- .../java/com/twtw/backend/config/firebase/FcmConfig.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java b/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java index f2c4632e..a271cff9 100644 --- a/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java +++ b/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java @@ -30,11 +30,14 @@ public FirebaseApp firebaseApp() { new ClassPathResource(firebaseProperties.getLocation()) .getInputStream())) .build(); + if (FirebaseApp.getApps().isEmpty()) { return FirebaseApp.initializeApp(options); } - } catch (IOException ignored) {} - return FirebaseApp.getInstance(); + return FirebaseApp.getInstance(); + } catch (final IOException ignored) { + return null; + } } @Bean From 4b6321122e89b9ea7c840d39c832d84391533732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 20:31:50 +0900 Subject: [PATCH 14/29] [FIX] return temp instance --- .../java/com/twtw/backend/config/firebase/FcmConfig.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java b/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java index a271cff9..af4bc04c 100644 --- a/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java +++ b/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java @@ -5,15 +5,11 @@ import com.google.firebase.FirebaseOptions; import com.google.firebase.messaging.FirebaseMessaging; import com.twtw.backend.global.properties.FirebaseProperties; - import lombok.RequiredArgsConstructor; - import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; -import java.io.IOException; - @Configuration @RequiredArgsConstructor public class FcmConfig { @@ -35,8 +31,8 @@ public FirebaseApp firebaseApp() { return FirebaseApp.initializeApp(options); } return FirebaseApp.getInstance(); - } catch (final IOException ignored) { - return null; + } catch (final Exception e) { + return FirebaseApp.initializeApp(); } } From 965ef5f0eed9ce0f82e45dce78127f58b2f45fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 21:00:55 +0900 Subject: [PATCH 15/29] [FIX] github actions error --- .github/workflows/backend-ci.yml | 9 +++++++++ .gitignore | 1 + docker-compose.monitoring.yml | 2 -- docker-compose.yml | 5 +++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 208d3cbf..c6b1dd9c 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -39,6 +39,15 @@ jobs: echo "${{ secrets.FIREBASE_JSON }}" > src/main/resources/twtw_firebase_key.json echo "${{ secrets.STORAGE_JSON }}" > src/test/resources/engaged-shade-405207-b8ba9bb8a30f.json echo "${{ secrets.STORAGE_JSON }}" > src/main/resources/engaged-shade-405207-b8ba9bb8a30f.json + + - name: Set File Permissions + run: | + chmod 644 src/test/resources/application-env.yml + chmod 644 src/main/resources/application-env.yml + chmod 644 src/test/resources/twtw_firebase_key.json + chmod 644 src/main/resources/twtw_firebase_key.json + chmod 644 src/test/resources/engaged-shade-405207-b8ba9bb8a30f.json + chmod 644 src/main/resources/engaged-shade-405207-b8ba9bb8a30f.json - run: chmod +x gradlew - run: ./gradlew build -x test diff --git a/.gitignore b/.gitignore index 5d791a0c..12ffb402 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ backend/src/main/resources/application-env.yml .env secret.yml +data/ diff --git a/docker-compose.monitoring.yml b/docker-compose.monitoring.yml index 88d0456f..ed6ec909 100644 --- a/docker-compose.monitoring.yml +++ b/docker-compose.monitoring.yml @@ -32,8 +32,6 @@ alertmanager: user: root ports: - 9093:9093 - environment: - - SLACK_API_URL=${SLACK_API_URL} volumes: - ./prometheus/alertmanager.yml:/etc/alertmanager/alertmanager.yml networks: diff --git a/docker-compose.yml b/docker-compose.yml index 114076df..d541621b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -98,10 +98,11 @@ services: user: root ports: - 9093:9093 - environment: - - SLACK_API_URL=${SLACK_API_URL} volumes: - ./prometheus/alertmanager.yml:/etc/alertmanager/alertmanager.yml + depends_on: + - backend + - prometheus networks: - backend From 365d6d7160f607830c92fcb87ee18cdc31dd697b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 21:44:39 +0900 Subject: [PATCH 16/29] [FIX] error on ci --- .github/workflows/backend-ci.yml | 3 +-- backend/build.gradle | 10 +--------- prometheus/alertmanager.yml | 2 +- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index c6b1dd9c..92aba197 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -50,8 +50,7 @@ jobs: chmod 644 src/main/resources/engaged-shade-405207-b8ba9bb8a30f.json - run: chmod +x gradlew - - run: ./gradlew build -x test - - run: ./gradlew test jacocoTestReport + - run: ./gradlew build - run: ./gradlew jib - name: Test Coverage Report diff --git a/backend/build.gradle b/backend/build.gradle index 9a1e30ad..ae335fa0 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -104,15 +104,7 @@ tasks.named('test') { outputs.dir snippetsDir useJUnitPlatform() - jacoco { - excludes += ["com/twtw/backend/BackendApplication.class", - "com/twtw/backend/config/**", - "com/twtw/backend/global/**", - "com/twtw/backend/aspect/**", - "com/twtw/backend/domain/**/exception/**", - "com/twtw/backend/domain/**/controller/advice/**", - "com/twtw/backend/domain/**/dto/**"] - } + finalizedBy 'jacocoTestReport' } jacoco { diff --git a/prometheus/alertmanager.yml b/prometheus/alertmanager.yml index 3822b1cb..c6890cdc 100644 --- a/prometheus/alertmanager.yml +++ b/prometheus/alertmanager.yml @@ -7,7 +7,7 @@ route: receivers: - name: 'slack-notifications' slack_configs: - - channel: '#monitoring' + - channel: '#server-alert' send_resolved: true title: "{{ range .Alerts }}{{ .Annotations.summary }}\n{{ end }}" text: "{{ range .Alerts }}{{ .Annotations.description }}\n{{ end }}" From 74bfc842cc2065cebda177748f0c3c90656ac17f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 21:52:34 +0900 Subject: [PATCH 17/29] [FIX] change file read logic --- .../backend/config/firebase/FcmConfig.java | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java b/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java index af4bc04c..44600176 100644 --- a/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java +++ b/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java @@ -6,38 +6,35 @@ import com.google.firebase.messaging.FirebaseMessaging; import com.twtw.backend.global.properties.FirebaseProperties; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; + +import java.io.IOException; @Configuration @RequiredArgsConstructor public class FcmConfig { private final FirebaseProperties firebaseProperties; + private final ApplicationContext context; @Bean - public FirebaseApp firebaseApp() { - try { - final FirebaseOptions options = - new FirebaseOptions.Builder() - .setCredentials( - GoogleCredentials.fromStream( - new ClassPathResource(firebaseProperties.getLocation()) - .getInputStream())) - .build(); + public FirebaseApp firebaseApp() throws IOException { + final FirebaseOptions options = new FirebaseOptions.Builder() + .setCredentials(GoogleCredentials.fromStream( + context.getResource(firebaseProperties.getLocation()).getInputStream())) + .build(); - if (FirebaseApp.getApps().isEmpty()) { - return FirebaseApp.initializeApp(options); - } + if (FirebaseApp.getApps().isEmpty()) { + return FirebaseApp.initializeApp(options); + } else { return FirebaseApp.getInstance(); - } catch (final Exception e) { - return FirebaseApp.initializeApp(); } } @Bean - public FirebaseMessaging firebaseMessaging() { + public FirebaseMessaging firebaseMessaging() throws IOException { return FirebaseMessaging.getInstance(firebaseApp()); } } From ba919527c744c0c1e4acdbd99e35c55d74ca73b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 22:08:30 +0900 Subject: [PATCH 18/29] [FIX] fixing fcm config --- .github/workflows/backend-ci.yml | 5 +++-- .../twtw/backend/config/firebase/FcmConfig.java | 15 ++++++++++----- backend/src/main/resources/application-dev.yml | 5 +++++ nginx/nginx.conf | 16 ++++++++-------- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 92aba197..4736d190 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -48,9 +48,10 @@ jobs: chmod 644 src/main/resources/twtw_firebase_key.json chmod 644 src/test/resources/engaged-shade-405207-b8ba9bb8a30f.json chmod 644 src/main/resources/engaged-shade-405207-b8ba9bb8a30f.json + chmod +x gradlew + find src + ./gradlew build - - run: chmod +x gradlew - - run: ./gradlew build - run: ./gradlew jib - name: Test Coverage Report diff --git a/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java b/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java index 44600176..a1a63f47 100644 --- a/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java +++ b/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java @@ -6,31 +6,36 @@ import com.google.firebase.messaging.FirebaseMessaging; import com.twtw.backend.global.properties.FirebaseProperties; import lombok.RequiredArgsConstructor; -import org.springframework.context.ApplicationContext; +import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; import java.io.IOException; +@Slf4j @Configuration @RequiredArgsConstructor public class FcmConfig { private final FirebaseProperties firebaseProperties; - private final ApplicationContext context; @Bean public FirebaseApp firebaseApp() throws IOException { + log.info("FirebaseApp initializing...11111"); + final FirebaseOptions options = new FirebaseOptions.Builder() .setCredentials(GoogleCredentials.fromStream( - context.getResource(firebaseProperties.getLocation()).getInputStream())) + new ClassPathResource(firebaseProperties.getLocation()).getInputStream())) .build(); + log.info("FirebaseApp initializing...22222"); if (FirebaseApp.getApps().isEmpty()) { + log.info("FirebaseApp initializing...33333"); return FirebaseApp.initializeApp(options); - } else { - return FirebaseApp.getInstance(); } + log.info("FirebaseApp initializing...44444"); + return FirebaseApp.getInstance(); } @Bean diff --git a/backend/src/main/resources/application-dev.yml b/backend/src/main/resources/application-dev.yml index f3679413..0ea28d18 100644 --- a/backend/src/main/resources/application-dev.yml +++ b/backend/src/main/resources/application-dev.yml @@ -39,3 +39,8 @@ spring: baseline-on-migrate: false user: root password: 1234 +management: + endpoints: + web: + exposure: + include: prometheus diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 078a461a..f70d1918 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -8,6 +8,14 @@ server { charset utf-8; client_max_body_size 0; + location = /api/v1/location { + proxy_pass http://api; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 180s; + } + location /api/ { default_type application/octet-stream; include /etc/nginx/mime.types; @@ -18,14 +26,6 @@ server { proxy_pass http://api; } - location ~ ^/api/v[0-9]+/location$ { - proxy_pass http://api; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_read_timeout 180s; - } - location ~* (service-worker\.js)$ { default_type application/octet-stream; include /etc/nginx/mime.types; From c15200085d509757ec10d265d91315ee23b7546f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 22:20:59 +0900 Subject: [PATCH 19/29] [FIX] error fixing --- .github/workflows/backend-ci.yml | 4 ++-- .../java/com/twtw/backend/config/firebase/FcmConfig.java | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 4736d190..67f495da 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -50,8 +50,8 @@ jobs: chmod 644 src/main/resources/engaged-shade-405207-b8ba9bb8a30f.json chmod +x gradlew find src - ./gradlew build - + + - run: ./gradlew build - run: ./gradlew jib - name: Test Coverage Report diff --git a/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java b/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java index a1a63f47..bd7b8fd2 100644 --- a/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java +++ b/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java @@ -22,19 +22,14 @@ public class FcmConfig { @Bean public FirebaseApp firebaseApp() throws IOException { - log.info("FirebaseApp initializing...11111"); - final FirebaseOptions options = new FirebaseOptions.Builder() .setCredentials(GoogleCredentials.fromStream( new ClassPathResource(firebaseProperties.getLocation()).getInputStream())) .build(); - log.info("FirebaseApp initializing...22222"); if (FirebaseApp.getApps().isEmpty()) { - log.info("FirebaseApp initializing...33333"); return FirebaseApp.initializeApp(options); } - log.info("FirebaseApp initializing...44444"); return FirebaseApp.getInstance(); } From 411cc3fb7045d09ffa9b3ef3add9723c14b57434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 22:49:39 +0900 Subject: [PATCH 20/29] [FIX] objectmapper to parse json with value containing backslash --- .github/workflows/backend-ci.yml | 12 +----------- .../java/com/twtw/backend/config/web/WebConfig.java | 9 +++++++-- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 67f495da..e9da9017 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -40,17 +40,7 @@ jobs: echo "${{ secrets.STORAGE_JSON }}" > src/test/resources/engaged-shade-405207-b8ba9bb8a30f.json echo "${{ secrets.STORAGE_JSON }}" > src/main/resources/engaged-shade-405207-b8ba9bb8a30f.json - - name: Set File Permissions - run: | - chmod 644 src/test/resources/application-env.yml - chmod 644 src/main/resources/application-env.yml - chmod 644 src/test/resources/twtw_firebase_key.json - chmod 644 src/main/resources/twtw_firebase_key.json - chmod 644 src/test/resources/engaged-shade-405207-b8ba9bb8a30f.json - chmod 644 src/main/resources/engaged-shade-405207-b8ba9bb8a30f.json - chmod +x gradlew - find src - + - run: chmod +x gradlew - run: ./gradlew build - run: ./gradlew jib diff --git a/backend/src/main/java/com/twtw/backend/config/web/WebConfig.java b/backend/src/main/java/com/twtw/backend/config/web/WebConfig.java index 92fecc15..8540816b 100644 --- a/backend/src/main/java/com/twtw/backend/config/web/WebConfig.java +++ b/backend/src/main/java/com/twtw/backend/config/web/WebConfig.java @@ -1,12 +1,14 @@ package com.twtw.backend.config.web; +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; - +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; @@ -23,11 +25,14 @@ public void configureMessageConverters(final List> conve super.configureMessageConverters(converters); } - private ObjectMapper objectMapper() { + @Bean + @Primary + public ObjectMapper objectMapper() { return new ObjectMapper() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true) .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true) + .configure(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true) .setPropertyNamingStrategy(PropertyNamingStrategies.LOWER_CAMEL_CASE) .registerModule(new JavaTimeModule()); } From 007eafc5a5788e660fb227c28d7aca1ba1669657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 23:08:28 +0900 Subject: [PATCH 21/29] [FIX] change to use key value --- .../com/twtw/backend/config/firebase/FcmConfig.java | 11 ++++------- .../backend/global/properties/FirebaseProperties.java | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java b/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java index bd7b8fd2..282a6f85 100644 --- a/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java +++ b/backend/src/main/java/com/twtw/backend/config/firebase/FcmConfig.java @@ -1,5 +1,6 @@ package com.twtw.backend.config.firebase; +import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; @@ -9,9 +10,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; - -import java.io.IOException; @Slf4j @Configuration @@ -21,10 +19,9 @@ public class FcmConfig { private final FirebaseProperties firebaseProperties; @Bean - public FirebaseApp firebaseApp() throws IOException { + public FirebaseApp firebaseApp() { final FirebaseOptions options = new FirebaseOptions.Builder() - .setCredentials(GoogleCredentials.fromStream( - new ClassPathResource(firebaseProperties.getLocation()).getInputStream())) + .setCredentials(GoogleCredentials.create(new AccessToken(firebaseProperties.getKey(), null))) .build(); if (FirebaseApp.getApps().isEmpty()) { @@ -34,7 +31,7 @@ public FirebaseApp firebaseApp() throws IOException { } @Bean - public FirebaseMessaging firebaseMessaging() throws IOException { + public FirebaseMessaging firebaseMessaging() { return FirebaseMessaging.getInstance(firebaseApp()); } } diff --git a/backend/src/main/java/com/twtw/backend/global/properties/FirebaseProperties.java b/backend/src/main/java/com/twtw/backend/global/properties/FirebaseProperties.java index da6f83a8..14cda45f 100644 --- a/backend/src/main/java/com/twtw/backend/global/properties/FirebaseProperties.java +++ b/backend/src/main/java/com/twtw/backend/global/properties/FirebaseProperties.java @@ -9,5 +9,5 @@ @RequiredArgsConstructor @ConfigurationProperties(prefix = "firebase") public class FirebaseProperties { - private final String location; + private final String key; } From c94bb4f171b2209ffd702d88eb285ab67b0aec54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 23:17:40 +0900 Subject: [PATCH 22/29] [CHORE] fix jacoco report flow --- .github/workflows/backend-ci.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index e9da9017..982b76e9 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -44,12 +44,11 @@ jobs: - run: ./gradlew build - run: ./gradlew jib - - name: Test Coverage Report + - name: Check test coverage id: jacoco uses: madrapps/jacoco-report@v1.2 with: - paths: ${{ github.workspace }}/backend/build/reports/jacoco/test/jacocoTestReport.xml - token: ${{ secrets.GITHUB_TOKEN }} - title: "test coverage" + paths: build/reports/jacoco/test/jacocoTestReport.xml + title: "Test Coverage" min-coverage-overall: 50 min-coverage-changed-files: 50 From c427365bb654bc030efa65ee33b74da44bac2cc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 23:21:48 +0900 Subject: [PATCH 23/29] [FIX] add token to github actions flow --- .github/workflows/backend-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 982b76e9..b3cec8ea 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -49,6 +49,7 @@ jobs: uses: madrapps/jacoco-report@v1.2 with: paths: build/reports/jacoco/test/jacocoTestReport.xml + token: ${{ secrets.GITHUB_TOKEN }} title: "Test Coverage" min-coverage-overall: 50 min-coverage-changed-files: 50 From 695de591982cf51f4f43e38389f08c9f74be5f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 23:33:43 +0900 Subject: [PATCH 24/29] [FIX] add verify flow for test --- .github/workflows/backend-ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index b3cec8ea..19b7dc3b 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -35,8 +35,6 @@ jobs: mkdir -p src/main/resources echo "${{ secrets.ENVIRONMENT_YML }}" > src/test/resources/application-env.yml echo "${{ secrets.ENVIRONMENT_YML }}" > src/main/resources/application-env.yml - echo "${{ secrets.FIREBASE_JSON }}" > src/test/resources/twtw_firebase_key.json - echo "${{ secrets.FIREBASE_JSON }}" > src/main/resources/twtw_firebase_key.json echo "${{ secrets.STORAGE_JSON }}" > src/test/resources/engaged-shade-405207-b8ba9bb8a30f.json echo "${{ secrets.STORAGE_JSON }}" > src/main/resources/engaged-shade-405207-b8ba9bb8a30f.json @@ -44,6 +42,9 @@ jobs: - run: ./gradlew build - run: ./gradlew jib + - name: Verify JaCoCo Test Report + run: ls -l build/reports/jacoco/test + - name: Check test coverage id: jacoco uses: madrapps/jacoco-report@v1.2 From 6e34091effbc9d5063cb5373ce4efbb2ac63a08a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 23:37:57 +0900 Subject: [PATCH 25/29] [FIX] error fix --- .github/workflows/backend-ci.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 19b7dc3b..e529b79a 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -42,14 +42,11 @@ jobs: - run: ./gradlew build - run: ./gradlew jib - - name: Verify JaCoCo Test Report - run: ls -l build/reports/jacoco/test - - name: Check test coverage id: jacoco uses: madrapps/jacoco-report@v1.2 with: - paths: build/reports/jacoco/test/jacocoTestReport.xml + paths: ${{ github.workspace }}/backend/build/reports/jacoco/test/jacocoTestReport.xml token: ${{ secrets.GITHUB_TOKEN }} title: "Test Coverage" min-coverage-overall: 50 From c66c9af3d257bb8c142d753edcf42e736c87d463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Fri, 8 Mar 2024 23:44:25 +0900 Subject: [PATCH 26/29] [CHORE] change to use pat in actions flow --- .github/workflows/backend-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index e529b79a..566daaf7 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -47,7 +47,7 @@ jobs: uses: madrapps/jacoco-report@v1.2 with: paths: ${{ github.workspace }}/backend/build/reports/jacoco/test/jacocoTestReport.xml - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.FLOWS_PAT }} title: "Test Coverage" min-coverage-overall: 50 min-coverage-changed-files: 50 From 4c1cc06ae76e6aa11e836c1c5dc3174d92c2af23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Sat, 9 Mar 2024 21:39:53 +0900 Subject: [PATCH 27/29] [FEAT] add circuitbreaker pattern with resilience4j --- backend/build.gradle | 1 + .../domain/path/client/SearchCarPathClient.java | 9 +++++++++ .../domain/path/client/SearchPedPathClient.java | 9 +++++++++ .../dto/client/car/SearchCarPathResponse.java | 11 +++++++++++ .../dto/client/ped/SearchPedPathResponse.java | 9 +++++++++ .../domain/place/client/SurroundPlaceClient.java | 9 +++++++++ .../place/dto/client/SurroundPlaceResponse.java | 9 +++++++++ .../plan/client/SearchDestinationClient.java | 10 +++++++++- .../dto/client/SearchDestinationResponse.java | 9 +++++++++ backend/src/main/resources/application-dev.yml | 15 ++++++++++++++- backend/src/main/resources/application-prod.yml | 15 ++++++++++++++- backend/src/test/resources/application.yml | 10 ++++++++++ 12 files changed, 113 insertions(+), 3 deletions(-) diff --git a/backend/build.gradle b/backend/build.gradle index ae335fa0..a9f2cee7 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -52,6 +52,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-reactor-netty' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.1.0' implementation 'io.jsonwebtoken:jjwt-api:0.11.5' implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' diff --git a/backend/src/main/java/com/twtw/backend/domain/path/client/SearchCarPathClient.java b/backend/src/main/java/com/twtw/backend/domain/path/client/SearchCarPathClient.java index 1aae218e..cee828a8 100644 --- a/backend/src/main/java/com/twtw/backend/domain/path/client/SearchCarPathClient.java +++ b/backend/src/main/java/com/twtw/backend/domain/path/client/SearchCarPathClient.java @@ -6,6 +6,8 @@ import com.twtw.backend.global.exception.WebClientResponseException; import com.twtw.backend.global.properties.NaverProperties; +import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.client.WebClient; @@ -14,6 +16,7 @@ import java.net.URI; import java.nio.charset.StandardCharsets; +@Slf4j @Component public class SearchCarPathClient extends NaverMapClient { @@ -45,6 +48,7 @@ private URI getPathUri(final SearchCarPathRequest request, final UriBuilder uriB } @Override + @CircuitBreaker(name = "backend-a", fallbackMethod = "fallback") public SearchCarPathResponse request(final SearchCarPathRequest request) { return webClient .get() @@ -58,4 +62,9 @@ public SearchCarPathResponse request(final SearchCarPathRequest request) { .blockOptional() .orElseThrow(WebClientResponseException::new); } + + public SearchCarPathResponse fallback(final Exception e) { + log.error("SearchCarPathClient fallback", e); + return SearchCarPathResponse.onError(); + } } diff --git a/backend/src/main/java/com/twtw/backend/domain/path/client/SearchPedPathClient.java b/backend/src/main/java/com/twtw/backend/domain/path/client/SearchPedPathClient.java index f050510a..704c4a05 100644 --- a/backend/src/main/java/com/twtw/backend/domain/path/client/SearchPedPathClient.java +++ b/backend/src/main/java/com/twtw/backend/domain/path/client/SearchPedPathClient.java @@ -6,6 +6,8 @@ import com.twtw.backend.global.exception.WebClientResponseException; import com.twtw.backend.global.properties.TmapProperties; +import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.client.WebClient; @@ -14,6 +16,7 @@ import java.net.URI; import java.nio.charset.StandardCharsets; +@Slf4j @Component public class SearchPedPathClient extends TmapClient { @@ -25,6 +28,7 @@ public SearchPedPathClient(final TmapProperties tmapProperties) { } @Override + @CircuitBreaker(name = "backend-a", fallbackMethod = "fallback") public SearchPedPathResponse request(SearchPedPathRequest request) { return webClient .post() @@ -42,4 +46,9 @@ private URI getPathUri(final UriBuilder uriBuilder) { final UriBuilder builder = uriBuilder.queryParam("version", 1.1); return builder.build(); } + + public SearchPedPathResponse fallback(final Exception e) { + log.error("SearchPedPathClient fallback", e); + return SearchPedPathResponse.onError(); + } } diff --git a/backend/src/main/java/com/twtw/backend/domain/path/dto/client/car/SearchCarPathResponse.java b/backend/src/main/java/com/twtw/backend/domain/path/dto/client/car/SearchCarPathResponse.java index 79b08f98..57a5184e 100644 --- a/backend/src/main/java/com/twtw/backend/domain/path/dto/client/car/SearchCarPathResponse.java +++ b/backend/src/main/java/com/twtw/backend/domain/path/dto/client/car/SearchCarPathResponse.java @@ -12,8 +12,19 @@ @NoArgsConstructor @AllArgsConstructor public class SearchCarPathResponse { + + private static final SearchCarPathResponse ON_ERROR_RESPONSE = new SearchCarPathResponse( + 0, + "Internal Server Error", + "", + Map.of()); + private int code; private String message; private String currentDateTime; private Map route; + + public static SearchCarPathResponse onError() { + return ON_ERROR_RESPONSE; + } } diff --git a/backend/src/main/java/com/twtw/backend/domain/path/dto/client/ped/SearchPedPathResponse.java b/backend/src/main/java/com/twtw/backend/domain/path/dto/client/ped/SearchPedPathResponse.java index 265c3baf..1cbb9427 100644 --- a/backend/src/main/java/com/twtw/backend/domain/path/dto/client/ped/SearchPedPathResponse.java +++ b/backend/src/main/java/com/twtw/backend/domain/path/dto/client/ped/SearchPedPathResponse.java @@ -12,6 +12,15 @@ @NoArgsConstructor @AllArgsConstructor public class SearchPedPathResponse { + + private static final SearchPedPathResponse ON_ERROR_RESPONSE = new SearchPedPathResponse( + "", + List.of()); + private String type; private List features; + + public static SearchPedPathResponse onError() { + return ON_ERROR_RESPONSE; + } } diff --git a/backend/src/main/java/com/twtw/backend/domain/place/client/SurroundPlaceClient.java b/backend/src/main/java/com/twtw/backend/domain/place/client/SurroundPlaceClient.java index 31a3aa69..ae6de461 100644 --- a/backend/src/main/java/com/twtw/backend/domain/place/client/SurroundPlaceClient.java +++ b/backend/src/main/java/com/twtw/backend/domain/place/client/SurroundPlaceClient.java @@ -7,6 +7,8 @@ import com.twtw.backend.global.exception.WebClientResponseException; import com.twtw.backend.global.properties.KakaoProperties; +import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.util.UriBuilder; @@ -14,6 +16,7 @@ import java.net.URI; import java.nio.charset.StandardCharsets; +@Slf4j @Component public class SurroundPlaceClient extends KakaoMapClient { @@ -27,6 +30,7 @@ public SurroundPlaceClient(final KakaoProperties kakaoProperties) { } @Override + @CircuitBreaker(name = "backend-a", fallbackMethod = "fallback") public SurroundPlaceResponse request(final SurroundPlaceRequest request) { return webClient .get() @@ -38,6 +42,11 @@ public SurroundPlaceResponse request(final SurroundPlaceRequest request) { .orElseThrow(WebClientResponseException::new); } + public SurroundPlaceResponse fallback(final Exception e) { + log.error("SurroundPlaceClient fallback", e); + return SurroundPlaceResponse.onError(); + } + private URI getUri(final SurroundPlaceRequest request, final UriBuilder uriBuilder) { return uriBuilder .path("search/category") diff --git a/backend/src/main/java/com/twtw/backend/domain/place/dto/client/SurroundPlaceResponse.java b/backend/src/main/java/com/twtw/backend/domain/place/dto/client/SurroundPlaceResponse.java index ae29f4d3..2b78d64a 100644 --- a/backend/src/main/java/com/twtw/backend/domain/place/dto/client/SurroundPlaceResponse.java +++ b/backend/src/main/java/com/twtw/backend/domain/place/dto/client/SurroundPlaceResponse.java @@ -13,6 +13,15 @@ @NoArgsConstructor @AllArgsConstructor public class SurroundPlaceResponse { + +private static final SurroundPlaceResponse ON_ERROR_RESPONSE = new SurroundPlaceResponse( + new MetaDetails(true), + List.of()); + private MetaDetails meta; private List documents; + + public static SurroundPlaceResponse onError() { + return ON_ERROR_RESPONSE; + } } diff --git a/backend/src/main/java/com/twtw/backend/domain/plan/client/SearchDestinationClient.java b/backend/src/main/java/com/twtw/backend/domain/plan/client/SearchDestinationClient.java index a7309d1e..075d42fe 100644 --- a/backend/src/main/java/com/twtw/backend/domain/plan/client/SearchDestinationClient.java +++ b/backend/src/main/java/com/twtw/backend/domain/plan/client/SearchDestinationClient.java @@ -5,7 +5,8 @@ import com.twtw.backend.domain.plan.dto.client.SearchDestinationResponse; import com.twtw.backend.global.client.KakaoMapClient; import com.twtw.backend.global.properties.KakaoProperties; - +import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.client.WebClient; @@ -14,6 +15,7 @@ import java.net.URI; import java.nio.charset.StandardCharsets; +@Slf4j @Component public class SearchDestinationClient extends KakaoMapClient { @@ -27,6 +29,7 @@ public SearchDestinationClient(final KakaoProperties kakaoProperties) { } @Override + @CircuitBreaker(name = "backend-a", fallbackMethod = "fallback") public SearchDestinationResponse request(final SearchDestinationRequest request) { return webClient .get() @@ -39,6 +42,11 @@ public SearchDestinationResponse request(final SearchDestinationRequest request) .orElseGet(SearchDestinationResponse::new); } + public SearchDestinationResponse fallback(final Exception e) { + log.error("SearchDestinationClient fallback", e); + return SearchDestinationResponse.onError(); + } + private URI getUri(final SearchDestinationRequest request, final UriBuilder uriBuilder) { final UriBuilder builder = uriBuilder diff --git a/backend/src/main/java/com/twtw/backend/domain/plan/dto/client/SearchDestinationResponse.java b/backend/src/main/java/com/twtw/backend/domain/plan/dto/client/SearchDestinationResponse.java index 8836c60c..85908ea5 100644 --- a/backend/src/main/java/com/twtw/backend/domain/plan/dto/client/SearchDestinationResponse.java +++ b/backend/src/main/java/com/twtw/backend/domain/plan/dto/client/SearchDestinationResponse.java @@ -10,6 +10,15 @@ @NoArgsConstructor @AllArgsConstructor public class SearchDestinationResponse { + + private static final SearchDestinationResponse ON_ERROR_RESPONSE = new SearchDestinationResponse( + new MetaDetails(true), + List.of()); + private MetaDetails meta; private List documents; + + public static SearchDestinationResponse onError() { + return ON_ERROR_RESPONSE; + } } diff --git a/backend/src/main/resources/application-dev.yml b/backend/src/main/resources/application-dev.yml index 0ea28d18..75d4396d 100644 --- a/backend/src/main/resources/application-dev.yml +++ b/backend/src/main/resources/application-dev.yml @@ -43,4 +43,17 @@ management: endpoints: web: exposure: - include: prometheus + include: * + health: + circuitbreakers: + enabled: true +resilience4j.circuitbreaker: + configs: + default: + registerHealthIndicator: true + slidingWindowSize: 10 + failureRateThreshold: 30 + waitDurationInOpenState: 10000 + instances: + backend-a: + base-config: default \ No newline at end of file diff --git a/backend/src/main/resources/application-prod.yml b/backend/src/main/resources/application-prod.yml index 238a9581..f8a3dbd3 100644 --- a/backend/src/main/resources/application-prod.yml +++ b/backend/src/main/resources/application-prod.yml @@ -52,7 +52,20 @@ management: endpoints: web: exposure: - include: prometheus + include: * + health: + circuitbreakers: + enabled: true +resilience4j.circuitbreaker: + configs: + default: + registerHealthIndicator: true + slidingWindowSize: 10 + failureRateThreshold: 30 + waitDurationInOpenState: 10000 + instances: + backend-a: + base-config: default jwt: secret: ${JWT_SECRET} kakao-map: diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml index 3148e2c7..81e2e3dc 100644 --- a/backend/src/test/resources/application.yml +++ b/backend/src/test/resources/application.yml @@ -42,3 +42,13 @@ spring: user: sa password: clean-disabled: false +resilience4j.circuitbreaker: + configs: + default: + registerHealthIndicator: true + slidingWindowSize: 10 + failureRateThreshold: 30 + waitDurationInOpenState: 10000 + instances: + backend-a: + base-config: default \ No newline at end of file From 887d74ce3df9dff5871720c364f6cece139cffb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Sun, 10 Mar 2024 09:25:20 +0900 Subject: [PATCH 28/29] [CHORE] remove test profile settings --- .../main/java/com/twtw/backend/config/socket/StompConfig.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backend/src/main/java/com/twtw/backend/config/socket/StompConfig.java b/backend/src/main/java/com/twtw/backend/config/socket/StompConfig.java index f9fbe13c..02cf6eb7 100644 --- a/backend/src/main/java/com/twtw/backend/config/socket/StompConfig.java +++ b/backend/src/main/java/com/twtw/backend/config/socket/StompConfig.java @@ -1,18 +1,14 @@ package com.twtw.backend.config.socket; import com.twtw.backend.global.properties.RabbitMQProperties; - import lombok.RequiredArgsConstructor; - import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.util.AntPathMatcher; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; -@Profile("!test") @Configuration @RequiredArgsConstructor @EnableWebSocketMessageBroker From 515c8af39695e9db9fc53395aea05317d2a6d2f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Sun, 10 Mar 2024 09:54:59 +0900 Subject: [PATCH 29/29] [FEAT] circuitbreaker config setting --- backend/build.gradle | 2 +- .../config/circuitbreaker/Resilience4jConfig.java | 15 +++++++++++++++ backend/src/main/resources/application-dev.yml | 2 +- backend/src/main/resources/application-prod.yml | 2 +- 4 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 backend/src/main/java/com/twtw/backend/config/circuitbreaker/Resilience4jConfig.java diff --git a/backend/build.gradle b/backend/build.gradle index a9f2cee7..92c33754 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -52,7 +52,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-reactor-netty' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-actuator' - implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.1.0' + implementation 'io.github.resilience4j:resilience4j-spring-boot2:2.2.0' implementation 'io.jsonwebtoken:jjwt-api:0.11.5' implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' diff --git a/backend/src/main/java/com/twtw/backend/config/circuitbreaker/Resilience4jConfig.java b/backend/src/main/java/com/twtw/backend/config/circuitbreaker/Resilience4jConfig.java new file mode 100644 index 00000000..27ded9fe --- /dev/null +++ b/backend/src/main/java/com/twtw/backend/config/circuitbreaker/Resilience4jConfig.java @@ -0,0 +1,15 @@ +package com.twtw.backend.config.circuitbreaker; + +import org.springframework.boot.actuate.health.SimpleStatusAggregator; +import org.springframework.boot.actuate.health.StatusAggregator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class Resilience4jConfig { + + @Bean + public StatusAggregator statusAggregator() { + return new SimpleStatusAggregator(); + } +} diff --git a/backend/src/main/resources/application-dev.yml b/backend/src/main/resources/application-dev.yml index 75d4396d..4ecc0a81 100644 --- a/backend/src/main/resources/application-dev.yml +++ b/backend/src/main/resources/application-dev.yml @@ -43,7 +43,7 @@ management: endpoints: web: exposure: - include: * + include: ["metrics", "prometheus", "circuitbreakers", "circuitbreakerevents"] health: circuitbreakers: enabled: true diff --git a/backend/src/main/resources/application-prod.yml b/backend/src/main/resources/application-prod.yml index f8a3dbd3..c24918cb 100644 --- a/backend/src/main/resources/application-prod.yml +++ b/backend/src/main/resources/application-prod.yml @@ -52,7 +52,7 @@ management: endpoints: web: exposure: - include: * + include: [ "metrics", "prometheus", "circuitbreakers", "circuitbreakerevents" ] health: circuitbreakers: enabled: true