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
35 changes: 35 additions & 0 deletions .coderabbit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
language: "ko"
early_access: false
reviews:
profile: "chill"
request_changes_workflow: false
high_level_summary: true
poem: true
review_status: true
collapse_walkthrough: false
auto_review:
enabled: true
drafts: false
path_filters:
- "!**/*.md"
- "!**/docs/**"
- "!**/.github/**"
path_instructions:
- path: "**/*.java"
instructions: |
Review this Java code for:
1. Spring Boot best practices
2. Clean code principles
3. Performance optimizations
4. Security considerations
5. Suggest more elegant solutions using Java features
6. Check for proper exception handling
7. Suggest better naming conventions
- path: "**/build.gradle"
instructions: |
Review Gradle configuration for:
1. Dependency management best practices
2. Build optimization opportunities
3. Plugin usage efficiency
chat:
auto_reply: true
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions .idea/MSA-SpringCloud-Kubernetes.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/git_toolbox_blame.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions api-gateway/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
plugins {
id 'org.springframework.boot' version '3.1.5'
}

dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class ApiGatewayApplication {

public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}

}
32 changes: 32 additions & 0 deletions api-gateway/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
discovery:
locator:
enabled: true
lower-case-service-id: true

server:
port: 8080

eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true

logging:
level:
org.springframework.cloud.gateway: DEBUG
48 changes: 48 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
plugins {
id 'java'
}

allprojects {
group = 'com.example'
version = '0.0.1-SNAPSHOT'

repositories {
mavenCentral()
}
}

subprojects {
apply plugin: 'java'
apply plugin: 'io.spring.dependency-management'

java {
sourceCompatibility = '17'
}

configurations {
compileOnly {
extendsFrom annotationProcessor
}
}

ext {
set('springCloudVersion', "2022.0.4")
}

// 공통 의존성
dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}

tasks.named('test') {
useJUnitPlatform()
}
}
3 changes: 3 additions & 0 deletions common/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}
33 changes: 33 additions & 0 deletions common/src/main/java/com/example/common/BaseEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.example.common;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;

@MappedSuperclass
@Getter
@Setter
public abstract class BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;

@Column(nullable = false)
private LocalDateTime updatedAt;

@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}

@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
}
116 changes: 116 additions & 0 deletions docs/QNA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# 🤔 개발 과정에서의 질문과 답변

## MSA 아키텍처 관련

### Q: Spring Cloud 없이도 MSA가 가능한데 왜 사용하나요?

**A:** Spring Cloud를 사용하는 이유는 다음과 같습니다:

1. **서비스 디스커버리 자동화**
```java
// Spring Cloud 없으면
RestTemplate.getForObject("http://user-service-1:8081/api/users/1", User.class); // 하드코딩

// Spring Cloud 있으면
RestTemplate.getForObject("http://user-service/api/users/1", User.class); // 자동 발견
```

2. **로드밸런싱 자동화**
- 인스턴스 장애 시 자동 전환
- 인스턴스 추가/제거 시 자동 감지

3. **개발 편의성**
```java
@FeignClient(name = "user-service") // 간단한 서비스 간 통신
interface UserClient {
@GetMapping("/api/users/{id}")
User getUser(@PathVariable Long id);
}
```

**결론**: 작은 MSA는 Spring Cloud 없어도 되지만, 서비스가 많아질수록 Spring Cloud의 자동화 기능이 큰 도움이 됩니다.

---

## Gradle 멀티모듈 관련

### Q: 멀티모듈에서 공통 설정은 어떻게 관리하나요?

**A:** `subprojects` 블록을 활용해서 공통 설정을 자동 적용합니다:

```groovy
subprojects {
// 모든 하위 모듈에 자동 적용
dependencies {
compileOnly 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
}
```

**장점**:
- 중복 설정 제거
- 새 서비스 추가 시 최소한의 설정만 필요
- 일관된 의존성 관리

### Q: 모든 서비스에서 공통 모듈을 꼭 사용해야 하나요?

**A:** 아닙니다! 필요한 모듈만 의존성을 추가하면 됩니다:

- **user-service**: `implementation project(':common')` ✅ (BaseEntity 사용)
- **order-service**: `implementation project(':common')` ✅ (BaseEntity 사용)
- **api-gateway**: 의존성 없음 ✅ (BaseEntity 안 씀)

**원칙**: 필요한 모듈만 의존성 추가, 불필요한 의존성은 추가하지 않음

---

## FeignClient vs RestTemplate

### Q: FeignClient를 왜 사용하나요? RestTemplate과 차이는?

**A:** 둘 다 MSA에서 서비스 간 통신에 사용되지만 편의성에 차이가 있습니다:

**RestTemplate (번거로움)**:
```java
val response = restTemplate.getForObject("http://user-service/api/users/$id", UserDto::class.java)
```

**FeignClient (간편함)**:
```java
@FeignClient(name = "user-service")
interface UserClient {
@GetMapping("/api/users/{id}")
UserDto getUserById(@PathVariable Long id);
}

// 사용
val user = userClient.getUserById(id)
```

**결론**: RestTemplate도 가능하지만 FeignClient가 더 선언적이고 간편합니다.

---

## 기술 선택 이유

### Q: Maven 대신 Gradle을 선택한 이유는?

**A:**
- **빌드 속도**: Gradle이 더 빠름
- **문법**: Groovy 문법이 XML보다 간결
- **현대적**: 요즘 Spring 프로젝트에서 더 많이 사용
- **유연성**: 복잡한 빌드 로직 구현 시 더 유연

### Q: Java 대신 Kotlin을 처음에 시도한 이유는?

**A:** Kotlin의 장점을 경험해보고 싶어서였지만, Java와 비교 학습을 위해 Java로 변경했습니다:

**Kotlin 장점**:
- data class로 boilerplate 코드 최소화
- null safety
- 간결한 문법

**Java를 최종 선택한 이유**:
- 두 언어의 차이점을 명확히 비교하기 위함
- Lombok 적용 전후 비교 가능
14 changes: 14 additions & 0 deletions order-service/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
plugins {
id 'org.springframework.boot' version '3.1.5'
}

dependencies {
implementation project(':common')
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'

runtimeOnly 'com.h2database:h2'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.example.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class OrderServiceApplication {

public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}

}
Loading