Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] redis를 통한 lock 적용과 api 캐싱 및 test container 적용 #88

Merged
merged 29 commits into from
Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
29dacde
[FEAT] redis를 통한 api 캐싱과 lock 적용
ohksj77 Mar 8, 2024
d97565c
[TEST] 테스트 컨테이너 redis와 rabbitmq에 적용
ohksj77 Mar 8, 2024
c93aed7
[CHORE] github actions 단계 수정
ohksj77 Mar 8, 2024
0f89fc2
[FEAT] 수동 조회 api에 cache evict 적용
ohksj77 Mar 8, 2024
ae95f68
[HOTFIX] rename github actions env variables
ohksj77 Mar 8, 2024
57104f8
[HOTFIX] fix github actions backend directory
ohksj77 Mar 8, 2024
063b523
[TEST] test 에러 수정
ohksj77 Mar 8, 2024
299cdf7
[TEST] add jacoco test command
ohksj77 Mar 8, 2024
9e2c761
[FIX] fix jacoco test error
ohksj77 Mar 8, 2024
9f428ae
[FIX] add missing env files
ohksj77 Mar 8, 2024
e151892
[FIX] github actions jacoco test error
ohksj77 Mar 8, 2024
ae2f908
[FIX] gcloud env error
ohksj77 Mar 8, 2024
80c492e
[FIX] firebase app init error
ohksj77 Mar 8, 2024
4b63211
[FIX] return temp instance
ohksj77 Mar 8, 2024
965ef5f
[FIX] github actions error
ohksj77 Mar 8, 2024
365d6d7
[FIX] error on ci
ohksj77 Mar 8, 2024
74bfc84
[FIX] change file read logic
ohksj77 Mar 8, 2024
ba91952
[FIX] fixing fcm config
ohksj77 Mar 8, 2024
c152000
[FIX] error fixing
ohksj77 Mar 8, 2024
411cc3f
[FIX] objectmapper to parse json with value containing backslash
ohksj77 Mar 8, 2024
007eafc
[FIX] change to use key value
ohksj77 Mar 8, 2024
c94bb4f
[CHORE] fix jacoco report flow
ohksj77 Mar 8, 2024
c427365
[FIX] add token to github actions flow
ohksj77 Mar 8, 2024
695de59
[FIX] add verify flow for test
ohksj77 Mar 8, 2024
6e34091
[FIX] error fix
ohksj77 Mar 8, 2024
c66c9af
[CHORE] change to use pat in actions flow
ohksj77 Mar 8, 2024
4c1cc06
[FEAT] add circuitbreaker pattern with resilience4j
ohksj77 Mar 9, 2024
887d74c
[CHORE] remove test profile settings
ohksj77 Mar 10, 2024
515c8af
[FEAT] circuitbreaker config setting
ohksj77 Mar 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions .github/workflows/backend-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ name: TWTW Backend CD
on:
workflow_run:
branches:
- "master"
- "master"
workflows: ["TWTW Backend CI", "TWTW Nginx Build"]
types:
- completed
- completed

jobs:
server-deploy:
Expand All @@ -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
27 changes: 15 additions & 12 deletions .github/workflows/backend-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,28 @@ 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: |
mkdir -p src/test/resources
echo "${{ secrets.ENVIRONMENT }}" | base64 --decode > src/main/resources/application-env.yml
find src

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.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
- run: ./gradlew build
- run: ./gradlew jib

- name: Test Coverage Report
- name: Check test coverage
id: jacoco
uses: madrapps/[email protected]
with:
paths: ${{ github.workspace }}/build/reports/jacoco/test/jacocoTestReport.xml
token: ${{ secrets.GITHUB_TOKEN }}
title: "test coverage"
min-coverage-overall: 60
min-coverage-changed-files: 60
paths: ${{ github.workspace }}/backend/build/reports/jacoco/test/jacocoTestReport.xml
token: ${{ secrets.FLOWS_PAT }}
title: "Test Coverage"
min-coverage-overall: 50
min-coverage-changed-files: 50
8 changes: 4 additions & 4 deletions .github/workflows/nginx-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
backend/src/main/resources/application-env.yml
.env
secret.yml
data/
1 change: 1 addition & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ out/

*.html
application-env.yml
*.json
19 changes: 11 additions & 8 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,21 @@ 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-boot2:2.2.0'

implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
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'
implementation 'com.google.firebase:firebase-admin:6.8.1'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
implementation 'org.redisson:redisson-spring-data-30:3.27.1'

annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"

Expand All @@ -82,6 +87,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'
}
Expand All @@ -96,14 +105,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/**/controller/advice/**",
"com/twtw/backend/domain/**/dto/**"]
}
finalizedBy 'jacocoTestReport'
}

jacoco {
Expand All @@ -123,6 +125,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/**"])
}))
Expand Down
9 changes: 9 additions & 0 deletions backend/src/docs/friend.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,28 @@
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;
import com.google.firebase.messaging.FirebaseMessaging;
import com.twtw.backend.global.properties.FirebaseProperties;

import lombok.RequiredArgsConstructor;

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;

@Bean
public FirebaseApp firebaseApp() throws IOException {
FirebaseOptions options =
new FirebaseOptions.Builder()
.setCredentials(
GoogleCredentials.fromStream(
new ClassPathResource(firebaseProperties.getLocation())
.getInputStream()))
.build();
public FirebaseApp firebaseApp() {
final FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.create(new AccessToken(firebaseProperties.getKey(), null)))
.build();

if (FirebaseApp.getApps().isEmpty()) {
return FirebaseApp.initializeApp(options);
Expand All @@ -37,7 +31,7 @@ public FirebaseApp firebaseApp() throws IOException {
}

@Bean
public FirebaseMessaging firebaseMessaging() throws IOException {
public FirebaseMessaging firebaseMessaging() {
return FirebaseMessaging.getInstance(firebaseApp());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down
Loading
Loading