Spring Boot 프로젝트 구조에 대해서 알아보자.
보통 크게 나눴을 때 Controller / Service / Repository / Entity / Dto 패키지로 나누어진다.
1. Controller
클라이언트와 서버 간의 중간자 역할을 한다.
클라이언트에서 보낸 요청 api(url)에 따라 적절한 응답을 하고 반환한다.
즉, Controller는 클라이언트(뷰)로부터 Request Body에 담긴 데이터를 DTO로 변환하여 Service에 넘겨주고, Service에서 처리된 결과를 다시 DTO로 받고 Response Body에 담아 클라이언트에게 반환하는 역할을 한다.
Controller 종류
- @Controller
Api와 View를 동시에 사용하는 경우 사용한다. 대신 api 서비스로 사용하는 경우에는 @ResponseBody를 사용하여 객체를 반환한다.
-> 즉, view 반환이 주 목적이다.
- @RestController
view가 필요없는 상황에서 api만 지원하는 서비스에서 사용한다. @RequestMapping 메서드가 기본적으로 @ResponseBody 의미를 가정한다. 이 @RequestMapping 메서드는 보통 json 형태의 데이터를 반환하기 위해서 사용한다.
-> 즉, 데이터 반환이 주 목적이다.
쉽게 설명하자면 @RestController는 @Controller와 @ResponseBody를 합친 메서드이다.
2. Service
비즈니스 로직을 통한 데이터 가공자의 역할을 한다. 클라이언트 요청(Request)에 대해서 어떤 처리를 할지 결정하는 부분을 담당한다. 한마디로, 요청이 들어온 부분을 개발자가 DAO에서 받아온 데이터를 어떻게 변환하고 가공하여 다시 사용자에게 전달할지 결정하는 부분을 담당한다.
@Autowired Repository를 통해 repository의 method를 이용한다.
DAO를 통해서 DB에 접근하고 DTO로 데이터를 전달받은 다음, 비즈니스 로직을 처리하여 적절한 데이터를 반환한다.
3. Repository(DAO)
실제 DB에 접근하기 위한 객체이다. 즉, 실제로 DB에 접근하여 데이터를 CRUD하는 객체이다.
Service와 DB를 연결해주는 다리 역할을 담당하고 있으며, 인터페이스와 이에 대한 구현체를 만들어서 구현체에 CRUD 관련 기능을 구현하고, 이를 DI(Dependency Injection) 해주는 방식으로 사용된다.
이때, DB에 어떻게 접근하느냐?
sql을 사용해서 DB에 접근한 후에 적절한 CRUD API를 제공한다. JPA 대부분이 기본적인 CRUD 메서드를 제공하고 있으며, 직접 쿼리문을 작성하거나 JPA를 사용해서 쉽게 DB에 접근할 수 있다.
public interface Repository extends CrudRepository<Question, Long>{
}
💡여기서 DAO와 Repository의 차이점은?
DAO(Data Access Object)는 영구 데이터 저장소에 가까운 것이고, Repository는 객체의 상태를 관리하는 저장소이다. DAO는 Repostiory를 사용하여 구현할 수 없지만, Repository는 DAO를 사용하여 구현할 수 있다.
쉽게 말하자면, Repository는 Entity 객체를 보관하고 있는 저장소이고, DAO는 데이터에 접근하도록 DB 접근 관련 로직을 모아둔 객체이다. 개념은 엄연히 다르지만 개발할 때는 비슷하게 사용되고 있다.
4. Entity(Domain)
Entity는 DB 테이블에 대응하는 하나의 클래스이다. 즉, 테이블가 연결됨 클래스임을 나타낸다.
즉, 이 클래스는 실제 데이터베이스 테이블과 매핑되는 핵심 클래스이며, 이 클래스는 각 행을 객체로 표현한다.
JPA를 사용하면 @Entity 어노테이션이 선언된 클래스가 이에 해당한다.
👀 Entity 클래스의 역할과 설계 원칙
- DB 테이블과의 매핑
Entity 클래스는 데이터베이스의 테이블과 매핑되어, 테이블의 각 컬럼을 클래스의 필드로 정의한다. 예를 들어, 데이터베이스에 'User'라는 테이블이 있다면, 'User' 테이블의 각 컬럼은 'User' Entity 클래스의 필드로 정의된다.
- Setter 메서드 사용 제한
Entity 클래스에서는 최대한 외부에서 Setter 메서드를 사용하지 않도록 설계해야 한다.
Setter 메서드를 남용하면 객체의 일관성을 유지하기 어렵고, 의도치 않은 상태 변경이 발생할 수 있기 때문이다.
이때, 생성자(Constructor) 또는 Builder를 사용한다. 여기서 구현한 메소드는 주로 Service 계층에 주로 사용된다.
- 생성자(Constructor)와 Builder 패턴 사용
필요한 로직은 생성자(Constructor) 또는 Builder 패턴을 사용하여 Entity 클래스 내부에서 구현한다.
생성자 : 객체 생성 시 필수 값들을 설정할 때 사용한다. 예를 들어, 'User' 클래스의 생성자는 사용자의 이름과 이메일을 필수 매개변수로 받을 수 있다.
Builder 패턴 : 복잡한 객체를 생성할 때 유용하며, 가독성을 높이고 필수 값과 선택적 값을 명확히 구분할 수 있게 합니다. 예를 들어, 'User' 클래스를 생성할 때 이름, 이메일, 주소 등을 Builder 패턴으로 설정할 수 있다.
- 비즈니스 로직 포함
Entity 클래스 내에 비즈니스 로직을 포함할 수 있다. 이 로직은 주로 데이터의 상태를 변경하거나 검증하는 역할을 한다. 예를 들어, User 클래스에는 사용자의 이메일을 변경하는 메서드나 비밀번호를 검증하는 메서드를 포함할 수 있다.
- Service Layer에서의 사용
Service 클래스는 주로 DTO를 사용하여 사용자 등록, 수정, 삭제 등의 작업을 수행한다. Service는 DTO를 Entity로 변환하여 데이터베이스와 상호작용하고, 처리 결과를 다시 DTO로 변환하여 반환한다. 예를 들어, UserService 클래스는 UserDTO를 사용하여 사용자 데이터를 처리하고, 필요 시 User Entity와 상호작용한다.
@Entity
@Builder
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Member {
// 회원 식별자를 나타내는 필드
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="member_id")
private Long id;
// 회원의 로그인 ID를 나타내는 필드
private String loginId;
// 회원의 이름을 나타내는 필드
private String name;
// 회원의 이메일을 나타내는 필드
private String email;
// 회원의 역할(Role)을 나타내는 필드
@Enumerated(EnumType.STRING)
private MemberRole role;
// OAuth2 공급자(Provider)를 나타내는 필드
// provider : naver가 들어감
private String provider;
// OAuth2 공급자에서 제공하는 회원의 고유 ID를 나타내는 필드
// providerId : 네이버 로그인을 한 유저의 고유 ID가 들어감
private String providerId;
}
5. DTO
데이터 교환을 도와주는 역할을 한다. 즉, 계층 간 데이터 교환을 도와주는 객체이다. 세부적인 로직을 갖고 있지 않은 순수한 객체라고 생각하면 된다. 오직, Getter/Setter 메소드만 가지고 있으며 계층 간 데이터 교환시에 사용된다.
// 회원가입 요청을 처리하기 위한 DTO(데이터 전송 객체)인 JoinRequest 클래스
@Getter @Setter
@NoArgsConstructor
public class JoinRequest {
// ID를 입력받는 필드. 비어 있을 수 없음을 나타냄
@NotBlank(message = "ID를 입력하세요.")
private String loginId;
// 이름을 입력받는 필드. 비어 있을 수 없음을 나타냄
@NotBlank(message = "이름을 입력하세요.")
private String name;
private String email;
// Member 엔티티로 변환하는 메서드
// toEntity() 메서드를 통해서 DTO에서 필요한 부분을 이용하여 Entity로 만듬
public Member toEntity(){
// Member 객체를 빌더 패턴을 통해 생성함
return Member.builder()
.loginId(this.loginId) // ID를 설정함
// .password(this.password)
.email(this.email) //email을 설정함
.name(this.name) // 이름을 설정함
.role(MemberRole.USER) // 사용자의 역할을 기본적으로 USER로 설정함
.build(); // Member 객체를 생성하여 반환함
}
}
💡Entity 클래스와 DTO 클래스를 분리하는 이유
DB와 View 사이의 역할 분리를 위해서이며 DTO는 순수하게 데이터를 담고 있다는 점이 Entity와 유사하지만, 목적 자체가 전달이므로 읽고, 쓰는 것이 모두 가능하다. 테이블과 매핑되는 entity 클래스가 변경된다면 여러 클래스에 영향을 미치는 반면 View와 통신하는 DTO 클래스는 자주 변경되므로 이 둘을 분리해야 한다.
JPA 사용시 Entity 객체는 단순히 데이터를 담는 객체 이상으로 DB와 중요한 역할을 하며, DTO가 일회성으로 데이터를 주고받는 용도로 사용되는 것과는 다르게 Entity의 생명주기도 달라 분리하며 사용한다.
→ 결론적으로, DTO는 Domain Model을 복사한 형태로, 다양한 계층형 로직을 추가한 정도로 사용하며 Domain Model 객체는 Persistent만을 위해서 사용한다.
동작 과정
1. 클라이언트가 DTO를 통해서 요청을 보낸다.
- 클라이언트에서 사용자 입력을 바탕으로 DTO를 생성하여 서버에 요청을 보낸다.
2. Controller에서 요청을 받는다.
- Controller는 클라이언트로 부터 DTO를 통해서 요청을 받으면, 받은 DTO를 Service로 전달한다. 이 과정에서 어떤 Service 메서드를 호출할 지 결정한다.
3. Service에서 비즈니스 로직을 처리한다.
- Controller로부터 받은 DTO를 이용하여 비즈니스 로직을 처리한다.
- 비즈니스 로직을 실행하는 도중 DB에 접근해야 할 경우 Repository를 호출한다.
4. Repository를 통해 DB에 접근한다.
- JPA나 쿼리문을 통해 호출하여 데이터를 처리하며, 이 때 Entity를 호출하여 DB 테이블과 매핑된 객체를 처리한다.
5. Entity는 DB 테이블과 매핑된다.
- 데이터베이스의 테이블 구조와 직접매핑되며, 비즈니스 로직을 포함할 수 있다.
- JPA를 사용하면 @Entity가 선언된다.
여기서, Spring MVC가 더 포괄적인 개념이라고 생각하면 된다.
Model : Service, Repository, Domain, DTO
View : FE
Controller : Controller
패키지 구조 종류
기본적으로 레이어 계층형과 도메인형으로 구성되어 있다.
1. 레이어 계층형
계층형 구조는 각 계층을 대표하는 디렉터리를 기준으로 코드를 작성한다.
프로젝트의 이해도가 낮아도 전체적인 구조 파악을 빠르게 할 수 있지만, 디렉터리 안에 클래스들이 너무 모인다는 단점이 있다.
src/main/java
자바파일들을 담는다.
- controller
- service
- db
- repository(dao)
- entity
- global(공통적으로 사용되는 것들)
- auth
- exception
- dto
- config
src/main/resources
리소스 파일들을 담는다.
- static
- js, css, img
- templates
- thymeleaf
- application.properties
2. 도메인형
도메인 디렉터리을 기준으로 코드를 구성하는 것이다.
도메인의 관련 코드를 응집할 수 있지만, 프로젝트의 이해도가 낮을 경우 전체적인 구조를 파악하기 어렵다.
src/main/java
- domain
- board
- controller
- entity
- service
- repository
- exception
- model(dto)
- follow
- controller
- entity
- service
- repository
- exception
- model(dto)
- board
- global(공통적으로 사용되는 것들)
- auth
- exception
- common
- request
- response
- config
'Framework > Spring' 카테고리의 다른 글
[Spring] JDBC란 무엇인가? (0) | 2024.05.13 |
---|