DevOpsスプリングサーバのエンドツーエンドの導入:DTOの使用理由、コントローラ、サービス層の作成


本番前に言うこと


今回の記事では、ControllerとService layerを作成します.
簡単なコントローラとサービス層を作成するので、個別のテストコードは作成しません.コードの作成方法ではなく、Springサーバの導入方法に重点を置いています.

DTOが何なのか知っていて、それを使ってみました。


DTOを構成する前に、DTOを使用する理由を理解しておきましょう.
サーバ・アプリケーションを構成するときにエンティティに直接戻ることはできません.DTOを使用して返さなければなりません.しかし、DTOをなぜ使うのか分からないかもしれません.実は私はこの文章を書く前に原因を知らなかった.
DTOを使用する理由について、有名な書籍のクリーンアップコードの著者マーティン・ファラーはよく説明しています.

上の文章を翻訳すると、内容はこうです.
ダイレクトコール
  • Entityはコストがかかります.理由は次のとおりです.
  • call自体はコストが高い.そしてそのcallの数が増えるかもしれません.
  • 呼び出しを低減するために、絶対に不潔であるマルチパスパラメータがある.
  • 言語(
  • Javaなど)はメソッドごとに1つの戻り値しかないため、Entityを直接呼び出すメソッドでは論理を構成することが困難な場合があります.
  • シリアル化アルゴリズム(serialization algorithm)は、複数のリモートコールのために
  • 単一コールを必要とする場合がある.これはDTOに対して非常に有効である.
  • シリアル化アルゴリズムをドメイン層の外に配置することによって、アーキテクチャをクリーニングすることができる.
  • それ以外に、私の経験、および私が調べた資料に基づいて、いくつかの理由を添付すると、以下の原因もあります.
  • サーバ・アプリケーションのアーキテクチャ効率を最大限に向上させるには、ドメイン・レイヤを分離する必要があります.(通常、ドメイン駆動開発と呼ばれる)しかし、ドメイン層はシリアル化アルゴリズムを記憶すべきではないため、シリアル化アルゴリズムをDTOに置く必要があるのも一つの原因である.
  • の1つのEntityの場合、応答の形式は多様であり、応答の中には、いくつかの情報を隠す必要があるか、拡張する必要がある.したがって、1つのEntityに対して複数の応答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に導入する方法について説明します.
    次の文章で会いましょう.ありがとうございます.

    参考文献


    なぜ
  • DTOを使うのですか?
    https://martinfowler.com/eaaCatalog/dataTransferObject.html