Skip to content

Commit

Permalink
Merge pull request #88 from HongDam-org/feat/redis-cache-lock
Browse files Browse the repository at this point in the history
[FEAT] redis 을 통한 lock 적용과 api 캐싱 및 test container 적용
  • Loading branch information
ohksj77 authored Mar 10, 2024
2 parents 196349b + 515c8af commit b0a33c2
Show file tree
Hide file tree
Showing 61 changed files with 886 additions and 202 deletions.
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

0 comments on commit b0a33c2

Please sign in to comment.