백엔드

SpringBoot 폴더 구조 이해하기

그린티_ 2025. 1. 10. 18:00
반응형

저는 카카오 로그인을 생각하고 User의 속성을 정하고 생성, 삭제, 조회 정도만 해보려고 했습니다.

 

간단 설명

  • Entity → 데이터 모델 정의
  • Repository → 데이터베이스 접근 계층 정의
  • Service → 비즈니스 로직 작성
  • Controller → HTTP 요청 처리
  • DTO → 필요에 따라 작성

폴더 구조 예시

controller, service, repository, entity, dto는 패키지로 생성

src/main/java/com/example/project/
├── controller/
│   └── UserController.java      # 클래스
├── service/
│   ├── UserService.java         # 인터페이스
│   └── UserServiceImpl.java     # 클래스 (인터페이스 구현체)
├── repository/
│   └── UserRepository.java      # 인터페이스
├── entity/
│   └── User.java                # 클래스
├── dto/
│   └── UserDto.java             # 클래스

Entity

  • Entity는 데이터 모델을 정의하며, 데이터베이스 테이블과 매핑됩니다.
  • 데이터를 중심으로 작업하므로 가장 먼저 정의하는 것이 논리적입니다.
  • 여기서 User에서 사용할 속성을 추가하고, lombok을 통해 Getter와 Setter를 설정했습니다.
package com.example.backend.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
public class User {
    @Id
    @GeneratedValue
    private Long id;

    private String nickname;
    private String profileNickname;
    private String profileImage;
}

Repository

  • 데이터베이스와 상호작용하는 인터페이스를 정의하며, Entity를 기반으로 작업합니다.

클래스로 작성하지 않는 이유

  • Spring이 자동으로 구현체를 제공하므로 추가적으로 클래스를 작성할 필요가 없습니다.
package com.example.backend.repository;

import com.example.backend.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

Service

  • Repository를 기반으로 비즈니스 로직을 구현합니다.
  • 데이터를 가공하거나 여러 저장소와 상호작용하는 로직을 작성합니다.
  • Service 계층은 비즈니스 로직을 담당하며, 보통 인터페이스와 클래스(구현체)로 나누는 경우가 많습니다. 하지만, 반드시 인터페이스를 사용할 필요는 없습니다.

인터페이스와 구현체를 나누는 이유

  • 인터페이스:
    • 유연성 확보 (추후 다른 구현체로 교체 가능).
    • 테스트에서 Mock 객체로 교체하기 쉬움.
  • 구현체(클래스 Impl 붙이는 파일):
    • 실제 비즈니스 로직을 작성.

그래도 한번 나눠서 코드를 작성해보고자 나눠보았습니다.

 

인터페이스 코드 예시

package com.example.backend.service;

import com.example.backend.dto.UserDto;

import java.util.List;

public interface UserService {
    UserDto createUser(UserDto userDto); // DTO를 반환
    void deleteUser(Long id);
    List<UserDto> getAllUsers(); // DTO 리스트 반환
}

구현체 코드 예시

package com.example.backend.service;

import com.example.backend.dto.UserDto;
import com.example.backend.entity.User;
import com.example.backend.repository.UserRepository;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;

    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // 엔티티 → DTO 변환 메서드
    private UserDto toDto(User user) {
        return new UserDto(
                user.getId(),
                user.getNickname(),
                user.getProfileNickname(),
                user.getProfileImage()
        );
    }

    // DTO → 엔티티 변환 메서드
    private User toEntity(UserDto userDto) {
        User user = new User();
        user.setId(userDto.getId());
        user.setNickname(userDto.getNickname());
        user.setProfileNickname(userDto.getProfileNickname());
        user.setProfileImage(userDto.getProfileImage());
        return user;
    }

    @Override
    public UserDto createUser(UserDto userDto) {
        User user = toEntity(userDto); // DTO → 엔티티 변환
        User savedUser = userRepository.save(user); // 엔티티 저장
        return toDto(savedUser); // 저장된 엔티티를 DTO로 변환
    }

    @Override
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }

    @Override
    public List<UserDto> getAllUsers() {
        return userRepository.findAll()
                .stream()
                .map(this::toDto) // 엔티티를 DTO로 변환
                .collect(Collectors.toList());
    }
}

