- 가독성 우선: 성능 최적화보다 코드의 이해 가능성과 유지보수성을 우선합니다.
- 일관성 유지: 기존 코드 스타일과 다를 경우, 기존 스타일을 우선합니다.
- 단일 책임 원칙(SRP): 클래스·메서드는 하나의 책임에 집중하도록 설계합니다.
한 도메인은 하나의 책임을 갖도록 구성합니다.
src/main/java/{base-package}/
├── global/
│ ├── config/
│ ├── exception/
│ ├── security/
│ ├── common/
│ └── ...
│
├── user/
├── auth/
├── post/
└── {domain}/ # 예: party, order 등
├── controller/
├── service/
├── repository/
├── domain/
├── dto/
│ ├── request/
│ └── response/
└── enums/
| 폴더 | 설명 |
|---|---|
controller |
HTTP 요청/응답, DTO 변환, 인증/인가 위임 |
service |
비즈니스 로직, 트랜잭션 경계 |
repository |
JPA 등 영속성 처리 |
domain |
엔티티 및 도메인 모델 |
dto |
요청/응답 DTO, 요청/응답/호출자별 패키지 분리 |
enums |
도메인 관련 Enum |
- 전부 소문자, 언더스코어 금지.
- 예:
springboot.boilerplate.user.controller,springboot.boilerplate.global.config.
| 종류 | 설명 | 이름 규칙 |
|---|---|---|
| 생성(Create) | 데이터 생성 | save |
| 수정(Update) | 데이터 수정 | update / update + Column (예: updatePassword) |
| 삭제(Delete) | 데이터 삭제 | delete (하드), softDelete (소프트) |
| 단일 조회 | 단건 조회 | findOne / 조건 포함 시 findOneByEmail 등 |
| 리스트 조회 | 다건 조회 | findAll / 호출자 구분 시 findAllByAdmin |
- 접근 지시자, 매개변수, 반환 타입, 주석을 명시합니다.
public메서드를 위에,private메서드를 아래에 배치합니다.- 비슷한 기능의 메서드는 가까운 위치에 모읍니다.
- Controller / Service의 public 메서드는 DTO를 인자/반환 타입으로 사용하고, Entity를 직접 노출하지 않습니다.
/**
* 유저를 저장합니다.
*/
public UserResponseDto save(UserSaveRequestDto dto) {
// ...
}
/**
* 유저를 ID 기반으로 단일 조회합니다.
*/
public UserResponseDto findOne(Long userId) {
// ...
}
/**
* 유저 리스트를 조회합니다.
*/
public List<UserResponseDto> findAll(UserSearchRequestDto dto) {
// ...
}
/**
* 유저의 저장 가능 여부를 검사합니다.
*/
private void validateUserExistence(String email) {
// ...
}- 이름은 Domain + Controller 형태로 작성합니다.
- 예:
UserController,AdminUserController,UserV2Controller.
- 예:
- 사용 주체(일반 유저 / 관리자 등)에 따라 Controller를 분리합니다.
- Spring Security 설정을 Controller 단위로 공통 처리하여 중복 애노테이션을 줄입니다.
// User Controller
@Api(tags = "User")
@RestController
@RequestMapping("/api/v1/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
}
// Admin User Controller
@Api(tags = "Admin User")
@RestController
@RequestMapping("/api/v1/admin/user")
@RequiredArgsConstructor
public class AdminUserController {
private final UserService userService;
}| Method | Description | Body 여부 |
|---|---|---|
| GET | 조회 | X |
| POST | 생성 / 이벤트 트리거 | O |
| DELETE | 삭제 | X |
| PUT | 전체 수정 | O |
| PATCH | 부분 수정 | O |
API Payload는 Header 영역(URL, Params, Query)과 Body 영역으로 구분합니다.
-
URL(Path): RESTful하게 명사 사용
- X:
POST /api/v1/food/save - O:
POST /api/v1/food
- X:
-
URL Params(PathVariable)
- 예:
GET /api/v1/food/{foodId},DELETE /api/v1/food/{foodId} id대신 의미 있는 이름 사용 (foodId,userId등).
- 예:
@GetMapping("/{foodId}")
public ResponseEntity<FoodResponseDto> findOne(@PathVariable("foodId") Long foodId) {
// ...
}- URL Query Params
- 단순 값:
@RequestParam사용. - 검색/페이징 등 복잡한 경우: Query DTO로 묶어 사용.
- 단순 값:
@GetMapping("/search")
public ResponseEntity<List<FoodResponseDto>> searchFoods(
@RequestParam(required = false) String name,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int pageSize
) {
// ...
}
@GetMapping
public ResponseEntity<PagedResponse<FoodResponseDto>> getFoods(
@Valid FoodSearchRequestDto dto
) {
// ...
}- Query Params는 DTO로 구성해 사용합니다.
- 필드에 타입, Optional 여부, 설명, 예시값을 명시합니다.
- 공통 페이징은
RequestPagingDto를 상속해 사용합니다.
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class RequestPagingDto {
@Schema(example = "0", description = "페이지 번호")
@Min(0)
private Integer page = 0;
@Schema(example = "10", description = "페이지 크기")
@Min(1)
@Max(100)
private Integer pageSize = 10;
public int getOffset() {
return page * pageSize;
}
}Query Params 타입 처리
| 타입 | 처리 방식 |
|---|---|
| Number | @RequestParam Integer age 등 기본 타입 바인딩 사용 |
| Boolean | 직접 boolean보다는 Enum(UseYnEnum) 등으로 처리 권장 |
| Enum | @RequestParam Status status 또는 DTO 필드에 Enum 사용 |
| List | @RequestParam List<Long> foodIdList 또는 리스트 필드를 가진 DTO 파라미터 |
- Body가 있을 경우 URL Params, Query Params 사용은 최소화합니다.
- Request / Response를 명확히 구분합니다.
UserSaveRequestDto,UserUpdateRequestDto,UserResponseDto등.
- Request DTO
- 필요 시
toEntity(),toCommand()등의 인스턴스 변환 메서드 사용.
- 필요 시
- Response DTO
- 도메인/엔티티에서 응답 객체를 만들 때 정적 팩토리 메서드(from/of) 사용을 기본으로 합니다.
/**
* 유저 단건 조회 응답 DTO
*/
@Getter
@Builder
@AllArgsConstructor
public class UserResponseDto {
private Long userId;
private String email;
private String name;
/**
* Entity → Response DTO 변환용 정적 팩토리 메서드
*/
public static UserResponseDto from(User user) {
return UserResponseDto.builder()
.userId(user.getId())
.email(user.getEmail())
.name(user.getName())
.build();
}
}- 클래스명: PascalCase (
UserService,UserController). - 메서드명: camelCase (
findUser,updatePassword). - 변수명: camelCase, 축약형 지양 (
user,userList,accessToken). - 상수명:
UPPER_SNAKE_CASE(DEFAULT_PAGE_SIZE). - 패키지명: 소문자, 의미 기반 (
user,auth,global등).
- “무엇을 했다”보다 **“왜 이렇게 했다”**를 설명합니다.
- 코드만 봐도 알 수 있는 내용(단순 로직 설명)은 주석으로 쓰지 않습니다.
- 공식 설명(Javadoc)은 외부 공개 메서드/복잡한 로직 위주로 사용합니다.
// 외부 시스템에서 null 이름이 들어올 수 있어, 기본값 "게스트"로 치환
String name = Optional.ofNullable(user.getName())
.orElse("게스트");
/**
* 유저 저장
*
* 외부 시스템에서 중복 생성 요청이 들어올 수 있기 때문에,
* 동일 이메일이 이미 존재하면 새로 만들지 않고 예외를 던짐
*
* @param dto 유저 저장 요청 정보
* @return 저장된 유저 도메인 객체
* @throws DuplicateEmailException 동일 이메일을 가진 유저가 이미 존재하는 경우
*/
public UserResponseDto saveUser(UserCreateRequestDto dto) {
// ...
}-
Controller
@RestController+ DTO 입출력.- 비즈니스 로직은 Service로 위임.
- RESTful URL/HTTP Method 사용.
-
Service
- 비즈니스 로직의 중심.
- 트랜잭션 관리 (
@Transactional) 담당. - 한 메서드는 한 유스케이스만 담당.
-
Repository
- Spring Data JPA 중심.
- 간단한 정적 쿼리는 메서드 이름/
@Query를 사용하고, 복잡한 동적 조회는QueryDSL을 사용합니다.
-
Entity / Domain
- 비즈니스 규칙을 최대한 도메인에 포함.
- Setter는 가급적 사용하지 않고, 의미 있는 도메인 메서드(
changePassword,updateProfile,activate등)를 제공합니다.
- 컬럼명, 타입, 길이, nullable 여부, 코멘트를 최대한 명시합니다.
- DB 컬럼명은 스네이크 케이스, 필드명은 카멜 케이스를 사용합니다.
@Table(name = "tb_user"),@Column(name = "user_id", ...)처럼 이름을 명시합니다.- Enum은
@Enumerated(EnumType.STRING)으로 매핑합니다.
@Entity
@Table(name = "tb_user")
public class User extends BaseTimeEntity {
// 유저 PK
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
private Long userId;
// 이메일
@Column(name = "email", length = 120, nullable = false)
private String email;
// 비밀번호
@Column(name = "password", length = 255, nullable = false)
private String password;
// 유저 활성/비활성 상태
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false)
private UserStatus status;
// 도메인 메서드 예시
public void changePassword(String encodedPassword) {
this.password = encodedPassword;
}
}- 생성자 주입 사용 (필수), Lombok
@RequiredArgsConstructor선호. - 필드 주입 금지.
- 트랜잭션: Service 계층에서
@Transactional, 조회 전용은readOnly = true. - 공통 설정은
global.config패키지에 위치.
- 비즈니스별 커스텀 예외 정의 (
UserNotFoundException등). - 전역 예외 처리:
@RestControllerAdvice+@ExceptionHandler. - 공통 에러 응답 포맷 유지:
code,message,status,timestamp,path등. - 요청 DTO 검증:
@Valid+ Bean Validation(@NotNull,@NotBlank,@Size,@Email등).
- 허용:
@Getter,@Setter(엔티티는 최소화),@RequiredArgsConstructor,@Builder,@Slf4j. - 지양:
@Data(엔티티/DTO에 무분별 사용 금지).