REST Web Service with Spring MVC

  • 효율적으로 MVC, Web layer를 설계하기 위해 기본적인 REST API를 짜보면서 기초를 학습했다.

MapStruct : Entity와 DTO의 Mapping

등장 배경

  • Spring Framework 개발 환경을 예로, 각 레이어(Controller, Service, Repository 등)에서
    데이터를 주고 받거나, 비즈니스 로직에서 정의된 객체 타입을 다른 타입으로 변환하는 일이 빈번하게 일어난다.

문제점

  • 소개된 배경으로 인하여, 그동안 개발자가 코드를 작성할 때는 다음과 같은 문제가 생겼다.
  1. 반복적이고 코드 중복 발생
  2. 개발자의 실수 번복
  3. 코드 생산성 저하
  4. 비즈니스 로직에 섞여(독립적X), 코드가 복잡해진다.
    등등의 이유로 "유지보수"가 힘들어진다.

해결 방법 : MapStruct

  • Java 개발 환경에서 객체간 매핑에 대해 코드를 자동으로 생성해주는 매핑 라이브러리
    Annotation Processor를 사용해 컴파일 시 매핑 코드를 생성한다.

그래서 뭐가 어떻게 좋은데?

  • 컴파일 시 설정된 방식으로 오류 확인 가능 (코드 생성 시)
  • 리플렉션을 사용하지 않아 매핑 속도가 빠르다.
    속도 비교 자료
    - ModelMapper와 Orika 에서는 Runtime 시점에 Reflection을 통해 Mapping을 해서 객체의 사이즈가 커질수록 메모리를 많이 사용해서 성능 저하의 주된 원인이 되는 듯 하다.

MVC, Web Layer 짜보기

  • 스프링 계층은 크게 Presentation, Business, Data Access 3가지로 나뉜다.

1. Presentation Layer

  • 브라우저 상 웹 클라이언트의 요청 및 응답 처리
  • Service, DA 계층에서 발생하는 Exception 처리
  • @Controller 어노테이션을 사용하여 작성된 Controller 클래스가 이 계층에 속한다.

2. Service Layer

  • 애플리케이션 비즈니스 로직 처리와 비즈니스와 관련된 도메인 모델의 적합성 검증
  • 트랜잭션 관리
  • 프레젠테이션 계층과 데이터 액세스 계층 사이를 연결하는 역할로서 두 계층이 직접적으로 통신하지 않게 한다.
  • Service 인터페이스와 @Service 어노테이션을 사용하여 Service 구현 클래스가 이 계층에 속한다.

3. Data Access Layer

  • ORM (Mybatis, Hibernate)를 주로 사용하는 계층
  • DAO 인터페이스와 @Repository 어노테이션을 사용하여 작성된 DAO 구현 클래스가 이 계층에 속한다.
  • Database에 Data를 CRUD 하는 계층

+ Domain Model Layer

  • DB의 테이블과 매칭될 클래스
  • Entity 클래스라고도 부른다.

코드와 정의

Domain

@Data
@Entity
public class Vendor {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	private String name;

}

Entity Class

  • Domain 이라고도 부른다(JPA 사용할 때 사용)
  • 실제 DB 테이블과 매칭될 클래스
  • Entity 클래스 또는 가장 Core 한 클래스라고 부른다.
  • Domain 로직만을 가지고 있어야 하며 Presentation Logic을 가지고 있어서는 안된다

DTO

@Data
@NoArgsConstructor
@AllArgsConstructor
public class VendorDto {

	private Long id;
	private String name;

}

...

@Data
@NoArgsConstructor
@AllArgsConstructor
public class VendorListDto {
	List<VendorDto> vendors;
}

DTO (Data Transfer Object)

  • 각 계층 간 데이터 교환을 위한 객체 (데이터를 주고 받을 포맷)
  • Domain, VO 라고도 부른다.
  • DB에서 데이터를 얻어 Service, Controller 등으로 보낼 때 사용한다.
  • 로직을 갖지 않고 순수하게 getter/setter 메소드를 가진다.

