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
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-actuator'

// Kafka
implementation 'org.springframework.kafka:spring-kafka'

// Monitoring & Observability
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'net.logstash.logback:logstash-logback-encoder:7.4'
Expand All @@ -70,6 +73,7 @@ dependencies {
// Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.springframework.kafka:spring-kafka-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

Expand Down
6 changes: 3 additions & 3 deletions src/main/java/com/project/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
35 changes: 18 additions & 17 deletions src/main/java/com/project/controller/HomeController.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.project.controller;

import java.util.Map;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -9,23 +10,23 @@
@RestController
public class HomeController {

@Value("${spring.application.name:backend}")
private String applicationName;
@Value("${spring.application.name:backend}")
private String applicationName;

@Value("${app.version:1.0.0}")
private String version;
@Value("${app.version:1.0.0}")
private String version;

@GetMapping("/")
public ResponseEntity<Map<String, String>> home() {
return ResponseEntity.ok(
Map.of(
"status",
"ok",
"message",
applicationName + " is running",
"version",
version,
"docs",
"/swagger-ui.html"));
}
@GetMapping("/")
public ResponseEntity<Map<String, String>> home() {
return ResponseEntity.ok(
Map.of(
"status",
"ok",
"message",
applicationName + " is running",
"version",
version,
"docs",
"/swagger-ui.html"));
}
}
28 changes: 15 additions & 13 deletions src/main/java/com/project/example/controller/ExampleController.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package com.project.example.controller;

import com.project.example.controller.dto.SaveExampleRequest;
import com.project.example.infra.entity.ExampleEntity;
import com.project.example.service.ExampleService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -12,20 +8,26 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.project.example.controller.dto.SaveExampleRequest;
import com.project.example.infra.entity.ExampleEntity;
import com.project.example.service.ExampleService;

import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/example")
@RequiredArgsConstructor
public class ExampleController {

private final ExampleService exampleService;
private final ExampleService exampleService;

@GetMapping("/{exampleId}")
public ResponseEntity<ExampleEntity> find(@PathVariable Long exampleId) {
return ResponseEntity.ok(exampleService.find(exampleId));
}
@GetMapping("/{exampleId}")
public ResponseEntity<ExampleEntity> find(@PathVariable Long exampleId) {
return ResponseEntity.ok(exampleService.find(exampleId));
}

@PostMapping
public void save(@RequestBody SaveExampleRequest request) {
exampleService.save(request);
}
@PostMapping
public void save(@RequestBody SaveExampleRequest request) {
exampleService.save(request);
}
}
22 changes: 12 additions & 10 deletions src/main/java/com/project/example/infra/entity/ExampleEntity.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.project.example.infra.entity;

import com.project.example.controller.dto.SaveExampleRequest;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

import com.project.example.controller.dto.SaveExampleRequest;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
Expand All @@ -16,16 +18,16 @@
@AllArgsConstructor
public class ExampleEntity {

@Id @GeneratedValue private Long exampleId;
@Id @GeneratedValue private Long exampleId;

private String exampleName;
private String exampleName;

private String exampleContent;
private String exampleContent;

public static ExampleEntity create(SaveExampleRequest request) {
return ExampleEntity.builder()
.exampleName(request.exampleName())
.exampleContent(request.exampleContent())
.build();
}
public static ExampleEntity create(SaveExampleRequest request) {
return ExampleEntity.builder()
.exampleName(request.exampleName())
.exampleContent(request.exampleContent())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.project.example.infra.repository;

import com.project.example.infra.entity.ExampleEntity;
import org.springframework.data.jpa.repository.JpaRepository;

import com.project.example.infra.entity.ExampleEntity;

public interface ExampleJpaRepository extends JpaRepository<ExampleEntity, Long> {}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

public interface ExampleRepository {

ExampleEntity find(Long exampleId);
ExampleEntity find(Long exampleId);

void save(ExampleEntity exampleEntity);
void save(ExampleEntity exampleEntity);
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
package com.project.example.infra.repository;

import org.springframework.stereotype.Repository;

import com.project.example.infra.entity.ExampleEntity;
import com.project.global.exception.ApplicationException;
import com.project.global.exception.code.domain.ExampleErrorCode;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class ExampleRepositoryImpl implements ExampleRepository {

private final ExampleJpaRepository exampleJpaRepository;
private final ExampleJpaRepository exampleJpaRepository;

public ExampleEntity find(Long exampleId) {
return exampleJpaRepository
.findById(exampleId)
.orElseThrow(() -> new ApplicationException(ExampleErrorCode.EXAMPLE_NOT_FOUND));
}
public ExampleEntity find(Long exampleId) {
return exampleJpaRepository
.findById(exampleId)
.orElseThrow(() -> new ApplicationException(ExampleErrorCode.EXAMPLE_NOT_FOUND));
}

public void save(ExampleEntity example) {
exampleJpaRepository.save(example);
}
public void save(ExampleEntity example) {
exampleJpaRepository.save(example);
}
}
26 changes: 14 additions & 12 deletions src/main/java/com/project/example/service/ExampleService.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
package com.project.example.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.project.example.controller.dto.SaveExampleRequest;
import com.project.example.infra.entity.ExampleEntity;
import com.project.example.infra.repository.ExampleRepository;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ExampleService {

private final ExampleRepository exampleRepository;
private final ExampleRepository exampleRepository;

@Transactional
public ExampleEntity find(Long exampleId) {
return exampleRepository.find(exampleId);
}
@Transactional
public ExampleEntity find(Long exampleId) {
return exampleRepository.find(exampleId);
}

@Transactional
public void save(SaveExampleRequest request) {
ExampleEntity exampleEntity = ExampleEntity.create(request);
exampleRepository.save(exampleEntity);
}
@Transactional
public void save(SaveExampleRequest request) {
ExampleEntity exampleEntity = ExampleEntity.create(request);
exampleRepository.save(exampleEntity);
}
}
25 changes: 25 additions & 0 deletions src/main/java/com/project/global/config/KafkaConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.project.global.config;

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.ConsumerFactory;
import org.springframework.kafka.listener.ContainerProperties;

@Configuration
@EnableKafka
public class KafkaConfig {

@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory(
ConsumerFactory<String, String> consumerFactory) {
ConcurrentKafkaListenerContainerFactory<String, String> factory =
new ConcurrentKafkaListenerContainerFactory<>();

factory.setConsumerFactory(consumerFactory);
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL);

return factory;
}
}
73 changes: 73 additions & 0 deletions src/main/java/com/project/global/config/MetricsConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.project.global.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;

@Configuration
public class MetricsConfig {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

CounterTimer 빈을 생성하는 데 상당한 양의 보일러플레이트 코드가 있습니다. 이 코드는 중복을 줄이고 유지보수성을 향상시키기 위해 리팩토링할 수 있습니다.

메트릭 생성 및 등록 로직을 중앙에서 관리하는 팩토리 빈이나 헬퍼 클래스를 만드는 것을 고려해 보세요. 예를 들어, getSuccessCounter(Channel channel)getProcessingTimer(Channel channel)와 같은 메서드를 노출하는 단일 NotificationMetrics 빈을 가질 수 있으며, 존재하지 않을 경우 동적으로 생성할 수 있습니다. 이는 NotificationService에서의 의존성 주입도 단순화할 것입니다.


@Bean
public Counter emailSuccessCounter(MeterRegistry registry) {
return Counter.builder("notification.sent.total")
.tag("channel", "EMAIL")
.tag("status", "SUCCESS")
.description("Total successful email notifications")
.register(registry);
}

@Bean
public Counter emailFailCounter(MeterRegistry registry) {
return Counter.builder("notification.sent.total")
.tag("channel", "EMAIL")
.tag("status", "FAIL")
.description("Total failed email notifications")
.register(registry);
}

@Bean
public Counter smsSuccessCounter(MeterRegistry registry) {
return Counter.builder("notification.sent.total")
.tag("channel", "SMS")
.tag("status", "SUCCESS")
.description("Total successful SMS notifications")
.register(registry);
}

@Bean
public Counter smsFailCounter(MeterRegistry registry) {
return Counter.builder("notification.sent.total")
.tag("channel", "SMS")
.tag("status", "FAIL")
.description("Total failed SMS notifications")
.register(registry);
}

@Bean
public Counter smsFallbackCounter(MeterRegistry registry) {
return Counter.builder("notification.sent.total")
.tag("channel", "SMS")
.tag("status", "FALLBACK")
.description("Total SMS fallback notifications")
.register(registry);
}

@Bean
public Timer emailProcessingTimer(MeterRegistry registry) {
return Timer.builder("notification.processing.time")
.tag("channel", "EMAIL")
.description("Email notification processing time")
.register(registry);
}

@Bean
public Timer smsProcessingTimer(MeterRegistry registry) {
return Timer.builder("notification.processing.time")
.tag("channel", "SMS")
.description("SMS notification processing time")
.register(registry);
}
}
20 changes: 10 additions & 10 deletions src/main/java/com/project/global/config/RedisConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
Loading
Loading