Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CI

on:
pull_request:
branches: [main]
branches: [main, develop]

jobs:
test:
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ services:
KAFKA_INTER_BROKER_LISTENER_NAME: 'PLAINTEXT'

KAFKA_CONTROLLER_QUORUM_VOTERS: '1@127.0.0.1:9093'
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1

volumes:
- kafka-data-dev:/tmp/kafka-logs
networks:
Expand Down
55 changes: 55 additions & 0 deletions src/main/java/com/catchtable/global/config/SchedulerConfig.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
package com.catchtable.global.config;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.*;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

@value ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์„ค์ •์„ ์™ธ๋ถ€ํ™”ํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ์ž„ํฌํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์‹ญ์‹œ์˜ค.

Suggested change
import org.springframework.kafka.core.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.core.*;

import org.springframework.scheduling.annotation.EnableScheduling;

import java.time.Clock;
import java.time.ZoneId;
import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableScheduling
Expand All @@ -20,4 +29,50 @@ public class SchedulerConfig {
public Clock clock() {
return Clock.system(ZoneId.of("Asia/Seoul"));
}

@EnableKafka
@Configuration
public static class KafkaConfig {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

KafkaConfig๊ฐ€ SchedulerConfig์˜ ๋‚ด๋ถ€ ํด๋ž˜์Šค๋กœ ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์นดํ”„์นด ์„ค์ •๊ณผ ์Šค์ผ€์ค„๋Ÿฌ ์„ค์ •์€ ์„œ๋กœ ๋‹ค๋ฅธ ๊ด€์‹ฌ์‚ฌ์ด๋ฏ€๋กœ, ๋ณ„๋„์˜ ์„ค์ • ํด๋ž˜์Šค๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์œ ์ง€๋ณด์ˆ˜ ์ธก๋ฉด์—์„œ ๋ฐ”๋žŒ์งํ•ฉ๋‹ˆ๋‹ค.


private final String bootstrapServers = "localhost:9092";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

bootstrapServers ์ฃผ์†Œ๊ฐ€ ํ•˜๋“œ์ฝ”๋”ฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ™˜๊ฒฝ๋ณ„๋กœ ์œ ์—ฐํ•˜๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก @value๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ application.yml ์„ค์ • ํŒŒ์ผ์—์„œ ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๋„๋ก ์ˆ˜์ •ํ•˜์‹ญ์‹œ์˜ค.

Suggested change
private final String bootstrapServers = "localhost:9092";
@Value("${spring.kafka.bootstrap-servers:localhost:9092}")
private String bootstrapServers;


@Bean
public KafkaTemplate<String, Object> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}

@Bean
public ProducerFactory<String, Object> producerFactory() {
Map<String, Object> config = new HashMap<>();
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);

// ๐Ÿšจ [์ˆ˜์ •] ์ž„ํฌํŠธ ์—†์ด ์Šคํ”„๋ง ์ œ๊ณต ๋ฌธ์ž์—ด ๊ฒฝ๋กœ๋กœ ์ •ํ™•ํ•˜๊ฒŒ ๊ณ ์ •ํ•ฉ๋‹ˆ๋‹ค.
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.springframework.kafka.support.serializer.JsonSerializer");
return new DefaultKafkaProducerFactory<>(config);
}

@Bean
public ConcurrentKafkaListenerContainerFactory<String, Object> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
return factory;
}

@Bean
public ConsumerFactory<String, Object> consumerFactory() {
Map<String, Object> config = new HashMap<>();
config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
config.put(ConsumerConfig.GROUP_ID_CONFIG, "catchtable-notification-group");
config.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);

config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.springframework.kafka.support.serializer.JsonDeserializer");

config.put("spring.json.trusted.packages", "com.catchtable.notification.event,java.lang.String,java.lang.Object");
config.put("spring.json.use.type.headers", true);

return new DefaultKafkaConsumerFactory<>(config);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package com.catchtable.notification.event;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.NoArgsConstructor;

