14 KiB
Core 모듈 설계
목표
- core는 admin/front가 공유하는 데이터 접근 계층과 인프라를 제공한다.
- admin/front는 각 서버의 업무 정책, 트랜잭션, 요청과 응답 조립, 인증 주체 확인, 서버별 운영 설정을 담당한다.
- 화면 전용 Form과 VO는 core에 두지 않는다. 단, core 업무 처리의 입력과 결과를 표현하는 내부 DTO/VO는 둔다.
패키지 구조
com.alist.api.core
├─ common
├─ config
└─ modules
├─ standard
└─ bespoke
standard
standard는 여러 기능에서 재사용할 수 있는 단일 테이블 데이터 처리를 둡니다.
- 단일 테이블의 조회, 등록, 수정, 삭제와 필요한 전용 조회 projection
- 한 테이블 중심의 Mapper, Service, 내부 DTO/VO
- admin/front 업무 Service가 호출한다.
- Form, 화면 전용 VO, HTTP 요청/응답 객체에 의존하지 않음
- 등록과 수정의 실제 SQL 처리는 특별한 사유가 없으면 standard가 소유한다.
- 단일 테이블에서 조회 조건이나 조회 필드가 추가되어도 새 Service를 만들지 않는다. 해당 테이블의 기존 standard Service, Mapper, DTO/VO 계약에 메서드 또는 선택 조건을 추가한다.
bespoke
bespoke는 단일 테이블 standard 계약으로 해결되지 않는 복합 데이터 처리를 둡니다. bespoke는 특정 목적에 맞춰 제작한 처리라는 뜻입니다.
- 여러 테이블 JOIN 조회
- standard로 처리할 수 없는 일괄 처리,
INSERT ... SELECT, 성능 최적화 SQL - 예외적인
JOIN UPDATE등 복합 DML
bespoke는 전용 Mapper와 그 Mapper를 호출하는 Service를 함께 둡니다. 단일 테이블 처리와 범용 CRUD는 bespoke마다 중복하지 않고 standard로 올립니다. admin/front의 업무 Service가 정책과 트랜잭션을 소유하며, core bespoke는 복합 데이터 접근 책임에 집중합니다.
업무 흐름과 호출 기준
Controller -> admin/front 업무 Service
admin/front 업무 Service -> standard Service
admin/front 업무 Service -> bespoke Service
bespoke Service -> bespoke Mapper
- core standard와 core bespoke는 서로의 Service 또는 Mapper를 호출하지 않는다.
- standard와 bespoke를 함께 써야 하는 업무 흐름은 반드시 admin/front 업무 Service가 각각 호출해 조합한다.
- 따라서 core는 독립적인 단일 테이블 접근(standard)과 복합 SQL 접근(bespoke)만 제공하고, 두 계층 사이의 호출 순서나 트랜잭션 경계는 갖지 않는다.
관리자 회원가입
UserController -> admin UserService
-> TestUserService
-> TestUserTokenService
회원과 토큰을 함께 조회하는 관리자 화면
MemberController -> admin MemberService
-> bespoke.testUser.TestUserMemberService
-> TestUserMemberMapper.testUserMemberList
- standard는 단일 테이블을 기준으로 실제
select,insert,update,delete를 처리한다. 단일 테이블의 인증용 조회처럼 필요한 필드 구성이 다른 경우도 standard의 전용 조회 메서드로 둔다. - admin/front 업무 Service는 어떤 standard/bespoke를 어떤 순서로 호출할지, 어떤 정책을 적용할지, 어디까지 하나의 트랜잭션으로 묶을지를 결정한다.
- Controller는 core standard/bespoke Service 또는 Mapper를 직접 호출하지 않는다.
- 여러 테이블 조인은 보통 bespoke 조회 Mapper에 둔다.
- 여러 테이블을 하나의 SQL로 갱신해야 하는
JOIN UPDATE는 bespoke 수정 Mapper의 예외다. - standard/bespoke의 첫 판단 기준은 SQL이 접근하는 테이블 수다. 단일 테이블이면 standard, 여러 테이블 JOIN·복합 DML이면 bespoke다. 정책과 트랜잭션 경계는 admin/front 업무 Service가 담당한다.
업무 모듈 파일 구조
modules/standard/testUser
├─ mapper
│ └─ TestUserMapper.java
├─ service
│ └─ TestUserService.java
├─ dto
└─ vo
resources/mapper/standard/testUser
└─ TestUserMapper.xml
modules/bespoke/notice
├─ mapper
│ └─ NoticeNoticeMapper.java
├─ service
│ └─ NoticeNoticeService.java
├─ dto
└─ vo
resources/mapper/bespoke/notice
└─ NoticeNoticeMapper.xml
- Mapper와 Service는 각각
mapper,service패키지로 분리한다. - DTO와 VO는
dto,vo패키지로 분리한다. - Mapper XML namespace는
mapper패키지를 포함한 Mapper 인터페이스 전체 경로와 일치해야 한다.
테이블·업무 모듈 기반 명명
core와 실행 모듈의 Service 이름은 역할을 임의로 줄이지 않고 기준 테이블과 업무 모듈을 조합한다.
standard
{Table}Service
bespoke
{BaseTable}{BusinessModule}Service
{Table}과{BaseTable}은 기준 테이블의 Java 이름이다. 예:test_user->TestUser,NOTICE->Notice.- standard는 해당 테이블의 기본 CRUD이므로 별도 업무 모듈 접미사를 붙이지 않는다. 예:
TestUserService,NoticeService. - bespoke는 기준 테이블 뒤에 이를 사용하는 업무 모듈명을 붙인다. 예:
TestUserAuthService,TestUserTokenAuthService,NoticeSupportService. - 업무 모듈명은
admin,front같은 실행 서버명이 아니라auth,support처럼 실제 기능 목적을 사용한다. Query,Custom,Bespoke,Etc처럼 처리 방식만 표현하는 접미사는 사용하지 않는다.- bespoke Service에는 이름에 포함된 업무 모듈 목적에 맞는 메서드만 둔다.
TestUserAuthService에는test_user를 기준으로 하는 여러 테이블 인증 JOIN 조회만 둔다. 단일TEST_USER조회는TestUserService에 둔다. - JOIN이 있더라도 SQL이 시작하고 결과를 소유하는 기준 테이블에 맞춰 bespoke 패키지를 정한다. 예: API Key와 refresh token 기준 인증 JOIN 조회는
bespoke.testUserToken에 둔다.
bespoke 메서드 명명
실행 모듈 Service는 API 업무 흐름을 기준으로, core bespoke는 해당 흐름을 위해 기준 테이블에서 반환하는 데이터 형태를 기준으로 이름을 정한다.
admin/front AuthService.selectLogin
-> core TestUserAuthService.testUserLoginView
-> core TestUserTokenService.updateTestUserTokenModify
-> core TestUserService.updateTestUserModify
admin/front Service
{select|insert|update|delete}{기능명}
core bespoke Service / Mapper / XML id
{기준테이블명}{기능명}{List|View|Count|Summary|...}
- core bespoke 메서드명에서는
select,insert,update,delete접두어를 사용하지 않는다. - 기준 테이블명은 반드시 앞에 둔다. 예:
testUserLoginView,noticeSupportList. - 기능명은 이를 호출하는 admin/front Service의 기능 흐름을 따른다. 한 흐름에서 여러 core 조회가 필요하면 각 기준 테이블명을 붙여 여러 메서드를 호출한다.
- 조회 메서드는
List,View,Count,Summary,Tree등 반환 형태를 반드시 마지막에 붙인다. - 조회 조건은 DTO에 둔다.
ByUserIdx,ByApiKey처럼 조건을 메서드명에 붙이지 않는다. - standard로 처리 가능한 단일 테이블 CRUD와 조회는 bespoke에 만들지 않는다. 예외적인 bespoke CUD도 같은 명명 방식으로 기준 테이블과 기능명을 사용한다. 예:
noticeSupportAdd,noticeSupportModify. - bespoke Service 메서드, Mapper 인터페이스 메서드, Mapper XML
id는 정확히 같은 이름을 사용한다.
실행 모듈에서 하나의 업무 패키지가 여러 테이블 기능을 포함할 때도 테이블별 Service를 분리한다.
admin/modules/support
├─ NoticeController
├─ FaqController
└─ service
├─ NoticeSupportService
└─ FaqSupportService
SupportService 하나에 Notice와 FAQ 로직을 함께 넣지 않는다. 반면 admin/modules/auth/AuthService처럼 하나의 인증 흐름을 조합하는 실행 모듈 업무 Service는 기능명만 사용하며, core standard/bespoke Service를 호출해 처리한다.
표준 DTO/VO 규칙
standard의 각 업무 모듈은 API 동작 이름을 기준으로 DTO와 VO를 분리한다. 이 객체들은 core 내부에서 Mapper와 Service 사이, 또는 core Service와 실행 모듈 업무 Service 사이에 전달되는 업무 객체이며 화면 전용 응답 객체가 아니다.
modules/standard/notice
├─ NoticeMapper.java
├─ NoticeService.java
├─ dto
│ ├─ NoticeAddDto.java
│ ├─ NoticeViewDto.java
│ ├─ NoticeListDto.java
│ ├─ NoticeModifyDto.java
│ └─ NoticeDeleteDto.java
└─ vo
├─ NoticeAddVo.java
├─ NoticeViewVo.java
├─ NoticeListVo.java
├─ NoticeListItemVo.java
├─ NoticeModifyVo.java
└─ NoticeDeleteVo.java
- 기본 동작명은
add,view,list,modify,delete로 통일한다. - 목록은 검색/페이징 조건인
ListDto, 목록 행인ListItemVo, 목록과 페이징 결과를 담는ListVo를 분리한다. - 변경 결과 VO는 동작이 드러나는 boolean과 대상 PK를 함께 둔다. 예:
NoticeModifyVo(updated, noticeIdx),NoticeDeleteVo(deleted, noticeIdx). 등록 결과에는added와 생성 PK 등 필요한 결과 값을 함께 둔다. - admin/front Form은 실행 모듈의 업무 Service까지 전달할 수 있다. 업무 Service는 Form 값을 core DTO로 변환하고, core VO는 필요할 때만 실행 모듈의 화면 전용 VO로 조립한다.
core VO와 실행 모듈 VO
core VO는 Mapper 조회 결과와 core 내부 업무 처리에 필요한 데이터를 표현한다. admin/front VO는 HTTP 응답으로 실제 클라이언트에 전달할 필드만 표현한다.
core Mapper
-> core VO
-> admin/front 업무 Service
-> admin/front 화면/API VO
-> Controller
- core VO는 특정 화면에 묶이지 않고 업무·DB 기준으로 필요한 데이터를 담는다.
- admin/front 업무 Service는 core VO에서 각 API에 필요한 필드만 골라 실행 모듈 VO로 조립한다.
- 같은 core VO라도 admin과 front는 서로 다른 외부 VO를 사용할 수 있다.
- 비밀번호, refresh token, 내부 인증값 같은 민감 정보는 일반 core VO에 기본으로 넣지 않는다. 꼭 필요한 인증 처리에는 전용 core VO를 사용하고, admin/front 외부 VO에는 절대 옮기지 않는다.
- Controller는 core DTO/VO를 import하거나 반환 타입으로 사용하지 않는다. core DTO/VO는 admin/front 업무 Service까지만 전달한다.
- Controller가 필요한 값은 반드시 해당 실행 모듈 Service가 조립한 admin/front VO로 받는다. core 조회 결과를 Controller에서 외부 VO로 변환하지 않는다.
AuthController
-> admin AuthService.selectLoginChecked
-> core TestUserTokenAuthService.testUserTokenLoginCheckedView
-> TestUserTokenLoginCheckedViewVo (core 내부)
-> AuthLoginCheckedVo (admin 외부 응답용)
-> ApiResponse<AuthLoginCheckedVo>
단일 수정과 상태 변경
단일 필드 변경도 별도 UseModifyDto, StatusModifyDto를 늘리지 않는다. 기본적으로 하나의 {Domain}ModifyDto를 사용한다.
NoticeModifyDto
- noticeIdx // 수정 대상
- noticeType // 선택
- targetScope // 선택
- title // 선택
- content // 선택
- pinYn // 선택
- startDate // 선택
- endDate // 선택
- useYn // 선택
- updateMember // 항상 기록
- Service는 DTO에 담긴 값을 그대로 Mapper에 전달한다.
- Mapper XML에서는
<set>을 사용하지 않는다. 항상 갱신하는 수정일 컬럼을SET의 첫 항목으로 두고, null이 아닌 선택 필드는<if>와 앞쪽 쉼표로 뒤에 붙인다. 따라서 사용 상태만 바꾸는 경우에도NoticeModifyDto에noticeIdx,useYn,updateMember만 넣어 호출한다. START_DATE,END_DATE처럼 null로 명시적 초기화가 필요한 컬럼은 단순 null 제외 규칙으로는 처리할 수 없다. 초기화 요구가 생기면 전용 초기화 플래그 또는 해당 업무에 맞는 bespoke SQL을 추가한다.
변경 결과 VO
수정과 삭제는 다음 기준으로 결과를 반환한다.
NoticeModifyVo
- updated // update 영향 행 수가 1 이상이면 true
- noticeIdx // 수정 요청 대상 PK
NoticeDeleteVo
- deleted // delete(논리 삭제) 영향 행 수가 1 이상이면 true
- noticeIdx // 삭제 요청 대상 PK
updated,deleted는 SQL 영향 행 수로 계산한다.- 변경 결과 VO에는 내부 처리용
resultCode를 둔다. 성공은2002또는2005, 수정값 없음은4001, 대상 없음은2003처럼 Service가 반환한다. - Controller는
resultCode에 맞는ApiResponseCode를 선택한다.resultCode는@JsonIgnore로 외부 응답 본문에는 직접 노출하지 않는다. - PK를 함께 반환하면 호출한 쪽이 실제 처리 대상을 명확히 확인할 수 있다.
common과 config
common: JWT, 공통 예외, ApiResponse, 페이징, 유틸, 시스템 전반 공통 기능config: DataSource, MyBatis, 캐시, 필터처럼 공통 인프라를 구성하는 클래스- 파일 업로드처럼 테이블 CRUD에 속하지 않는 공통 기능은
common또는config의 책임에 맞춰 둡니다.
admin과 front
api-admin / api-front
├─ Api*Application
├─ config
│ ├─ SecurityConfig
│ └─ OpenApiConfig
└─ modules
└─ Controller, 업무 Service, Form, 화면 VO
- Security와 OpenAPI는 서버별 정책과 문서를 가지므로 각 실행 모듈에 둡니다.
- Controller는 Form 검증, 업무 Service 호출, 서버별 응답 VO 조립을 담당합니다.
- 업무 Service는 Form을 받아 필요한 core standard/bespoke DTO로 변환하고, 업무 정책과 트랜잭션을 처리합니다.