init: 클린 기반 auth 서버 설계

This commit is contained in:
DongHyeonka
2026-07-24 14:30:18 +09:00
parent 471db0203d
commit 8a1ac1e769
3642 changed files with 275893 additions and 1 deletions
+175
View File
@@ -0,0 +1,175 @@
# ApplicationEvent 예시
## 좋은 예시 1: 핵심 작업 후 후속 반응 분리
```java
public record UserRegisteredEvent(
Long userId,
String email,
Instant occurredAt
) {}
@Service
public class RegisterUserService {
private final ApplicationEventPublisher eventPublisher;
private final Clock clock;
public RegisterUserService(ApplicationEventPublisher eventPublisher, Clock clock) {
this.eventPublisher = eventPublisher;
this.clock = clock;
}
@Transactional
public Long register(CreateUserCommand command) {
User user = ...;
userRepository.save(user);
eventPublisher.publishEvent(
new UserRegisteredEvent(user.getId(), user.getEmail(), Instant.now(clock))
);
return user.getId();
}
}
```
**왜 좋은가:**
- 핵심 등록 작업과 후속 반응을 분리한다
- 이벤트 payload가 필요한 상태를 직접 담는다
- publisher가 listener 구현을 모른다
## 좋은 예시 2: commit 후에만 처리
```java
@Component
public class UserRegisteredAuditListener {
@TransactionalEventListener
public void handle(UserRegisteredEvent event) {
auditLog.record("USER_REGISTERED", event.userId(), event.occurredAt());
}
}
```
**왜 좋은가:**
- 기본 AFTER_COMMIT 의미를 활용한다
- rollback된 작업에 대해 잘못된 후속 기록을 남기지 않는다
## 좋은 예시 3: listener는 짧고 부가적
```java
@Component
public class WelcomeMetricListener {
@EventListener
public void handle(UserRegisteredEvent event) {
metrics.counter("user.registered").increment();
}
}
```
**왜 좋은가:**
- 짧고 효율적이다
- 핵심 비즈니스 흐름을 숨기지 않는다
## 좋은 예시 4: 테스트에서 이벤트 검증
```java
@RecordApplicationEvents
@SpringBootTest
class RegisterUserServiceTest {
@Test
void publishes_user_registered_event(ApplicationEvents events) {
service.register(command);
assertThat(events.stream(UserRegisteredEvent.class)).hasSize(1);
}
}
```
**왜 좋은가:**
- 이벤트 발행 사실을 테스트로 확인할 수 있다
- “어딘가에서 되겠지” 상태를 줄인다
## 나쁜 예시 1: 핵심 오케스트레이션을 이벤트에 숨김
```java
@Service
public class LoginService {
public LoginResponse login(LoginCommand command) {
eventPublisher.publishEvent(new LoginRequestedEvent(command));
return LoginResponse.pending();
}
}
```
**문제:**
- 핵심 로그인 흐름이 listener들 뒤로 숨어버린다
- 메인 결과가 이벤트 체인에 의존하게 된다
## 나쁜 예시 2: payload가 너무 빈약함
```java
public record UserRegisteredEvent(Long userId) {}
```
**문제:**
- 모든 listener가 다시 조회를 강요받을 수 있다
- 필요한 최소 상태가 누락되면 결합과 조회 비용이 커진다
**개선:**
- 정말 필요한 상태를 payload에 포함
## 나쁜 예시 3: 무거운 작업을 listener에 직접 넣음
```java
@Component
public class HeavyListener {
@EventListener
public void handle(UserRegisteredEvent event) {
externalApi.call(...);
fileExporter.export(...);
Thread.sleep(5000);
}
}
```
**문제:**
- listener가 너무 무겁고 느리다
- event hand-off의 장점을 해친다
- 장애 반경이 커진다
## 나쁜 예시 4: listener 순서에 핵심 의존
```java
@Component
class FirstListener {
@Order(1)
@EventListener
void handle(UserRegisteredEvent event) { ... }
}
@Component
class SecondListener {
@Order(2)
@EventListener
void handle(UserRegisteredEvent event) { ... } // 첫 번째가 반드시 먼저 돌 것을 기대
}
```
**문제:**
- 이벤트 기반 구조가 사실상 숨은 절차형 흐름이 된다
- 순서 의존이 커질수록 명시적 호출이 더 낫다