Spring 아키텍처
Spring 아키텍처
Controller · Service · Repository · JPA · DTO · Entity
1. Controller
Controller는 클라이언트의 요청을 받아 Service 계층을 호출하고
View 또는 데이터(JSON)를 반환하는 역할을 담당한다.
👉 요청과 처리 흐름을 연결해주는 입구 역할
1-1. @Controller
View, 즉 화면 반환이 주 목적
API와 View를 함께 사용하는 경우에도 사용 가능
객체나 문자열 데이터를 그대로 반환하려면
@ResponseBody를 명시적으로 사용해야 한다.
1
2
3
4
5
6
7
8
9
@Controller
public class SampleController {
@GetMapping("/test")
@ResponseBody
public String test() {
return "hello";
}
}
1-2. @RestController
View가 필요 없는 API 전용 Controller에서 주로 사용
모든 메서드에
@ResponseBody가 자동 적용된다.
1
2
3
4
5
6
7
8
@RestController
public class ApiController {
@GetMapping("/api/test")
public ResponseEntity<String> test() {
return ResponseEntity.ok("hello");
}
}
정리하면 다음과 같다.
1
@RestController = @Controller + @ResponseBody
따라서 @RestController는 View 이름을 반환하는 것이 아니라
JSON, XML, 문자열 같은 데이터를 응답 Body에 담아 반환한다.
2. Service 계층
Service는 비즈니스 로직을 담당하는 계층이다.
- 여러 Repository 조합
- 트랜잭션 처리
- 비즈니스 규칙 구현
예를 들어 회원가입을 처리할 때 단순히 DB에 저장만 하는 것이 아니라
중복 회원 검사, 비밀번호 암호화, 회원 권한 부여 같은 작업이 함께 필요할 수 있다.
이런 비즈니스 흐름을 처리하는 곳이 Service 계층이다.
2-1. Service Interface와 Impl을 나누는 이유
Service를 인터페이스와 구현체로 나누는 방식은 프로젝트에 따라 선택할 수 있다.
1
2
UserService → 인터페이스
UserServiceImpl → 구현 클래스
Service를 인터페이스로 분리하면 다음과 같은 장점이 있다.
- 구현체 변경에 유연하다.
- 결합도를 낮출 수 있다.
- 다형성을 활용할 수 있다.
- 테스트용 구현체를 따로 만들기 쉽다.
예를 들어 결제 기능이 있다고 가정하면
결제 방식이 카드 결제, 카카오페이 결제, 네이버페이 결제처럼 여러 개로 나뉠 수 있다.
이런 경우에는 인터페이스로 분리하는 것이 좋다.
1
2
3
public interface PaymentService {
void pay();
}
1
2
3
4
5
6
7
8
@Service
public class CardPaymentService implements PaymentService {
@Override
public void pay() {
// 카드 결제 로직
}
}
1
2
3
4
5
6
7
8
@Service
public class KakaoPaymentService implements PaymentService {
@Override
public void pay() {
// 카카오페이 결제 로직
}
}
언제 Interface와 Impl을 나누는 것이 좋을까?
다음과 같은 경우에는 나누는 것이 좋다.
구현체가 2개 이상 존재할 가능성이 있을 때
외부 API 연동 방식이 바뀔 가능성이 있을 때
테스트용 가짜 구현체가 필요할 때
OCP, DIP 원칙을 적용하고 싶을 때
반대로 구현체가 하나뿐이고 변경 가능성이 낮다면
처음부터 무조건 Interface와 Impl을 나누지 않아도 된다.
3. 트랜잭션(Transaction)
트랜잭션이란 DB 상태를 변경하는 하나의 논리적 작업 단위이다.
- 모두 성공하면 Commit
- 하나라도 실패하면 Rollback
예를 들어 주문 기능에서는 다음 작업들이 함께 일어날 수 있다.
1
2
3
4
주문 생성
재고 감소
결제 정보 저장
배송 정보 저장
이 중 하나라도 실패하면 전체 작업이 취소되어야 한다.
이처럼 여러 작업을 하나의 단위로 묶는 것이 트랜잭션이다.
트랜잭션의 특징
- 작업의 완전성을 보장한다.
- 일부 작업만 DB에 반영되는 문제를 방지한다.
- 비즈니스 작업 단위로 관리하는 것이 좋다.
Spring의 트랜잭션 처리 방식
Spring에서는 선언적 트랜잭션을 주로 사용한다.
1
2
3
4
@Transactional
public void save() {
...
}
@Transactional을 적용하면 Spring이 프록시 객체를 생성하고
트랜잭션 시작, Commit, Rollback 처리를 대신 수행한다.
1
2
정상 실행 → Commit
예외 발생 → Rollback
트랜잭션은 어디에 적용해야 할까?
1
2
3
Controller ❌
Repository ❌
Service ⭕
트랜잭션은 보통 Service 계층에 적용한다.
이유는 하나의 비즈니스 로직이 여러 Repository 작업을 포함할 수 있기 때문이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentRepository paymentRepository;
@Transactional
public void order() {
orderRepository.save(...);
paymentRepository.save(...);
}
}
Repository 각각에 트랜잭션을 걸면 비즈니스 작업 전체를 하나의 단위로 묶기 어렵다.
따라서 트랜잭션은 보통 Service 계층에서 관리한다.
4. Repository 계층
Repository는 Entity를 통해 DB에 접근하는 계층이다.
CRUD 처리
DB 접근 코드 담당
Entity 저장, 조회, 수정, 삭제 담당
Service는 비즈니스 로직을 담당하고 Repository는 DB 접근을 담당한다.
1
Service → Repository → DB
5. Spring Data JPA
JPA란?
JPA는 Java Persistence API의 약자이다.
Java ORM 기술 표준
객체와 관계형 DB 간의 차이를 해결하기 위한 기술
인터페이스, 즉 명세에 해당한다.
구조는 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
Application
↓
Spring Data JPA
↓
JPA
↓
Hibernate
↓
JDBC
↓
DB
Hibernate
Hibernate는 JPA 구현체 중 가장 대표적인 구현체이다.
JPA는 명세이고, Hibernate는 그 명세를 실제로 구현한 기술이다.
1
2
JPA → 표준 인터페이스
Hibernate → JPA 구현체
Spring Data JPA
Spring Data JPA는 JPA를 더 쉽게 사용할 수 있도록 도와주는 프레임워크이다.
Repository 인터페이스 제공
기본 CRUD 메서드 자동 제공
쿼리 메서드 기능 제공
반복적인 DB 접근 코드 감소
1
2
public interface RegistryRepository extends JpaRepository<Registry, Long> {
}
JpaRepository<Entity, PK 타입> 형태로 작성한다.
1
2
Registry → Entity 클래스
Long → PK 타입
Spring Data JPA의 Repository 인터페이스는
Spring이 자동으로 구현체를 만들어 Bean으로 등록해준다.
따라서 일반적으로 @Repository를 생략할 수 있다.
정리
1
2
3
JPA : ORM 기술 표준
Hibernate : JPA 구현체
Spring Data JPA : Repository 사용을 편하게 해주는 프레임워크
6. DTO · Entity · Repository
DTO
DTO는 Data Transfer Object의 약자이다.
계층 간 데이터를 전달하기 위한 객체이다.
Controller에서 요청/응답 데이터를 받을 때 사용
Service로 데이터를 전달할 때 사용
Entity를 직접 노출하지 않기 위해 사용
DTO는 getter/setter 방식으로 만들 수도 있고
생성자, Builder, record 등을 사용해서 만들 수도 있다.
1
2
3
4
5
public class UserRequestDto {
private String name;
private String email;
}
Entity
Entity는 DB 테이블과 매핑되는 객체이다.
@Entity사용JPA가 관리하는 객체
DB 테이블과 연결됨
Repository를 통해 저장, 조회, 수정, 삭제됨
1
2
3
4
5
6
7
8
9
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
DTO와 Entity를 분리하는 이유
Entity를 Controller에서 그대로 사용하면
DB 구조가 외부 API에 그대로 노출될 수 있다.
또한 화면이나 API 요청에 필요한 데이터와 DB에 저장되는 데이터가 항상 같지는 않다.
따라서 보통은 DTO와 Entity를 분리해서 사용한다.
계층 흐름
1
2
3
4
5
6
7
8
9
Client
↓
Controller
↓ DTO
Service
↓ DTO 또는 Entity
Repository
↓ Entity
DB
단순하게 정리하면 다음과 같다.
1
2
3
DTO : 계층 간 데이터 전달용 객체
Entity : DB 테이블과 매핑되는 객체
Repository : Entity를 이용해 DB에 접근하는 계층
7. DAO · Model
JPA를 사용하지 않고 MyBatis나 JDBC를 사용하는 경우에는
Repository 대신 DAO라는 표현을 사용하는 경우가 많다.
DAO
DAO는 Data Access Object의 약자이다.
DB 접근 객체
SQL 실행 담당
Service와 DB 사이에서 데이터 접근 역할 수행
보통
@Repository를 사용한다.
MyBatis를 사용하는 경우 XML Mapper에 SQL을 작성할 수 있다.
1
2
3
4
5
<mapper namespace="project.dao.SampleDAO">
<select id="methodName" resultType="">
SELECT ...
</select>
</mapper>
Model
Model이라는 용어는 상황에 따라 의미가 조금 다르게 사용된다.
MVC에서 Model: 화면에 전달할 데이터
MyBatis/JDBC 환경에서 Model: 데이터를 담는 객체
JPA 환경에서 Entity와 비슷한 의미로 사용되기도 함
다만 Spring/JPA 구조에서는 보통 다음 용어를 더 명확하게 사용한다.
1
2
DTO : 요청/응답 데이터 전달
Entity : DB 테이블 매핑
JPA 미사용 시 흐름
1
2
3
4
5
6
7
8
9
Client
↓
Controller
↓
Service
↓
DAO
↓
DB
REFERENCE