@Getter
@RequiredArgsConstructor
@NoArgsConstructor
@AllArgsConstructor
public class ReservationCanceledEvent {
private final Long reservationId;
private final Long userId;
private final String storeName;
private final String remainDate;
private final String remainTime;
private Long reservationId;
private Long userId;
private String storeName;
private String remainDate;
private String remainTime;
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package com.catchtable.notification.event;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.NoArgsConstructor;

@Getter
@RequiredArgsConstructor
@NoArgsConstructor
@AllArgsConstructor
public class ReservationChangedEvent {
private final Long newReservationId;
private final Long userId;
private final String storeName;
private final String oldRemainDate;
private final String oldRemainTime;
private final String newRemainDate;
private final String newRemainTime;
private Long newReservationId;
private Long userId;
private String storeName;
private String oldRemainDate;
private String oldRemainTime;
private String newRemainDate;
private String newRemainTime;
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package com.catchtable.notification.event;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.NoArgsConstructor;

@Getter
@RequiredArgsConstructor
@NoArgsConstructor
@AllArgsConstructor
public class ReservationConfirmedEvent {
private final Long reservationId;
private final Long userId;
private final String storeName;
private final String remainDate;
private final String remainTime;
private Long reservationId;
private Long userId;
private String storeName;
private String remainDate;
private String remainTime;
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package com.catchtable.notification.event;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.NoArgsConstructor;

@Getter
@RequiredArgsConstructor
@NoArgsConstructor
@AllArgsConstructor
public class ReservationReminderEvent {
private final Long reservationId;
private final Long userId;
private final String storeName;
private final String remainDate;
private final String remainTime;
private Long reservationId;
private Long userId;
private String storeName;
private String remainDate;
private String remainTime;
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package com.catchtable.notification.event;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.NoArgsConstructor;

@Getter
@RequiredArgsConstructor
@NoArgsConstructor
@AllArgsConstructor
public class ReservationVisitedEvent {
private final Long reservationId;
private final Long userId;
private final String storeName;
private final String remainDate;
private final String remainTime;
private Long reservationId;
private Long userId;
private String storeName;
private String remainDate;
private String remainTime;
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.catchtable.notification.event;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.NoArgsConstructor;

@Getter
@RequiredArgsConstructor
@NoArgsConstructor
@AllArgsConstructor
public class VacancyEvent {
private final Long remainId;
}
private Long remainId;
}
Original file line number Diff line number Diff line change
@@ -1,50 +1,50 @@
package com.catchtable.notification.listener;

import com.catchtable.global.exception.CustomException;
import com.catchtable.global.exception.ErrorCode;
import com.catchtable.notification.entity.NotificationType;
import com.catchtable.notification.event.ReservationCanceledEvent;
import com.catchtable.notification.service.NotificationService;
import com.catchtable.user.entity.User;
import com.catchtable.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;

@Slf4j
@Component
@RequiredArgsConstructor
public class ReservationCanceledNotificationListener {

private final NotificationService notificationService;
private final UserRepository userRepository;

@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void handleReservationCanceledEvent(ReservationCanceledEvent event) {
User user = userRepository.findById(event.getUserId())
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));

String title = "์˜ˆ์•ฝ์ด ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.";
String content = String.format("'%s' ๋งค์žฅ %s %s ์˜ˆ์•ฝ์ด ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
event.getStoreName(),
event.getRemainDate(),
event.getRemainTime());

notificationService.createNotification(
user,
NotificationType.RESERVATION_CANCELED,
title,
content,
event.getReservationId()
);

log.info("[์˜ˆ์•ฝ ์ทจ์†Œ ์•Œ๋ฆผ] userId: {}, reservationId: {} ์•Œ๋ฆผ ์ƒ์„ฑ ์™„๋ฃŒ", event.getUserId(), event.getReservationId());
}
}
//package com.catchtable.notification.listener;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

์นดํ”„์นด ๋„์ž…์œผ๋กœ ์ธํ•ด ๋” ์ด์ƒ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ธฐ์กด ๋ฆฌ์Šค๋„ˆ ์ฝ”๋“œ๊ฐ€ ์ฃผ์„ ์ฒ˜๋ฆฌ๋œ ์ฑ„๋กœ ๋‚จ์•„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ ๋ฒ ์ด์Šค์˜ ์ฒญ๊ฒฐํ•จ์„ ์œ„ํ•ด ์ฃผ์„ ์ฒ˜๋ฆฌ๋œ ํŒŒ์ผ์€ ์‚ญ์ œํ•˜๊ฑฐ๋‚˜ ๋‚ด์šฉ์„ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. (๋‹ค๋ฅธ ๋ฆฌ์Šค๋„ˆ ํŒŒ์ผ๋“ค๋„ ๋™์ผํ•˜๊ฒŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.)

//
//import com.catchtable.global.exception.CustomException;
//import com.catchtable.global.exception.ErrorCode;
//import com.catchtable.notification.entity.NotificationType;
//import com.catchtable.notification.event.ReservationCanceledEvent;
//import com.catchtable.notification.service.NotificationService;
//import com.catchtable.user.entity.User;
//import com.catchtable.user.repository.UserRepository;
//import lombok.RequiredArgsConstructor;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.scheduling.annotation.Async;
//import org.springframework.stereotype.Component;
//import org.springframework.transaction.annotation.Propagation;
//import org.springframework.transaction.annotation.Transactional;
//import org.springframework.transaction.event.TransactionPhase;
//import org.springframework.transaction.event.TransactionalEventListener;
//
//@Slf4j
//@Component
//@RequiredArgsConstructor
//public class ReservationCanceledNotificationListener {
//
// private final NotificationService notificationService;
// private final UserRepository userRepository;
//
// @Async
// @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
// @Transactional(propagation = Propagation.REQUIRES_NEW)
// public void handleReservationCanceledEvent(ReservationCanceledEvent event) {
// User user = userRepository.findById(event.getUserId())
// .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
//
// String title = "์˜ˆ์•ฝ์ด ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.";
// String content = String.format("'%s' ๋งค์žฅ %s %s ์˜ˆ์•ฝ์ด ์ทจ์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
// event.getStoreName(),
// event.getRemainDate(),
// event.getRemainTime());
//
// notificationService.createNotification(
// user,
// NotificationType.RESERVATION_CANCELED,
// title,
// content,
// event.getReservationId()
// );
//
// log.info("[์˜ˆ์•ฝ ์ทจ์†Œ ์•Œ๋ฆผ] userId: {}, reservationId: {} ์•Œ๋ฆผ ์ƒ์„ฑ ์™„๋ฃŒ", event.getUserId(), event.getReservationId());
// }
//}
Loading
Loading