티스토리 뷰

반응형

API를 만들기 위해서 필요한 3개의 클래스

  • Dto: Request 데이터를 받음
  • Controller: API 요청을 받음
  • Service: 트랜잭션, 도메인 기능 간의 순서를 보장
💡 Service에서 비즈니스 로직을 처리하는 것이 아니다. Service는 트랜잭션, 도메인 간 순서 보장의 역할만 한다.

 

Spring Web Layer

Web Layer

  • 컨트롤러(@Controller)와 JSP/Freemaker 등의 뷰 템플릿 영역
  • 필터(@Filter), 인터셉터, 컨트롤러 어드바이스(@ControllerAdvice) 등 외부 요청과 응답에 대한 전반적인 영역

Service Layer

  • @Service에 사용되는 서비스 영역
  • 일반적으로 Controller와 Dao의 중간 영역에서 사용
  • @Transactional이 사용되어야 하는 영역

Repository Layer

  • Database와 같이 데이터 저장소에 접근하는 영역

Dtos

  • Dto(Data Transfer Object): 객체 간에 데이터 교환을 위한 객체
  • Dtos는 Dto의 영역
  • 뷰 템플릿 엔진에서 사용될 객체나 Repository Layer에서 결과로 넘겨준 객체 등이 해당됨

Domain Model

  • 도메인을 단순화 시킨 것
  • 비즈니스 처리를 담당
💡 Transcation Script 🆚 Domain Model

모든 로직이 서비스 클래스 내부에서 처리된다. 그래서 서비스 계층이 무의미해지며, 객체는 단순히 데이터 덩어리 역할을 한다.
Transcation Script와 반대로 도메인 모델에서 서비스 메소드는 트랜잭션과 도메인간의 순서만 보장해 준다.
도메인 모델에서는 객체가 단순한 데이터 덩어리 뿐만 아니라 데이터 처리 등과 같은 기능도 한다.
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private PostsRepository postsRepository;

    @After
    public void tearDown() throws Exception {
        postsRepository.deleteAll();
    }

    @Test
    public void Posts_등록된다() throws Exception {
        // given
        String title = "title";
        String content = "content";
        PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder()
                .title(title)
                .content(content)
                .author("author")
                .build();

        String url = "http://localhost:" + port + "/api/v1/posts";

        // when
        ResponseEntity<Long> responseEntity = restTemplate.
                postForEntity(url, requestDto, Long.class);

        // then
        assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(responseEntity.getBody()).isGreaterThan(0L);

        List<Posts> all = postsRepository.findAll();
        assertThat(all.get(0).getTitle()).isEqualTo(title);
        assertThat(all.get(0).getContent()).isEqualTo(content);
    }
}

여기서는 @WebMvcTest가 아닌 @SpringBootTest를 사용하였다. @WebMvcTest의 경우 JPA 기능이 작동하지 않기 때문에 JPA 기능을 테스트 하기 위해서 @SpringBootTest를 사용했다.

 

@RequiredArgsConstructor
@RestController
public class PostsApiController {
    private final PostsService postsService;

    @PostMapping("/api/v1/posts")
    public Long save(@RequestBody PostsSaveRequestDto requestDto) {
        return postsService.save(requestDto);
    }

    @PutMapping("/api/v1/posts/{id}")
    public Long update(@PathVariable Long id, @RequestBody PostsUpdateRequestDto requestDto) {
        return postsService.update(id, requestDto);
    }

    @GetMapping("/api/v1/posts/{id}")
    public PostsResponseDto findById(@PathVariable Long id) {
        return postsService.findById(id);
    }
}
@Getter
public class PostsResponseDto {

    private Long id;
    private String title;
    private String content;
    private String author;

    public PostsResponseDto(Posts entity) {
        this.id = entity.getId();
        this.title = entity.getTitle();
        this.content = entity.getContent();
        this.author = entity.getAuthor();
    }
}
@Getter
@NoArgsConstructor
public class PostsUpdateRequestDto {
    private String title;
    private String content;

    @Builder
    public PostsUpdateRequestDto(String title, String content) {
        this.title = title;
        this.content = content;
    }
}
@Getter
@NoArgsConstructor
@Entity
public class Posts {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 500, nullable = false)
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;

    private String author;

    @Builder
    public Posts(String title, String content, String author) {
        this.title = title;
        this.content = content;
        this.author = author;
    }

    public void update(String title, String content) {
        this.title = title;
        this.content = content;
    }
}
@RequiredArgsConstructor
@Service
public class PostsService {
    private final PostsRepository postsRepository;

    @Transactional
    public Long save(PostsSaveRequestDto requestDto) {
        return postsRepository.save(requestDto.toEntity()).getId();
    }

    @Transactional
    public Long update(Long id, PostsUpdateRequestDto requestDto) {
        Posts posts = postsRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id=" + id));

        posts.update(requestDto.getTitle(), requestDto.getContent());

        return id;
    }

    public PostsResponseDto findById(Long id) {
        Posts entity = postsRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id=" + id));

        return new PostsResponseDto(entity);
    }
}

updata 기능을 구현하였다. JPA의 영속성 컨텍스트 때문에 update 기능에서 DB에 쿼리를 날리는 부분이 없다. 

 

영속성 컨텍스트

엔티티를 영구 저장하는 환경
일종의 논리적 개념

JPA의 Entity Manager가 활성화된 상태로 트랜잭션 안에 DB에서 데이터를 가져온다. <- 이 때 이 데이터는 영속성 컨텍스트가 유지된 상태이다.

영속성 컨텍스트가 유지된 상태에서 데이터를 변경하면, 트랜잭션이 끝나는 시점에 테이블에 변경이 반영된다.

💡 더티 체킹(Dirty Checking)

Entity 객체의 값만 변경하면 Updata 쿼리를 날릴 필요가 없다.

업데이트가 된 것을 확인할 수 있다.

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함