DevOpsスプリングサーバのエンドツーエンドの導入:DTOの使用理由、コントローラ、サービス層の作成
本番前に言うこと
今回の記事では、ControllerとService layerを作成します.
簡単なコントローラとサービス層を作成するので、個別のテストコードは作成しません.コードの作成方法ではなく、Springサーバの導入方法に重点を置いています.
DTOが何なのか知っていて、それを使ってみました。
DTOを構成する前に、DTOを使用する理由を理解しておきましょう.
サーバ・アプリケーションを構成するときにエンティティに直接戻ることはできません.DTOを使用して返さなければなりません.しかし、DTOをなぜ使うのか分からないかもしれません.実は私はこの文章を書く前に原因を知らなかった.
DTOを使用する理由について、有名な書籍のクリーンアップコードの著者マーティン・ファラーはよく説明しています.
上の文章を翻訳すると、内容はこうです.
ダイレクトコール
DTOを実施しましょう。ここではコードだけを見せてあげます。
MenuRequestDto.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MenuRequestDto {
private String name;
@JsonProperty("shop_id")
private Long shopId;
}
MenuResponseDto.java@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MenuResponseDto {
private Long id;
private String name;
@JsonProperty("shop_name")
private String shopName;
}
ShopRequestDto.java@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ShopRequestDto {
private String name;
private String address;
public static Shop dtoToEntity(ShopRequestDto requestDto) {
return Shop.builder()
.name(requestDto.getName())
.address(requestDto.getAddress())
.build();
}
}
ShopResponseDto.java@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ShopResponseDto {
private Long id;
private String name;
private String address;
}
サービス層の構成
まずはMenuServiceを見てみましょう
@Service
@RequiredArgsConstructor
public class MenuService {
private final MenuRepository menuRepository;
private final ShopRepository shopRepository;
public ResponseEntity<SingleResult<MenuResponseDto>> read(Long id) {
return menuRepository.findById(id).map(menu -> ResponseEntity.ok(
ResultProvider.getSingleResult(entityToResponseDto(menu)))
).orElseThrow(() -> new EntityNotFoundException("Entity not found!")
);
}
public ResponseEntity<CommonResult> create(MenuRequestDto requestDto) {
// request가 들어오지 않은 경우
if(requestDto == null)
throw new RuntimeException();
Menu requestMenu = requestDtoToEntity(requestDto);
Menu savedMenu = menuRepository.save(requestMenu);
return ResponseEntity.ok(ResultProvider.getSuccessResult());
}
// entity -> responseDto
private MenuResponseDto entityToResponseDto(Menu menu) {
return MenuResponseDto.builder()
.id(menu.getId())
.name(menu.getName())
.shopName(menu.getShop().getName())
.build();
}
// requestDto -> entity
// 무조건 service logic 내부에서만 사용할 메소드이기 때문에 여기서 예외처리를 해주면 되는 것임.
private Menu requestDtoToEntity(MenuRequestDto requestDto) {
Optional<Shop> foundShop = shopRepository.findById(requestDto.getShopId());
return foundShop.map(shop -> Menu.builder()
.name(requestDto.getName())
.shop(shop)
.build()
).orElseThrow(() -> new EntityNotFoundException("Entity not found!")
);
}
}
まず、依存性注入に必要なリポジトリを受け入れます.サービス層でcreateとreadを作成するだけです.
readでは、パラメータpathVariableのみでidを受信する.戻り値として、DTOをSingleResultに送信します.
まず、idに基づいてmenuを検索します.findByIdはオプションフィールドなのでnullの場合、例外を投げ出しながらトランザクションをロールバックします.
menuの結果がnullでない場合、エンティティとして存在するため、entityをdtoに変換してSingleResultにマウントして返すことができます.
createの場合、パラメータを使用してrequestDtoを受信します.requestDtoを転送できない場合は、例外を放出します.
要求Dtoがnullでない場合、要求Dtoが無効な場合に例外を起こすべきかどうかという問題が発生する可能性があります.これはサービス層ではなく、DTOで検証されます.Service layerは、ビジネスロジックに専念する必要があります.もちろん、面倒なので検証はしていません.
その後、すべてのケースでdtoをentityに変換して保存し、CommonResultに戻ります.
ShopServiceの場合、実装も上記と同様です.コードのみを提供し、Controllerに移動します.
@Service
@RequiredArgsConstructor
public class ShopService {
private final ShopRepository shopRepository;
// GET Method
public ResponseEntity<SingleResult<ShopResponseDto>> read(Long id) {
return shopRepository.findById(id).map(shop ->
ResponseEntity.ok(ResultProvider.getSingleResult(entityToResponseDto(shop)))
).orElseThrow(() -> new EntityNotFoundException("Entity not found!"));
}
// Post Method
public ResponseEntity<CommonResult> create(ShopRequestDto requestDto) {
Shop requestShop = ShopRequestDto.dtoToEntity(requestDto);
Shop savedShop = shopRepository.save(requestShop);
return ResponseEntity.ok(ResultProvider.getSuccessResult());
}
// 모든 가게 목록을 뽑아오는 메소드
public ResponseEntity<MultipleResult<ShopResponseDto>> searchAllShops() {
List<Shop> allShops = shopRepository.findAll();
if(allShops.size() == 0)
throw new RuntimeException("가게가 존재하지 않아요!");
List<ShopResponseDto> responseList = allShops.stream().map(shop -> entityToResponseDto(shop)
).collect(Collectors.toList()
);
return ResponseEntity.ok(ResultProvider.getMultipleResult(responseList)
);
}
private ShopResponseDto entityToResponseDto(Shop shop) {
return ShopResponseDto.builder()
.id(shop.getId())
.name(shop.getName())
.address(shop.getAddress())
.build();
}
}
コントローラを見てみましょう。簡単なのでコードだけ残します。
ShopController.java
@RequestMapping("/api/shop")
@RestController
@RequiredArgsConstructor
public class ShopController {
private final ShopService shopService;
@GetMapping("/{id}")
public ResponseEntity<SingleResult<ShopResponseDto>> read(@PathVariable Long id) {
return shopService.read(id);
}
// Post Method
@PostMapping("")
public ResponseEntity<CommonResult> create(@RequestBody ShopRequestDto requestDto) {
return shopService.create(requestDto);
}
@GetMapping("/all-list")
public ResponseEntity<MultipleResult<ShopResponseDto>> searchAllShops() {
return shopService.searchAllShops();
}
// For Health Check
@GetMapping("/check")
public String healthCheck() {
return "Health check!!";
}
}
MenuController.java@RestController
@RequestMapping("/api/menu")
@RequiredArgsConstructor
public class MenuController {
private final MenuService menuService;
@GetMapping("/{id}")
public ResponseEntity<SingleResult<MenuResponseDto>> read(@PathVariable Long id) {
return menuService.read(id);
}
@PostMapping("")
public ResponseEntity<CommonResult> create(@RequestBody MenuRequestDto requestDto) {
return menuService.create(requestDto);
}
}
ShopControllerには特殊な方法,healthCheck()方法がある.この方法は,今後の負荷バランスで健康診断を行うために保持する方法である.次の記事では、これまでに作成したSpringアプリケーションをec 2に導入する方法について説明します.
次の文章で会いましょう.ありがとうございます.
参考文献
なぜ
https://martinfowler.com/eaaCatalog/dataTransferObject.html
Reference
この問題について(DevOpsスプリングサーバのエンドツーエンドの導入:DTOの使用理由、コントローラ、サービス層の作成), 我々は、より多くの情報をここで見つけました https://velog.io/@18k7102dy/devops-mono-4テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol