본문 바로가기
Clone Coding/스프링 부트와 AWS

[Spring] 등록 API 만들기 (+h2 웹 콘솔 이용하기)

by 연로그 2021. 1. 15.
반응형

API 생성을 위한 클래스

1. Dto : Request 데이터 받기

2. Controller : API 요청 받기

3. Service : 트랜잭션, 도메인 기능 간의 순서 보장

  Service에서 비즈니스 로직을 처리해야 한다고 오해하기 쉬운데, 트랜잭션과 도메인 간 순서만 보장하면 된다.

 

 

Spring 웹 계층

Spring 웹 계층

Web Layer

- Controller와 View Template 영역

- 필터, 인터셉터, @ControllerAdvice 등 외부 요청과 응답에 대한 전반적인 영역

 

Service Layer

- @Service

- 보통 Controller와 Dao의 중간 영역

- @Transactional이 사용되어야 하는 영역

 

Repository Layer

- DB 같은 데이터 저장소에 접근하는 영역

- Dao 영역과 유사

 

Dtos

- Dto: 계층 간 데이터 교환을 위한 객체

 

Domain Model

: 도메인이라는 개발 대상을 모든 사람이 동일한 관점에서 이해, 공유할 수 있도록 단순화시킨 것

- db의 테이블과 관계 필수 X. (VO처럼 값 객체들도 이 영역에 해당)

- ex: 택시 앱-> 도메인: 배차, 탑승, 요금, ...

 

 

그렇다면 로직은 어느 영역에서 수행되어야 할까?

기존 서비스로 처리하던 방식 (트랜잭션 스크립트)

-> 모든 로직이 서비스 클래스 내부에서 처리

-> 서비스 계층이 무의미

@Transactional
public Order cancelOrer(int orderId) {
    OrdersDto order = orderDao.selectOrders(orderId);
    BillingDto billing = billingDao.selectBilling(orderId);
    DeliveryDto delivery= deliveryDao.selectDelivery(orderId);

    String deliveryStatus = delivery.getStatus();

    if("IN_PROGRESS".equals(deliveryStatus)) {
        delivery.setStatus("CANCEL");
        deliveryDao.update(delivery);
    }

    order.setStatus("CANCEL");
    ordersDao.update(order);

    billing.setStauts("CANCEL");
    deliveryDao.update(billing);

    return order;
}

 

도메인 모델에서 처리할 경우

-> 트랜잭션과 도메인 간 순서만 보장                                                                

@Transactional
public Order cancelOrer(int orderId) {
    Orders order = orderRepository.findById(orderId);
    Billing billing = billingRepository.findByOrderId(orderId);
    Delivery delivery= deliveryRepository.findByOrderId(orderId);

    delivery.cancel();
    order.cancel();
    billing.cancel();

    return order;
}

 

 

이제 등록 기능을 만들어보자.

/web/PostsApiController.java

@RequiredArgsConstructor
@RestController
public class PostApiController {
	private final PostsService postsService;
	
	@PostMapping("/api/v1/posts")
	public Long save(@RequestBody PostsSaveRequestDto requestDto) {
		return postsService.save(requestDto);
	}
}

 

/service/posts/PostsService.java

- @Autowired가 아닌 생성자를 이용한 Bean 주입

@RequiredArgsConstructor	// final로 선언된 모든 필드로 생성자 생성
@Service
public class PostsService {
	private final PostsRepository postsRepository;
	
	@Transactional
	public Long save(PostsSaveRequestDto requestDto) { 
		return postsRepository.save(requestDto.toEntity()).getId();
	}
}

 

/web/dto/PostsSaveRequestDto.java

@Getter
@NoArgsConstructor
public class PostsSaveRequestDto {
	private String title;
	private String content;
	private String author;
	
	@Builder
	public PostsSaveRequestDto(String title, String content, String author) {
		this.title 		= title;
		this.content 	= content;
		this.author 	= author;
	}
	
	public Posts toEntity() {
		return Posts.builder()
				.title(title)
				.content(content)
				.author(author)
				.build();
	}
}

 

이제 Test 코드를 짜보겠다.

/web/PostsApiControllerTest.java

@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";
		String url = "http://localhost:" + port + "/api/v1/posts";
		PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder()
				.title(title)
				.content(content)
				.author("author")
				.build();
		
		//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);
		
	}
}

- 이전의 HelloController와 달리 @WebMvcTest를 사용하지 않았다.

 -> @WebMvcTest에서는 JPA 기능이 작동하지 않기 때문

 => JPA 기능까지 한 번에 테스트할 경우엔 @SpringBootTest와 TestRestTemplate을 사용하면 된다.

 

수정, 조회 기능도 이와 비슷한 구조로 생성하면 된다.

자세한 코드는 책을 참고하길 바란다.

 

 

참고로 db 사용에 앞서 웹 콘솔을 이용하려면 application.properties를 수정해야 한다. (다음 코드 추가)

spring.h2.console.enabled=true

이후에 Application 클래스의 main 메소드 실행 후, http://localhost:8080/h2-console로 접속하면 다음 화면처럼 보인다.

JDBC URL을 jdbc:h2:mem:testdb로 수정하고 connect 버튼을 누른다.

위 과정을 잘 따라왔다면 왼쪽 상단에 POSTS 테이블이 존재해야 한다.


해당 게시글은 [ 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 / 이동욱 ] 책을 따라한 것을 정리하기 위한 게시글입니다. 요약, 생략한 부분이 많으니 보다 자세한 설명은 책 구매를 권장합니다.

 

반응형