TIL - 20251012

juni·2025년 10월 12일

TIL

목록 보기
151/318

1012 Spring Boot : 서비스, 트랜잭션, DTO


✅ 1. 계층형 아키텍처 (Layered Architecture)와 서비스 계층

  • Spring 애플리케이션은 일반적으로 계층형 아키텍처를 따릅니다. 이는 각 부분이 특정 책임만 갖도록 코드를 구성하여, 시스템의 유지보수성과 확장성을 높이는 설계 방식입니다.

  • 주요 계층:

    1. Controller (Presentation Layer): HTTP 요청을 받고, 응답을 보내는 역할. 사용자의 입력을 검증하고, 비즈니스 로직 처리를 서비스 계층에 위임합니다.
    2. Service (Business Layer): 애플리케이션의 핵심 비즈니스 로직을 수행하는 곳. 여러 Repository를 조합하여 하나의 비즈니스 기능을 완성하거나, 트랜잭션을 관리하는 등 가장 중요한 역할을 합니다.
    3. Repository (Persistence/Data Access Layer): 데이터베이스에 직접 접근하여 데이터를 CRUD하는 역할. (어제 학습한 JpaRepository가 여기에 해당)

➕ 서비스 계층 (@Service)

  • 역할: 컨트롤러와 리포지토리 사이의 중재자 역할을 합니다.
    • 컨트롤러로부터 받은 데이터를 가공하여 리포지토리에 전달합니다.
    • 리포지토리로부터 받은 엔티티(Entity)를 가공하여 컨트롤러에 전달합니다.
    • 트랜잭션의 경계를 설정합니다.
@Service
public class PostService {

    private final PostRepository postRepository;
    private final UserRepository userRepository;

    // 생성자 주입 (Dependency Injection)
    public PostService(PostRepository postRepository, UserRepository userRepository) {
        this.postRepository = postRepository;
        this.userRepository = userRepository;
    }

    // 비즈니스 로직 예시: 게시글 생성
    public Post createPost(String title, String content, Long userId) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new IllegalArgumentException("User not found"));
        
        Post newPost = new Post(title, content, user);
        return postRepository.save(newPost);
    }
}

✅ 2. 트랜잭션 (Transaction)과 @Transactional

  • 트랜잭션이란 데이터베이스의 상태를 변화시키는 하나의 논리적인 작업 단위입니다. 이 작업 단위 내의 모든 연산은 모두 성공(Commit)하거나, 하나라도 실패하면 모두 실패(Rollback)해야 합니다. (All or Nothing)

  • 예시: 계좌 이체 (A 계좌 출금 + B 계좌 입금 = 하나의 트랜잭션)

@Transactional 어노테이션

  • Spring은 @Transactional 어노테이션을 통해 선언적으로 트랜잭션을 관리하는 매우 강력한 기능을 제공합니다.

  • 이 어노테이션을 서비스 계층의 메서드나 클래스에 붙이면, Spring은 해당 메서드가 시작될 때 트랜잭션을 시작하고, 메서드가 성공적으로 종료되면 Commit, 예외(기본적으로 RuntimeException)가 발생하면 Rollback을 자동으로 수행합니다.

  • 주요 속성:

    • readOnly = true: 조회(SELECT)만 하는 메서드에 이 속성을 적용하면, JPA가 성능 최적화를 수행합니다. 데이터를 변경하지 않으므로, 읽기 전용 트랜잭션은 성능 향상에 도움이 됩니다. 매우 중요하고 자주 사용됩니다.
    • propagation: 트랜잭션 전파 규칙을 설정합니다. (e.g., REQUIRED, REQUIRES_NEW)
@Service
@Transactional(readOnly = true) // 클래스 레벨에 기본으로 읽기 전용 설정
public class PostService {
    // ...

    @Transactional // 쓰기 작업이므로 클래스 설정을 덮어쓰고 readOnly=false (기본값) 적용
    public Post createPost(String title, String content, Long userId) {
        // ... 비즈니스 로직 ...
        return postRepository.save(newPost);
    }

    public Post getPostById(Long postId) {
        // 읽기 작업이므로 클래스 레벨의 readOnly=true 설정이 적용됨
        return postRepository.findById(postId).orElseThrow(...);
    }
}

✅ 3. DTO (Data Transfer Object)의 역할과 필요성

  • DTO란 계층 간에 데이터를 전달(Transfer)하는 데 사용되는 객체입니다.

  • 왜 엔티티(Entity)를 직접 사용하면 안 되는가?

    1. API 스펙의 불안정성: 엔티티는 데이터베이스 스키마와 1:1로 매핑됩니다. 만약 엔티티에 필드를 추가하거나 변경하면, 그 변경 사항이 API 응답에 그대로 노출되어 API 스펙이 불안정해집니다.
    2. 불필요한 정보 노출: 엔티티에는 비밀번호나 내부 관리용 필드 등 외부에 노출되면 안 되는 민감한 정보가 포함될 수 있습니다.
    3. 양방향 연관관계 문제: 양방향으로 매핑된 엔티티를 JSON으로 변환할 때, 서로를 계속 참조하면서 무한 루프에 빠져 스택 오버플로우가 발생할 수 있습니다.
    4. 요청/응답과 엔티티의 불일치: 회원가입 요청 시 필요한 데이터와 실제 User 엔티티의 필드가 정확히 일치하지 않는 경우가 많습니다.

➕ DTO의 활용

  • Request DTO: 컨트롤러가 클라이언트로부터 요청을 받을 때 사용하는 DTO. @Valid를 통한 유효성 검증 로직을 포함하는 경우가 많습니다.

  • Response DTO: 컨트롤러가 클라이언트에게 응답을 보낼 때 사용하는 DTO. 엔티티에서 필요한 정보만 선별하여 담습니다.

  • 흐름:

    1. [Controller] Request DTO로 요청을 받음.
    2. [Controller] Request DTOService에 전달.
    3. [Service] Request DTO의 데이터를 기반으로 비즈니스 로직 수행 후, Entity를 조회하거나 생성.
    4. [Service] EntityResponse DTO로 변환하여 Controller에 반환.
    5. [Controller] Response DTO를 JSON으로 변환하여 클라이언트에게 응답.
// PostRequestDto.java (요청 DTO)
public class PostRequestDto {
    private String title;
    private String content;
    // ...
}

// PostResponseDto.java (응답 DTO)
public class PostResponseDto {
    private Long id;
    private String title;
    private String authorName;

    public PostResponseDto(Post post) { // 엔티티를 DTO로 변환하는 생성자
        this.id = post.getId();
        this.title = post.getTitle();
        this.authorName = post.getUser().getUsername();
    }
}

📌 요약

  • Spring 애플리케이션은 Controller - Service - Repository계층형 아키텍처를 따르며, 핵심 비즈니스 로직은 서비스 계층에서 처리됩니다.
  • @Transactional 어노테이션을 서비스 계층에 적용하여, 데이터의 일관성을 보장하는 트랜잭션을 선언적으로 손쉽게 관리할 수 있습니다. (특히 readOnly = true 최적화가 중요)
  • 엔티티를 API 계층에 직접 노출하는 것은 매우 위험하며, 반드시 Request/Response DTO를 사용하여 계층 간 데이터를 전달해야 합니다. 이는 API 스펙을 안정적으로 유지하고, 보안을 강화하며, 무한 루프 문제를 방지하는 핵심적인 설계 원칙입니다.

0개의 댓글