- API 구현 개요
// dto - AddArticleRequest.java
@NoArgsConstructor //기본 생성자 추가
@AllArgsConstructor // 모든 필드 값을 파라미터로 받는 생성자 추가
@Getter
public class AddArticleRequest { // 서비스계층에서 요청 받은 데이터를 엔티티로 생성.
private String title;
private String content;
public Article toEntity() { // 생성자를 사용해 객체 생성
return Article.builder()
.title(title)
.content(content)
.build(); // 빌더 패턴을 사용해 DTO를 엔티티로 만들어주는 메소드.
}
}
DTO
데이터를 옮기기 위해 사용하는 전달자 역할을 하는 객체이다.
별도의 비즈니스 로직을 포함하지 않는다.
DAO
데이터베이스와 연결되고 데이터를 조회하고 수정하는데 사용하는 객체이기 때문에 데이터 수정과 관련된 로직이 포함된다.
toEntity()
빌더 패턴을 사용해 DTO를 엔티티로 만들어주는 메서드이다. 블로그 글을 추가할 때 저장할 엔티티로 변환하는 역할을 한다.
// service - BlogService.java
@RequiredArgsConstructor // final이 붙거나 @NotNull이 붙은 필드의 생성자 추가
@Service // 빈으로 등록
public class BlogService {
private final BlogRepository blogRepository;
public Article save(AddArticleRequest request){ // 블로그 글 추가 메소드
return blogRepository.save(request.toEntity());
}
}
@RequiredArgsConstructor
생성자가 하나 이상 존재할 시 @Autowierd가 자동으로 생성되어 의존성을 주입할 수 있다.
따라서 해당 어노테이션을 통해 생성자를 생성함과 동시에 의존성을 주입을 해준다.
의존성을 주입 받음으로써 빈으로 등록된 객체들을 해당 클래스에서 직접 생성하지 않고 사용할 수 있게 된다.
@Service
비즈니스 로직을 담당하고 있는 클래스로써, 해당 클래스를 빈으로 서블릿 컨테이너에 등록한다.
save()
JpaRepository에서 지원하는 저장 메서드 save()로 AddArticleRequest 클래스에 저장된 값들을 article 데이터베이스에 저장한다.
cf. BlogRepository는 JpaRepository를 상속 받고, JpaRepository의 부모 클래스인 CrudRepository에 save() 메서드가 선언이 돼있다. 이 메서드를 사용하여 데이터베이스에 Article 엔티티를 저장할 수 있게된다.
@RequiredArgsConstructor
@RestController // HTTP Response Body에 객체 데이터를 JSON 형식으로 반환하는 컨트롤러
public class BlogApiController {
private final BlogService blogService;
@PostMapping("/api/articles") // HTTP 메서드가 POST일 때 전달 받은 URL과 동일하면 해당 메서드로 매핑
public ResponseEntity<Article> addArticle(
@RequestBody AddArticleRequest request){
Article savedArticle = blogService.save(request);
return ResponseEntity.status(HttpStatus.CREATED)
.body(savedArticle); // 요청한 자원이 성공적으로 생성되었으면 저장된 블로그 글 정보를 응답 객체에 담아 반환.
}
}
@RequiredArgsConstructor
의존성 주입 역할.
@RestController
HTTP 응답으로 객체 데이터를 JSON 형식으로 반환한다.
@PostMapping
HTTP 메서드가 POST일 때 요청받은 URL과 동일하면 addArticle() 메서드에 매핑한다.
@RequestBody
HTTP를 요청할 때 본문 내용을 자바 객체로 변환하여 AddArticleRequest에 매핑한다.
ResponseEntity.status(a).body(b);
ResponseEntity는 스프링 프레임워크에서 사용자의 HttpRequest에 대한 응답 데이터를 포함하는 클래스이다.
따라서 HttpStatus, HttpHeaders, HttpBody를 포함하고, RestTemplate 또는 컨트롤러에서 주로 사용된다.
해당 코드는 HTTP 요청을 보냈을 때 응답 코드를 a로 설정하여 데이터 b를 반환한다는 의미이다.
상태코드를 알맞게 설정하여 반환해야 클라이언트가 요청 상태를 알 수 있다.
- 응답 코드 종류
- 200 ok : 요청을 성공적으로 수행.
- 201 Created : 요청이 성공적으로 수행되었고, 새로운 리소스 생성.
- 400 Bad Requeste : 요청 값이 잘못되어 요청에 실패.
- 403 Forbidden : 권한이 없어 요청에 실패.
- 404 Not Found : 요청 값으로 찾은 리소스가 없어 요청에 실패.
- 500 Internal Server Error : 서버상에 문제가 있어 요청에 실패.
H2 콘솔 활성화
// application.yml
spring:
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
defer-datasource-initialization: true
datasource:
url: jdbc:h2:mem:testdb
h2:
console:
enabled: true
Postman 접속후 HTTP 메서드 POST, URL: http://localhost:8080/api/articles,
[body]는 JSON으로 변경 후 다음과 같이 작성하여 서버에 요청 전송.
{
"title": "제목",
"content": "내용"
}
localhost:8080/h2-console에 접속 하여 JDBC URL에 application.yml의 datasource의 URL 값을 입력 후 H2 데이터베이스 접속하여 해당 테이블을 확인하여 값이 잘 들어왔는지 확인한다.
H2 데이터베이스에 매번 접속하여 확인하기가 번거로우니, 테스트 과정을 간소화 시켜줄 테스트 코드를 작성한다.
BlogApiController 클래스에 Alt+ Enter를 누르고 [Create Test]를 눌러 기본값 설정대로 Test 코드를 생성하여 다음과 같이 코드를 수정한다.
@SpringBootTest // 테스트용 애플리케이션 컨텍스트
@AutoConfigureMockMvc // MockMvc 생성
class BlogApiControllerTest {
@Autowired
protected MockMvc mockMvc;
@Autowired
protected ObjectMapper objectMapper; // 직렬화, 역직렬화를 위한 클래스
@Autowired
private WebApplicationContext context;
@Autowired
BlogRepository blogRepository;
@BeforeEach // 테스트 실행 전 실행하는 메소드
public void MockMvcSetUp(){
this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
blogRepository.deleteAll();
}
}
Given-When-Then 패턴 작성
- Given
블로그 글 추가에 필요한 요청 객체 생성
- When
블로그 글 추가 API에 요청 전송. 요청 타입은 JSON이며, given절에서 미리 만들어둔 객체를 요청 본문으로 함께 보낸다.
- Then
응답 코드가 201 Created인지 확인 후 Blog를 전체 조회해 크기가 1인지 확인하고, 실제 저장된 데이터와 요청 값을 비교한다.
// BlogApiControllerTest.java
@SpringBootTest
@AutoConfigureMockMvc
class BlogApiControllerTest {
~생략~
@DisplayName("addArticle: 블로그 글 추가에 성공한다.")
@Test
public void addArticle() throws Exception {
// given
final String url = "/api/articles";
final String title = "title";
final String content = "content";
final AddArticleRequest userRequest = new AddArticleRequest(title, content);
// 객체를 JSON으로 직렬화
final String requestBody = objectMapper.writeValueAsString(userRequest);
// when
// 설정한 내용을 바탕으로 요청 전송
ResultActions result = mockMvc.perform(post(url)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(requestBody));
// then
result.andExpect(status().isCreated());
List<Article> articles = blogRepository.findAll();
assertThat(articles.size()).isEqualTo(1); // 크기가 1인지 검증
assertThat(articles.get(0).getTitle()).isEqualTo(title);
assertThat(articles.get(0).getContent()).isEqualTo(content);
}
}
writrValueAsString()
객체를 JSON으로 직렬화 한다.
assertThat()
각 데이터들의 값들을 검증하는 역할을 한다.
- assertThat(articles.size()).isEqualTo(1)
블로그 글 크기가 1임을 검증
- assertThat(articles.size()).isGreaterThan(2)
블로그 글 크기가 2보다 큰 지를 검증
- assertThat(articles.size()).isLessThan(5)
블로그 글 크기가 5보다 작은 지를 검증
- assertThat(articles.size()).isZero()
블로그 글 크기가 0인 지를 검증
- assertThat(articles.title()).isEqualTo("제목")
블로그 글의 title이 "제목"인 지를 검증
- assertThat(articles.title()).isNotEmpty()
블로그 글의 title이 비어있지 않은 지를 검증
- assertThat(articles.title()).isContains("제")
블로그 글의 title이 "제"를 포함하는 지를 검증