nti설계.md
· 6.4 KiB · Markdown
Bruto
지금 구조는 **1:N 관계 (알림 1건 ↔ 처리내역 여러 건)** 이므로,
이건 단순 “속성”이 아니라 **하위 리소스(sub-resource)** 로 취급해야 합니다.
즉, “NTI(알림)”가 상위 도메인, “처리내역”은 NTI 하위에 종속된 도메인으로 보아야 합니다.
아래는 **REST 설계, DTO 분리, 서비스 구조** 모두를 기준으로 한 권장 설계안입니다.
---
## 1. 도메인 관계 요약
| 개체 | 설명 | 관계 |
| ------------ | ----------- | ------ |
| `Nti` | 알림 (상위 도메인) | 1 |
| `NtiProcess` | 알림의 처리내역 | N (하위) |
예시:
```
Nti(id=100, title="재난 문자")
├── NtiProcess(id=1, status="발송대기", updatedAt="2025-10-31")
├── NtiProcess(id=2, status="발송완료", updatedAt="2025-10-31")
└── ...
```
---
## 2. REST API 설계
### (1) 상위 도메인: 알림
| 기능 | Method | URI | 설명 |
| -------- | ------ | --------------------- | -------- |
| 알림 목록 조회 | `GET` | `/api/v1/nti` | 알림 전체 목록 |
| 알림 상세 조회 | `GET` | `/api/v1/nti/{ntiId}` | 알림 단건 조회 |
| 알림 생성 | `POST` | `/api/v1/nti` | 새 알림 생성 |
### (2) 하위 리소스: 알림의 처리내역
| 기능 | Method | URI | 설명 |
| ----------------- | ------ | ------------------------------------------- | --------------- |
| 특정 알림의 처리내역 목록 조회 | `GET` | `/api/v1/nti/{ntiId}/processes` | 특정 알림의 처리내역 리스트 |
| 특정 처리내역 조회 | `GET` | `/api/v1/nti/{ntiId}/processes/{processId}` | 특정 내역 상세 |
| 처리내역 추가 | `POST` | `/api/v1/nti/{ntiId}/processes` | 해당 알림에 내역 추가 |
---
## 3. Controller 구조
```java
@RestController
@RequestMapping("/api/v1/nti")
@RequiredArgsConstructor
@Slf4j
public class NtiController {
private final NtiService ntiService;
@GetMapping
public ResponseEntity<List<NtiListResponse>> getAllNti() {
return ResponseEntity.ok(ntiService.getAll());
}
@GetMapping("/{ntiId}")
public ResponseEntity<NtiDetailDto> getNti(@PathVariable Long ntiId) {
return ResponseEntity.ok(ntiService.getById(ntiId));
}
}
```
```java
@RestController
@RequestMapping("/api/v1/nti/{ntiId}/processes")
@RequiredArgsConstructor
@Slf4j
public class NtiProcessController {
private final NtiProcessService ntiProcessService;
@GetMapping
public ResponseEntity<List<NtiProcessDto>> getProcesses(@PathVariable Long ntiId) {
return ResponseEntity.ok(ntiProcessService.getProcessesByNtiId(ntiId));
}
@GetMapping("/{processId}")
public ResponseEntity<NtiProcessDto> getProcess(
@PathVariable Long ntiId,
@PathVariable Long processId
) {
return ResponseEntity.ok(ntiProcessService.getProcessDetail(ntiId, processId));
}
@PostMapping
public ResponseEntity<Void> addProcess(
@PathVariable Long ntiId,
@RequestBody NtiProcessRequest request
) {
ntiProcessService.addProcess(ntiId, request);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
}
```
---
## 4. DTO 구조
### Nti (상위)
```java
public class NtiDetailDto {
private Long id;
private String title;
private String classification;
private LocalDateTime createdAt;
private List<NtiProcessDto> processes; // 옵션: 포함 조회용
}
```
### NtiProcess (하위)
```java
public class NtiProcessDto {
private Long id;
private String status;
private String handler;
private String comment;
private LocalDateTime updatedAt;
}
```
요청용 DTO
```java
public class NtiProcessRequest {
private String status;
private String handler;
private String comment;
}
```
---
## 5. 서비스 및 패키지 구조
```
com.example.nti
├── controller
│ ├── NtiController.java
│ └── NtiProcessController.java
├── service
│ ├── NtiService.java
│ └── NtiProcessService.java
├── dto
│ ├── NtiListResponse.java
│ ├── NtiDetailDto.java
│ ├── NtiProcessDto.java
│ └── NtiProcessRequest.java
├── repository
│ ├── NtiRepository.java
│ └── NtiProcessRepository.java
└── entity
├── Nti.java
└── NtiProcess.java
```
---
## 6. 데이터 모델 예시 (JPA)
```java
@Entity
public class Nti {
@Id @GeneratedValue
private Long id;
private String title;
private String classification;
@OneToMany(mappedBy = "nti", cascade = CascadeType.ALL, orphanRemoval = true)
private List<NtiProcess> processes = new ArrayList<>();
}
```
```java
@Entity
public class NtiProcess {
@Id @GeneratedValue
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "nti_id")
private Nti nti;
private String status;
private String handler;
private String comment;
private LocalDateTime updatedAt;
}
```
---
## 7. 설계 요약
| 구분 | 설계 방식 | URI 예시 |
| ---------- | ------------------------------------ | ------------------------------------------- |
| 상위 리소스 | NTI (알림) | `/api/v1/nti/{ntiId}` |
| 하위 리소스 | 처리내역 | `/api/v1/nti/{ntiId}/processes` |
| 단건 처리내역 조회 | ID 기반 | `/api/v1/nti/{ntiId}/processes/{processId}` |
| DTO | `NtiProcessDto`, `NtiProcessRequest` | 속성 분리 |
---
## 8. 핵심 요약 문장
> 알림 하위의 처리내역은 `Nti`의 속성이 아니라 `NtiProcess`라는 독립된 **하위 리소스(Sub-resource)** 로 분리해야 하며,
> `/api/v1/nti/{ntiId}/processes` 형태로 접근하도록 설계하는 것이 RESTful하고 확장성에 유리합니다.
---
원하신다면
“처리내역이 단순 로그 형태인지, 상태 전이(예: 진행중→완료)인지” 알려주시면
그 흐름에 맞는 **상태 기반 도메인 설계(state transition model)** 까지 구체화해드릴 수 있습니다.
그 부분 포함해서 볼까요?
지금 구조는 1:N 관계 (알림 1건 ↔ 처리내역 여러 건) 이므로, 이건 단순 “속성”이 아니라 하위 리소스(sub-resource) 로 취급해야 합니다.
즉, “NTI(알림)”가 상위 도메인, “처리내역”은 NTI 하위에 종속된 도메인으로 보아야 합니다. 아래는 REST 설계, DTO 분리, 서비스 구조 모두를 기준으로 한 권장 설계안입니다.
1. 도메인 관계 요약
| 개체 | 설명 | 관계 |
|---|---|---|
Nti |
알림 (상위 도메인) | 1 |
NtiProcess |
알림의 처리내역 | N (하위) |
예시:
Nti(id=100, title="재난 문자")
├── NtiProcess(id=1, status="발송대기", updatedAt="2025-10-31")
├── NtiProcess(id=2, status="발송완료", updatedAt="2025-10-31")
└── ...
2. REST API 설계
(1) 상위 도메인: 알림
| 기능 | Method | URI | 설명 |
|---|---|---|---|
| 알림 목록 조회 | GET |
/api/v1/nti |
알림 전체 목록 |
| 알림 상세 조회 | GET |
/api/v1/nti/{ntiId} |
알림 단건 조회 |
| 알림 생성 | POST |
/api/v1/nti |
새 알림 생성 |
(2) 하위 리소스: 알림의 처리내역
| 기능 | Method | URI | 설명 |
|---|---|---|---|
| 특정 알림의 처리내역 목록 조회 | GET |
/api/v1/nti/{ntiId}/processes |
특정 알림의 처리내역 리스트 |
| 특정 처리내역 조회 | GET |
/api/v1/nti/{ntiId}/processes/{processId} |
특정 내역 상세 |
| 처리내역 추가 | POST |
/api/v1/nti/{ntiId}/processes |
해당 알림에 내역 추가 |
3. Controller 구조
@RestController
@RequestMapping("/api/v1/nti")
@RequiredArgsConstructor
@Slf4j
public class NtiController {
private final NtiService ntiService;
@GetMapping
public ResponseEntity<List<NtiListResponse>> getAllNti() {
return ResponseEntity.ok(ntiService.getAll());
}
@GetMapping("/{ntiId}")
public ResponseEntity<NtiDetailDto> getNti(@PathVariable Long ntiId) {
return ResponseEntity.ok(ntiService.getById(ntiId));
}
}
@RestController
@RequestMapping("/api/v1/nti/{ntiId}/processes")
@RequiredArgsConstructor
@Slf4j
public class NtiProcessController {
private final NtiProcessService ntiProcessService;
@GetMapping
public ResponseEntity<List<NtiProcessDto>> getProcesses(@PathVariable Long ntiId) {
return ResponseEntity.ok(ntiProcessService.getProcessesByNtiId(ntiId));
}
@GetMapping("/{processId}")
public ResponseEntity<NtiProcessDto> getProcess(
@PathVariable Long ntiId,
@PathVariable Long processId
) {
return ResponseEntity.ok(ntiProcessService.getProcessDetail(ntiId, processId));
}
@PostMapping
public ResponseEntity<Void> addProcess(
@PathVariable Long ntiId,
@RequestBody NtiProcessRequest request
) {
ntiProcessService.addProcess(ntiId, request);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
}
4. DTO 구조
Nti (상위)
public class NtiDetailDto {
private Long id;
private String title;
private String classification;
private LocalDateTime createdAt;
private List<NtiProcessDto> processes; // 옵션: 포함 조회용
}
NtiProcess (하위)
public class NtiProcessDto {
private Long id;
private String status;
private String handler;
private String comment;
private LocalDateTime updatedAt;
}
요청용 DTO
public class NtiProcessRequest {
private String status;
private String handler;
private String comment;
}
5. 서비스 및 패키지 구조
com.example.nti
├── controller
│ ├── NtiController.java
│ └── NtiProcessController.java
├── service
│ ├── NtiService.java
│ └── NtiProcessService.java
├── dto
│ ├── NtiListResponse.java
│ ├── NtiDetailDto.java
│ ├── NtiProcessDto.java
│ └── NtiProcessRequest.java
├── repository
│ ├── NtiRepository.java
│ └── NtiProcessRepository.java
└── entity
├── Nti.java
└── NtiProcess.java
6. 데이터 모델 예시 (JPA)
@Entity
public class Nti {
@Id @GeneratedValue
private Long id;
private String title;
private String classification;
@OneToMany(mappedBy = "nti", cascade = CascadeType.ALL, orphanRemoval = true)
private List<NtiProcess> processes = new ArrayList<>();
}
@Entity
public class NtiProcess {
@Id @GeneratedValue
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "nti_id")
private Nti nti;
private String status;
private String handler;
private String comment;
private LocalDateTime updatedAt;
}
7. 설계 요약
| 구분 | 설계 방식 | URI 예시 |
|---|---|---|
| 상위 리소스 | NTI (알림) | /api/v1/nti/{ntiId} |
| 하위 리소스 | 처리내역 | /api/v1/nti/{ntiId}/processes |
| 단건 처리내역 조회 | ID 기반 | /api/v1/nti/{ntiId}/processes/{processId} |
| DTO | NtiProcessDto, NtiProcessRequest |
속성 분리 |
8. 핵심 요약 문장
알림 하위의 처리내역은
Nti의 속성이 아니라NtiProcess라는 독립된 하위 리소스(Sub-resource) 로 분리해야 하며,/api/v1/nti/{ntiId}/processes형태로 접근하도록 설계하는 것이 RESTful하고 확장성에 유리합니다.
원하신다면 “처리내역이 단순 로그 형태인지, 상태 전이(예: 진행중→완료)인지” 알려주시면 그 흐름에 맞는 상태 기반 도메인 설계(state transition model) 까지 구체화해드릴 수 있습니다. 그 부분 포함해서 볼까요?