Entity 클래스와 DTO 클래스 분리하는 이유는?

  • View Layer와 DB Layer 역할을 철저하게 분리하기 위해
    • 이거 무시했다간 나중에 한 파일에 온갖 코드가 덕지 덕지 붙어있는 맛있는 스파게티 코드가 완성된다. (경험담)
    • 테이블과 매핑되는 Entity 클래스가 변경되면 여러 클래스에 영향을 끼치게 되지만 View와 통신하는 DTO는 자주 변경되므로 분리해주도록 한다.
    • 즉 DTO는 Domain 모델을 복사한 것, 다양한 프레젠테이션 로직을 추가한 정도로 사용하며 도메인 모델은 Persistent(영속성) 만을 위해서 사용한다.

Mapper

@Mapper
public interface VendorMapper {

	VendorMapper INSTANCE = Mappers.getMapper(VendorMapper.class);

	VendorDto vendorToVendorDto(Vendor vendor);

	Vendor vendorDtoToVendor(VendorDto vendorDto);
}

  • 사진과 같이 View 를 통해 요청이 들어오면 각 계층을 타고 DB까지 내려와 정보를 얻어 뿌려준다.

Repository

public interface VendorRepository extends JpaRepository<Vendor, Long> {}

DAO (Data Access Object)

  • DB에 접근하는 객체, DB를 사용해 데이터를 조작하는 기능을 하는 객체 (MyBatis 사용 시 DAO, Mapper 사용)
  • Repository 라고도 부른다. ( JPA 사용 시 Repository 사용 )
  • Service 계층과 DB를 연결하는 고리 역할을 한다.

Service

public interface VendorService {

	List<VendorDto> getAllVendors();

	VendorDto getVendorByName(String name);

	VendorDto getVendorById(Long id);

	VendorDto createNewVendor(VendorDto vendorDto);

	VendorDto saveVendorByDto(Long id, VendorDto vendorDto);

	VendorDto patchVendor(Long id, VendorDto vendorDto);

	void deleteVendorById(Long id);
}

... Impl 생략

Service

  • Web Layer 바로 아래에 존재하는 계층으로 트랜잭션에 대한 경계 역할
  • 어플리케이션과 인프라 서비스 모두를 포함
  • 어플리케이션 서비스는 서비스 계층의 공개 API를 공급하며 트랜잭션의 경계 역할과 응답을 책임지고 있다.
  • Controller와 Dao의 중간영역에서 많이 사용되며 @Transcational, @Service에 사용되는 영역이다.

Controller

@GetMapping
	@ResponseStatus(HttpStatus.OK)
	public VendorListDto getListOfVendors() {
		return new VendorListDto(vendorService.getAllVendors());
	}

	@GetMapping({"/{id}"})
	@ResponseStatus(HttpStatus.OK)
	public VendorDto getVendorById(@PathVariable Long id) {
		return vendorService.getVendorById(id);
	}

	@PostMapping
	@ResponseStatus(HttpStatus.CREATED)
	public VendorDto createNewCustomer(@RequestBody VendorDto vendorDto) {
		return vendorService.createNewVendor(vendorDto);
	}

	@PutMapping("{/id}")
	@ResponseStatus(HttpStatus.OK)
	public VendorDto updateVendor(@PathVariable Long id, @RequestBody VendorDto vendorDto) {
		return vendorService.saveVendorByDto(id, vendorDto);
	}

	@PatchMapping("{/id}")
	@ResponseStatus(HttpStatus.OK)
	public VendorDto patchVendor(@PathVariable Long id, @RequestBody VendorDto vendorDto) {
		return vendorService.patchVendor(id, vendorDto);
	}

	@DeleteMapping("{/id}")
	@ResponseStatus(HttpStatus.OK)
	public void deleteVendor(@PathVariable Long id) {
		vendorService.deleteVendorById(id);
	}
  • 사용자의 요청이 진입하는 지점 (Entry Point)
  • 요청에 따라 어떤 처리를 할 지 결정한다.
    - 단 Controller는 결정만 하고 실질적인 처리는 Service Layer에서 담당한다.
  • 사용자에게 View 또는 통신 결과를 응답으로 보내준다.

Test Code

자세한 코드는 Github에!


출처

MapStruct
Web Layer 사진 및 자료 출처

좋은 웹페이지 즐겨찾기