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

Feature: 결제 완료 시 RunningRoom 에서 사용자가 나갈 수 있도록 이벤트를 발행한다. #119

Merged
merged 13 commits into from
Aug 21, 2024
Merged
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
# src 아래 yml파일 잠금
**/src/**/*.yml
**/src/main/**/*.yml
backend-config/*.yml

### IntelliJ IDEA ###
Expand Down
2 changes: 1 addition & 1 deletion backend-config
10 changes: 2 additions & 8 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'io.micrometer:micrometer-registry-prometheus'

// Redisson
implementation 'org.redisson:redisson-spring-boot-starter:3.17.6'
Expand All @@ -60,6 +61,7 @@ dependencies {
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
testImplementation "org.testcontainers:testcontainers:1.20.1"
testImplementation 'org.testcontainers:mysql:1.20.1'
testImplementation 'org.testcontainers:junit-jupiter:1.20.1'
}

Expand Down Expand Up @@ -109,14 +111,6 @@ tasks.register('copyYml') {
}
into("src/main/resources")
}

copy {
duplicatesStrategy = dupStr
from(src) {
include(fileExt)
}
into("src/test/resources")
}
}

build {
Expand Down
4 changes: 2 additions & 2 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/version: '3.8'
version: '3.8'
services:
mysql:
image: mysql:8.0.32
hostname: mysql
container_name: mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: demo
MYSQL_DATABASE: ticketing
ports:
- "3306:3306"
restart: always
Expand Down
10 changes: 10 additions & 0 deletions scripts/local-end.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash

# 현재 스크립트의 디렉토리로 이동
cd "$(dirname "$0")"

# Docker Compose 파일이 있는 디렉토리로 이동
cd ../docker

# Docker Compose를 사용하여 서비스 종료
docker-compose -f docker-compose.yml down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@

import com.thirdparty.ticketing.domain.ItemResult;
import com.thirdparty.ticketing.domain.common.LoginMember;
import com.thirdparty.ticketing.domain.ticket.dto.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.TicketElement;
import com.thirdparty.ticketing.domain.ticket.dto.TicketPaymentRequest;
import com.thirdparty.ticketing.domain.ticket.dto.request.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.request.TicketPaymentRequest;
import com.thirdparty.ticketing.domain.ticket.dto.response.TicketElement;
import com.thirdparty.ticketing.domain.ticket.service.ReservationService;
import com.thirdparty.ticketing.domain.ticket.service.TicketService;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.thirdparty.ticketing.domain.ticket.dto.event;

import com.thirdparty.ticketing.domain.common.Event;

import lombok.Data;

@Data
public class PaymentEvent implements Event {
private final String email;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.thirdparty.ticketing.domain.ticket.dto;
package com.thirdparty.ticketing.domain.ticket.dto.request;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.thirdparty.ticketing.domain.ticket.dto;
package com.thirdparty.ticketing.domain.ticket.dto.request;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.thirdparty.ticketing.domain.ticket.dto;
package com.thirdparty.ticketing.domain.ticket.dto.response;

import java.util.UUID;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.thirdparty.ticketing.domain.ticket.service;

import com.thirdparty.ticketing.domain.ticket.dto.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.TicketPaymentRequest;
import com.thirdparty.ticketing.domain.ticket.dto.request.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.request.TicketPaymentRequest;

public interface ReservationService {
void selectSeat(String memberEmail, SeatSelectionRequest seatSelectionRequest);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
import org.springframework.transaction.annotation.Transactional;

import com.thirdparty.ticketing.domain.common.ErrorCode;
import com.thirdparty.ticketing.domain.common.EventPublisher;
import com.thirdparty.ticketing.domain.common.TicketingException;
import com.thirdparty.ticketing.domain.member.Member;
import com.thirdparty.ticketing.domain.member.repository.MemberRepository;
import com.thirdparty.ticketing.domain.payment.PaymentProcessor;
import com.thirdparty.ticketing.domain.payment.dto.PaymentRequest;
import com.thirdparty.ticketing.domain.seat.Seat;
import com.thirdparty.ticketing.domain.ticket.dto.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.TicketPaymentRequest;
import com.thirdparty.ticketing.domain.ticket.dto.event.PaymentEvent;
import com.thirdparty.ticketing.domain.ticket.dto.request.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.request.TicketPaymentRequest;
import com.thirdparty.ticketing.domain.ticket.service.strategy.LockSeatStrategy;

import lombok.RequiredArgsConstructor;
Expand All @@ -22,6 +24,7 @@ public class ReservationTransactionService implements ReservationService {
private final MemberRepository memberRepository;
private final PaymentProcessor paymentProcessor;
private final LockSeatStrategy lockSeatStrategy;
private final EventPublisher eventPublisher;

@Override
@Transactional
Expand Down Expand Up @@ -55,12 +58,18 @@ public void reservationTicket(String memberEmail, TicketPaymentRequest ticketPay
.findByEmail(memberEmail)
.orElseThrow(() -> new TicketingException(ErrorCode.NOT_FOUND_MEMBER));

seat.markAsPendingPayment();
paymentProcessor.processPayment(new PaymentRequest());
seat.markAsPaid();
processPayment(seat, loginMember);

if (seat.isAssignedByMember(loginMember)) {
throw new TicketingException(ErrorCode.NOT_SELECTABLE_SEAT);
}
}

private void processPayment(Seat seat, Member loginMember) {
seat.markAsPendingPayment();
paymentProcessor.processPayment(new PaymentRequest());
seat.markAsPaid();
PaymentEvent paymentEvent = new PaymentEvent(loginMember.getEmail());
eventPublisher.publish(paymentEvent);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import com.thirdparty.ticketing.domain.common.TicketingException;
import com.thirdparty.ticketing.domain.member.Member;
import com.thirdparty.ticketing.domain.member.repository.MemberRepository;
import com.thirdparty.ticketing.domain.ticket.dto.TicketElement;
import com.thirdparty.ticketing.domain.ticket.dto.response.TicketElement;
import com.thirdparty.ticketing.domain.ticket.repository.TicketRepository;

import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import com.thirdparty.ticketing.domain.common.ErrorCode;
import com.thirdparty.ticketing.domain.common.LettuceRepository;
import com.thirdparty.ticketing.domain.common.TicketingException;
import com.thirdparty.ticketing.domain.ticket.dto.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.TicketPaymentRequest;
import com.thirdparty.ticketing.domain.ticket.dto.request.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.request.TicketPaymentRequest;
import com.thirdparty.ticketing.domain.ticket.service.ReservationTransactionService;

import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

import com.thirdparty.ticketing.domain.common.ErrorCode;
import com.thirdparty.ticketing.domain.common.TicketingException;
import com.thirdparty.ticketing.domain.ticket.dto.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.TicketPaymentRequest;
import com.thirdparty.ticketing.domain.ticket.dto.request.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.request.TicketPaymentRequest;
import com.thirdparty.ticketing.domain.ticket.service.ReservationTransactionService;

import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

import com.thirdparty.ticketing.domain.common.ErrorCode;
import com.thirdparty.ticketing.domain.common.TicketingException;
import com.thirdparty.ticketing.domain.ticket.dto.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.TicketPaymentRequest;
import com.thirdparty.ticketing.domain.ticket.dto.request.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.request.TicketPaymentRequest;
import com.thirdparty.ticketing.domain.ticket.service.ReservationTransactionService;

import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

import com.thirdparty.ticketing.domain.common.ErrorCode;
import com.thirdparty.ticketing.domain.common.TicketingException;
import com.thirdparty.ticketing.domain.ticket.dto.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.TicketPaymentRequest;
import com.thirdparty.ticketing.domain.ticket.dto.request.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.request.TicketPaymentRequest;
import com.thirdparty.ticketing.domain.ticket.service.ReservationTransactionService;

import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import com.thirdparty.ticketing.domain.common.EventPublisher;
import com.thirdparty.ticketing.domain.common.LettuceRepository;
import com.thirdparty.ticketing.domain.member.repository.MemberRepository;
import com.thirdparty.ticketing.domain.payment.PaymentProcessor;
Expand Down Expand Up @@ -51,29 +52,32 @@ ReservationService pessimisticReservationServiceProxy(
public ReservationTransactionService cacheReservationTransactionService(
PaymentProcessor paymentProcessor,
MemberRepository memberRepository,
SeatRepository seatRepository) {
SeatRepository seatRepository,
EventPublisher eventPublisher) {
LockSeatStrategy lockSeatStrategy = new NaiveSeatStrategy(seatRepository);
return new ReservationTransactionService(
memberRepository, paymentProcessor, lockSeatStrategy);
memberRepository, paymentProcessor, lockSeatStrategy, eventPublisher);
}

@Bean
public ReservationTransactionService persistenceOptimisticReservationService(
PaymentProcessor paymentProcessor,
MemberRepository memberRepository,
SeatRepository seatRepository) {
SeatRepository seatRepository,
EventPublisher eventPublisher) {
LockSeatStrategy lockSeatStrategy = new OptimisticLockSeatStrategy(seatRepository);
return new ReservationTransactionService(
memberRepository, paymentProcessor, lockSeatStrategy);
memberRepository, paymentProcessor, lockSeatStrategy, eventPublisher);
}

@Bean
public ReservationTransactionService persistencePessimisticReservationService(
PaymentProcessor paymentProcessor,
MemberRepository memberRepository,
SeatRepository seatRepository) {
SeatRepository seatRepository,
EventPublisher eventPublisher) {
LockSeatStrategy lockSeatStrategy = new PessimisticLockSeatStrategy(seatRepository);
return new ReservationTransactionService(
memberRepository, paymentProcessor, lockSeatStrategy);
memberRepository, paymentProcessor, lockSeatStrategy, eventPublisher);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import com.thirdparty.ticketing.domain.member.MemberRole;
import com.thirdparty.ticketing.domain.member.service.JwtProvider;
Expand Down Expand Up @@ -55,18 +55,21 @@ public SecurityFilterChain filterChain(HttpSecurity http, JwtProvider jwtProvide
.addFilterBefore(
new AuthenticationFilter(jwtProvider),
UsernamePasswordAuthenticationFilter.class)
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.build();
}

private CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("http://localhost:8080"));
configuration.setAllowedMethods(
List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:3000/"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
config.setAllowCredentials(true);
config.setAllowedHeaders(List.of("*"));
config.setExposedHeaders(List.of("Authorization"));
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import com.thirdparty.ticketing.domain.seat.SeatStatus;
import com.thirdparty.ticketing.domain.seat.repository.SeatGradeRepository;
import com.thirdparty.ticketing.domain.seat.repository.SeatRepository;
import com.thirdparty.ticketing.domain.ticket.dto.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.request.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.zone.Zone;
import com.thirdparty.ticketing.domain.zone.repository.ZoneRepository;
import com.thirdparty.ticketing.support.TestContainerStarter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
import org.springframework.test.context.jdbc.SqlConfig;

import com.thirdparty.ticketing.domain.common.TicketingException;
import com.thirdparty.ticketing.domain.ticket.dto.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.TicketPaymentRequest;
import com.thirdparty.ticketing.domain.ticket.dto.request.SeatSelectionRequest;
import com.thirdparty.ticketing.domain.ticket.dto.request.TicketPaymentRequest;
import com.thirdparty.ticketing.support.TestContainerStarter;

@SpringBootTest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.junit.jupiter.Testcontainers;

@Testcontainers
Expand All @@ -12,14 +13,36 @@ public class TestContainerStarter {
private static final int REDIS_PORT = 6379;
private static final GenericContainer<?> REDIS;

private static final String MYSQL_IMAGE = "mysql:8.0.32";
private static final int MYSQL_PORT = 3306;
private static final MySQLContainer<?> MYSQL;

static {
REDIS = new GenericContainer<>(REDIS_IMAGE).withExposedPorts(REDIS_PORT).withReuse(true);
REDIS.start();

MYSQL =
new MySQLContainer<>(MYSQL_IMAGE)
.withExposedPorts(MYSQL_PORT)
.withUsername("root")
.withPassword("root")
.withDatabaseName("ticketing")
.withReuse(false);
MYSQL.start();
}

@DynamicPropertySource
private static void registerRedisProperties(DynamicPropertyRegistry registry) {
private static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("spring.data.redis.host", REDIS::getHost);
registry.add("spring.data.redis.port", () -> REDIS.getMappedPort(REDIS_PORT).toString());

String mysqlUrl =
String.format(
"jdbc:mysql://%s:%s/ticketing",
MYSQL.getHost(), MYSQL.getMappedPort(MYSQL_PORT));

registry.add("spring.datasource.url", () -> mysqlUrl);
registry.add("spring.datasource.username", () -> "root");
registry.add("spring.datasource.password", () -> "root");
}
}
18 changes: 18 additions & 0 deletions src/test/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
spring:
data:
redis:
host: localhost
port: 6379
datasource:
url: jdbc:mysql://localhost:3306/ticketing
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: create-drop

jwt:
issuer: test
expiry-seconds: 1800
secret: thisisjusttestaccesssecretsodontworry
Loading