Controller

  • Repository를 기반으로 비즈니스 로직을 구현합니다.
  • 데이터를 가공하거나 여러 저장소와 상호작용하는 로직을 작성합니다.
  • RequestMapping으로 주소를 정해줍니다. 
    • ex) localhost:8080/api/users
package com.example.backend.controller;

import com.example.backend.dto.UserDto;
import com.example.backend.entity.User;
import com.example.backend.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/users")
public class UserController {
    private final UserService userService;
    public UserController(UserService userService){
        this.userService = userService;
    }

    @PostMapping
    public ResponseEntity<UserDto> createUser(@RequestBody UserDto userDto) {
        UserDto createdUser = userService.createUser(userDto);
        return ResponseEntity.ok(createdUser);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }

    @GetMapping
    public ResponseEntity<List<UserDto>> getAllUsers() {
        List<UserDto> users = userService.getAllUsers();
        return ResponseEntity.ok(users);
    }

}

Dto

  • 데이터 교환:
    • 서버와 클라이언트 사이에서 데이터를 전달하는 객체로 사용됩니다.
    • 예를 들어, API 요청을 받을 때 DTO 형태로 데이터를 받고, 응답할 때 DTO 형태로 데이터를 반환합니다.
  • 보안 강화:
    • 엔티티에는 비밀번호나 민감한 데이터가 포함될 수 있는데, DTO를 사용하면 외부로 노출하지 않아야 하는 데이터를 제외하고 필요한 데이터만 제공할 수 있습니다.
    • DTO를 통해 데이터의 가공, 필터링, 또는 변환이 가능하여 데이터 노출을 최소화할 수 있습니다.
  • 계층 분리:
    • 엔티티와 DTO를 분리함으로써 도메인 계층과 API 계층 간의 의존성을 줄이고, 데이터베이스 스키마의 변경이 클라이언트에 영향을 미치지 않도록 할 수 있습니다.
  • 유효성 검사:
    • DTO를 통해 클라이언트에서 전달된 데이터에 대해 유효성 검사를 할 수 있습니다.
    • Spring에서는 @Valid 어노테이션과 javax.validation을 사용해 유효성 검사를 쉽게 추가할 수 있습니다.
package com.example.backend.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
    private Long id;
    private String nickname;
    private String profileNickname;
    private String profileImage;
}

+ 가공시키는 코드 예시

nickname과 profileNickname을 조합한 전체 이름을 클라이언트에 제공해야 한다면:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
    private Long id;
    private String nickname;
    private String profileNickname;
    private String profileImage;

    public String getFullName() {
        return nickname + " (" + profileNickname + ")";
    }
}

Entity와 Dto의 차이와 역할

  1. Entity:
    • 데이터베이스 테이블과 직접 매핑되는 클래스입니다.
    • 데이터베이스와의 CRUD 작업을 위해 설계되었습니다.
    • @Entity와 같은 JPA 어노테이션을 사용하여 데이터베이스의 구조를 정의합니다.
    • 비즈니스 로직이나 DTO와 분리하여 데이터베이스와 직접적인 상호작용을 담당합니다.
  2. DTO (Data Transfer Object):
    • 클라이언트와 서버 간 데이터를 교환하기 위한 객체입니다.
    • 주로 컨트롤러에서 사용되며, API의 요청 및 응답 데이터를 정의합니다.
    • 비즈니스 로직이나 데이터베이스와의 직접적인 연관이 없습니다.
    • Entity의 필드를 그대로 사용할 수도 있지만, 필요에 따라 일부 필드만 포함하거나 추가적인 정보(예: 상태, 메시지 등)를 가질 수 있습니다.
반응형

'백엔드' 카테고리의 다른 글

Springboot MySQL 연동하기  (2) 2025.01.14
SpringBoot 첫 프로젝트 생성  (2) 2025.01.10