# 개발 가이드 및 컨벤션 (Development Guide) ## 1. 프로젝트 디렉토리 구조 ### Backend (Spring Boot) ``` src/main/java/com/hq/support/portal/ ├── global/ │ ├── common/ │ │ └── ApiResponse.java // 공통 응답 래퍼 │ ├── config/ │ │ ├── AsyncConfig.java // @Async 스레드 풀 │ │ ├── JpaAuditConfig.java // @EnableJpaAuditing │ │ ├── BatchConfig.java // Spring Batch 메타 설정 │ │ └── SecurityConfig.java // Security Filter Chain │ ├── error/ │ │ ├── ErrorCode.java // 에러코드 Enum 매트릭스 │ │ ├── GlobalExceptionHandler.java // @RestControllerAdvice │ │ └── exception/ │ │ └── BusinessException.java // 최상위 예외 클래스 │ └── security/ │ ├── JwtTokenProvider.java // JWT 생성/검증 │ └── JwtAuthenticationFilter.java // 인증 필터 │ ├── domain/ │ ├── base/ │ │ └── BaseEntity.java // createdAt, updatedAt, isDeleted │ ├── employee/ │ │ ├── entity/TbEmployee.java │ │ ├── repository/EmployeeRepository.java │ │ └── exception/InvalidEmployeeException.java │ ├── project/ │ │ ├── entity/TbProject.java │ │ └── repository/ProjectRepository.java │ ├── dispatch/ │ │ ├── entity/TbDispatchHistory.java │ │ └── repository/DispatchRepository.java │ ├── ticket/ │ │ ├── entity/TbIssueTicket.java │ │ ├── entity/TbTicketComment.java │ │ ├── repository/TicketRepository.java │ │ ├── controller/IssueTicketController.java │ │ ├── dto/TicketDto.java │ │ ├── service/IssueTicketService.java │ │ └── event/TicketCreatedEvent.java │ ├── report/ │ │ ├── entity/TbWorkReport.java │ │ └── repository/ReportRepository.java │ ├── equipment/ │ │ ├── entity/TbEquipment.java │ │ ├── entity/TbEquipmentAssignment.java │ │ ├── entity/TbEquipmentMaintenance.java │ │ └── repository/EquipmentRepository.java │ └── training/ │ ├── entity/TbTrainingProgress.java │ └── repository/TrainingRepository.java │ └── infra/ └── notification/ ├── listener/NotificationEventListener.java └── scheduler/SlaJobScheduler.java src/main/resources/ ├── application.yml └── ddl/oracle_11g_schema.sql ``` ### Frontend (Next.js 14+) ``` src/ ├── app/ │ ├── (auth)/login/page.tsx │ ├── tickets/ │ │ ├── page.tsx // 칸반 보드 │ │ └── [ticketId]/page.tsx // 상세/타임라인 │ ├── equipment/page.tsx // 장비 인벤토리 │ ├── reports/page.tsx // 업무 보고서 │ ├── training/page.tsx // 교육 진도 │ ├── employees/page.tsx // 직원 관리 (ADMIN) │ └── layout.tsx // GNB + LNB 레이아웃 ├── components/ │ ├── layout/ │ │ ├── GNB.tsx │ │ └── LNB.tsx │ ├── ticket/ │ │ ├── KanbanBoard.tsx │ │ ├── TicketCard.tsx │ │ └── TimelineComment.tsx │ ├── equipment/ │ │ ├── EquipmentTable.tsx │ │ └── AssignModal.tsx │ └── common/ │ ├── ApiResponse.ts │ └── Pagination.tsx ├── stores/ │ ├── authStore.ts │ └── notificationStore.ts ├── services/ │ ├── apiClient.ts // Axios 인스턴스 + 인터셉터 │ ├── ticketService.ts │ ├── equipmentService.ts │ └── reportService.ts └── types/ └── index.ts // 전역 TypeScript 타입 ``` --- ## 2. Backend 코딩 컨벤션 ### 2.1 엔티티 네이밍 - **테이블명:** `TB_XXX` (Oracle 11g 컨벤션) - **엔티티 클래스명:** `TbXxx.java` (PascalCase, 테이블명 매핑) - **PK 컬럼:** `xxx_id` (NUMBER, Sequence 할당) ### 2.2 ID 생성 전략 (Oracle 11g 최적화) ```java @Entity @SequenceGenerator(name = "SEQ_TB_TICKET", sequenceName = "SEQ_TB_TICKET", allocationSize = 50) public class TbIssueTicket { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_TB_TICKET") private Long ticketId; } ``` - Oracle DDL 시퀀스: `CREATE SEQUENCE SEQ_TB_TICKET INCREMENT BY 50;` - `allocationSize = 50` + `INCREMENT BY 50` 반드시 정합 ### 2.3 Soft Delete 패턴 ```java @SQLDelete(sql = "UPDATE tb_issue_ticket SET is_deleted = 'Y' WHERE ticket_id = ?") @Where(clause = "is_deleted = 'N'") public class TbIssueTicket { ... } ``` - 모든 엔티티에 `@SQLDelete` + `@Where` 선언 - `BaseEntity`에 `isDeleted` 컬럼 공통 상속 ### 2.4 레이어드 아키텍처 규칙 | 레이어 | 책임 | 규칙 | |--------|------|------| | Controller | HTTP 진입점 | DTO 반환, `@Valid` 검증, 서비스 호출만 | | Service | 비즈니스 로직 | `@Transactional`, 예외 throw, 이벤트 발행 | | Repository | DB 접근 | Spring Data JPA, `Specification` 동적 쿼리 | | DTO | 데이터 전송 | Request/Response 분리, 엔티티 직접 노출 금지 | ### 2.5 예외 처리 계층 ```java // 최상위 예외 public class BusinessException extends RuntimeException { private final ErrorCode errorCode; } // 도메인별 구체 예외 public class TicketClosedException extends BusinessException { public TicketClosedException() { super(ErrorCode.CLOSED_TICKET_MOD); } } // ControllerAdvice에서 ErrorCode → ApiResponse 자동 변환 ``` ### 2.6 EDA (이벤트 기반 알림) ```java // 발행 @Service public class IssueTicketService { @Autowired private ApplicationEventPublisher publisher; public TicketDto createTicket(TicketCreateReq req) { TbIssueTicket ticket = repository.save(req.toEntity()); publisher.publishEvent(new TicketCreatedEvent(ticket)); return TicketDto.from(ticket); } } // 구독 (트랜잭션 완료 후 비동기 실행) @Component public class NotificationEventListener { @Async @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void handleTicketCreated(TicketCreatedEvent event) { // Slack/Email 발송 (메인 트랜잭션과 완전 격리) } } ``` --- ## 3. Frontend 코딩 컨벤션 ### 3.1 API 통신 (Axios) ```typescript // apiClient.ts - 공통 인터셉터 const apiClient = axios.create({ baseURL: '/api/v1', timeout: 10000, }); apiClient.interceptors.response.use( (res) => res.data, // ApiResponse.data 자동 언래핑 (err) => { const { errorCode, message } = err.response.data; // errorCode 기반 전역 토스트/알림 return Promise.reject({ errorCode, message }); } ); ``` ### 3.2 Pinia Store 패턴 ```typescript // stores/authStore.ts export const useAuthStore = defineStore('auth', () => { const user = ref(null); const roleCode = computed(() => user.value?.roleCode); async function fetchMe() { const res = await apiClient.get('/employees/me'); user.value = res.data; } return { user, roleCode, fetchMe }; }); ``` ### 3.3 컴포넌트 네이밍 - **페이지 컴포넌트:** `page.tsx` (App Router), 도메인 폴더 하위 - **UI 컴포넌트:** `PascalCase.tsx` (단일 책임) - **Props:** Interface 선언 후 `defineProps()` - **Event Emit:** `defineEmits<{ eventName: [payload: type] }>()` --- ## 4. 개발 환경 설정 ### Backend (application.yml) ```yaml spring: datasource: url: jdbc:oracle:thin:@localhost:1521:XE username: portal password: ${DB_PASSWORD} jpa: hibernate: ddl-auto: none # 운영 DDL은 별도 스크립트 관리 properties: hibernate: dialect: org.hibernate.dialect.Oracle10gDialect batch: jdbc: initialize-schema: always # 배치 메타 테이블 자동 생성 jwt: secret: ${JWT_SECRET} access-token-validity: 3600000 # 1시간 refresh-token-validity: 604800000 # 7일 ``` ### Frontend (.env.local) ``` NEXT_PUBLIC_API_BASE_URL=http://localhost:8080/api/v1 NEXT_PUBLIC_WS_URL=ws://localhost:8080/ws/notifications ``` --- ## 5. 빌드 및 배포 ### CI/CD (GitHub Actions) - **트리거:** `release` 생성 시에만 운영 빌드 - **Push 이벤트:** Lint + Test만 실행 (배포 없음) - **파이프라인:** Build → Test → Docker Image → Deploy (Nginx Proxy Manager) ### 로컬 빌드 검증 ```bash # Backend ./gradlew clean build # Frontend npm run build npm run lint ``` ### Nginx Proxy Manager - SSL 인증서 관리 - `/api/*` → Backend Container (Reverse Proxy) - `/*` → Frontend Static/Node Server - Rate Limit 설정 (API abuse 방어) --- ## 6. DB DDL 관리 규칙 - DDL 파일: `src/main/resources/ddl/oracle_11g_schema.sql` - 모든 시퀀스: `CREATE SEQUENCE SEQ_TB_XXX INCREMENT BY 50 START WITH 1` - `ddl-auto: none` — DDL은 스크립트로만 관리 (Hibernate 자동 생성 금지) - 스키마 변경 시 DDL 파일 버전 관리 (Git) - 인덱스: 자주 조회하는 컬럼(상태, 날짜, 외래키)에 인덱스 별도 추가 - 공통 코드 데이터: `INSERT` 문으로 시드 데이터 관리 --- ## 7. 커밋 컨벤션 한국어로 명확하게 작성 (팀 협업 가독성 보장) ``` feat: SR 티켓 칸반 보드 API 구현 fix: 장비 지급 시 낙관적 락 충돌 해결 refactor: BaseEntity 공통 필드 추출 docs: DB 설계서 최신화 ``` ### 브랜치 전략 ``` main ← develop ← feature/{domain}-{description} - feature/issue-ticket-kanban - feature/equipment-assign-optimistic-lock - fix/report-status-transition-bug ```