Spring-Layered Architecture

3 분 소요

Controller에서 중복되는 부분을 처리하려면?

  • 별도의 객체로 분리합니다.
  • 별도의 메소드로 분리합니다.
  • 예를 들어 쇼핑몰에서 게시판에서도 회원 정보를 보여주고, 상품 목록 보기에서도 회원 정보를 보여줘야 한다면 회원 정보를 읽어오는 코드는 어떻게 해야 할까요?

컨트롤러와 서비스

  • 비지니스 메소드를 별도의 Service객체에서 구현하도록 하고 컨트롤러는 Service객체를 사용하도록 합니다.

서비스(Service)객체란?

  • 비지니스 로직(Business logic)을 수행하는 메소드를 가지고 있는 객체를 서비스 객체라고 합니다.
  • 보통 하나의 비지니스 로직은 하나의 트랜잭션으로 동작합니다.

트랜잭션(Transaction)이란?

  • 트랜잭션의 특징은 크게 4가지로 구분됩니다.
  1. 원자성 (Atomicity)
  2. 일관성 (Consistency)
  3. 독립성 (Isolation)
  4. 지속성 (Durability)

원자성 (Atomicity)

  • 예를 들어 “출금”이라는 기능의 흐름이 다음과 같다고 생각해봅시다.
  1. 잔액이 얼마인지 조회한다.
  2. 출금하려는 금액이 잔액보다 작은지 검사한다.
  3. 출금하려는 금액이 잔액보다 작다면 (잔액 - 출금액)으로 수정한다.
  4. 언제, 어디서 출금했는지 정보를 기록한다.
  5. 사용자에게 출금한다.
  • 위의 작업이 4번에서 오류가 발생했다면 어떻게 될까요?
  • 4번에서 오류가 발생했다면, 앞의 작업을 모두 원래대로 복원을 시켜야 합니다.
  • 이를 rollback이라고 합니다.
  • 5번까지 모두 성공했을 때만 정보를 모두 반영해야 합니다.
  • 이를 commit 한다고 합니다.
  • 이렇게 rollback 하거나 commit을 하게 되면 하나의 트랜잭션 처리가 완료됩니다.

일관성 (Consistency)

  • 일관성은 트랜잭션의 작업 처리 결과가 항상 일관성이 있어야 한다는 것입니다.
  • 트랜잭션이 진행되는 동안에 데이터가 변경되더라도 업데이트된 데이터로 트랜잭션이 진행되는 것이 아니라, 처음에 트랜잭션을 진행하기 위해 참조한 데이터로 진행됩니다.
  • 이렇게 함으로써 각 사용자는 일관성 있는 데이터를 볼 수 있는 것입니다.

독립성 (Isolation)

  • 독립성은 둘 이상의 트랜잭션이 동시에 병행 실행되고 있을 경우에 어느 하나의 트랜잭션이라도 다른 트랜잭션의 연산을 끼어들 수 없습니다.
  • 하나의 특정 트랜잭션이 완료될 때까지, 다른 트랜잭션이 특정 트랜잭션의 결과를 참조할 수 없습니다.

지속성 (Durability)

  • 지속성은 트랜잭션이 성공적으로 완료됬을 경우, 결과는 영구적으로 반영되어야 한다는 점입니다.

JDBC 프로그래밍에서 트랜잭션 처리 방법

  • DB에 연결된 후 Connection객체의 setAutoCommit메소드에 false를 파라미터로 지정합니다.
  • 입력, 수정, 삭제 SQL이 실행을 한 후 모두 성공했을 경우 Connection이 가지고 있는 commit()메소드를 호출합니다.

@EnableTransactionManagement

  • Spring Java Config파일에서 트랜잭션을 활성화 할 때 사용하는 애노테이션입니다.
  • Java Config를 사용하게 되면 PlatformTransactionManager 구현체를 모두 찾아서 그 중에 하나를 매핑해 사용합니다.
  • 특정 트랜잭션 메니저를 사용하고자 한다면 TransactionManagementConfigurer를 Java Config파일에서 구현하고 원하는 트랜잭션 메니저를 리턴하도록 합니다.
  • 아니면, 특정 트랜잭션 메니저 객체를 생성시 @Primary 애노테이션을 지정합니다.

서비스 객체에서 중복으로 호출되는 코드의 처리

  • 데이터 엑세스 메소드를 별도의 Repository(Dao) 객체에서 구현하도록 하고 Service는 Repository객체를 사용하도록 합니다.

레이어드 아키텍처

  • presentation layer을 앱으로 하고 싶으면 service랑 repository는 그대로 유지하고 화면 계층만 안드로이드 스튜디오로 구현하면 된다.
  • 재사용 측면이나 유지보수 측면으로 봤을 때 강점이 있

설정의 분리

  • Spring 설정 파일을 프리젠테이션 레이어쪽과 나머지를 분리할 수 있습니다.
  • web.xml 파일에서 프리젠테이션 레이어에 대한 스프링 설정은 DispathcerServlet이 읽도록 하고, 그 외의 설정은 ContextLoaderListener를 통해서 읽도록 합니다.
  • DispatcherServlet을 경우에 따라서 2개 이상 설정할 수 있는데 이 경우에는 각각의 DispathcerServlet의 ApplicationContext가 각각 독립적이기 때문에 각각의 설정 파일에서 생성한 빈을 서로 사용할 수 없습니다.
  • 위의 경우와 같이 동시에 필요한 빈은 ContextLoaderListener를 사용함으로써 공통으로 사용하게 할 수 있습니다.
  • ContextLoaderListener와 DispatcherServlet은 각각 ApplicationContext를 생성하는데, ContextLoaderListener가 생성하는 ApplicationContext가 root컨텍스트가 되고 DispatcherServlet이 생성한 인스턴스는 root컨텍스트를 부모로 하는 자식 컨텍스트가 됩니다.
  • 참고로, 자식 컨텍스트들은 root컨텍스트의 설정 빈을 사용할 수 있습니다.

  • 참고 -> https://tinkerbellbass.tistory.com/9

@RestController

  • Spring 4 에서 Rest API 또는 Web API를 개발하기 위해 등장한 애노테이션입니다.
  • 이전 버전의 @Controller와 @ResponseBody를 포함합니다.
  • RestController를 사용하려면 반드시 jackson라이브러리를 추가해서 json을 사용할 수 있어야한다.

MessageConvertor

  • 자바 객체와 HTTP 요청 / 응답 바디를 변환하는 역할
  • @ResponseBody, @RequestBody
  • @EnableWebMvc 로 인한 기본 설정
  • WebMvcConfigurationSupport 를 사용하여 Spring MVC 구현
  • Default MessageConvertor 를 제공
  • 링크 바로가기 의 addDefaultHttpMessageConverters메소드 항목 참조
  • 종류

JSON 응답하기

  • 컨트롤러의 메소드에서는 JSON으로 변환될 객체를 반환합니다.
  • jackson라이브러리를 추가할 경우 객체를 JSON으로 변환하는 메시지 컨버터가 사용되도록 @EnableWebMvc에서 기본으로 설정되어 있습니다.
  • jackson라이브러리를 추가하지 않으면 JSON메시지로 변환할 수 없어 500오류가 발생합니다.
  • 사용자가 임의의 메시지 컨버터(MessageConverter)를 사용하도록 하려면 WebMvcConfigurerAdapter의 configureMessageConverters메소드를 오버라이딩 하도록 합니다.
      package kr.or.connect.guestbook.controller;
        
      import java.util.ArrayList;
      import java.util.Collections;
      import java.util.HashMap;
      import java.util.List;
      import java.util.Map;
        
      import javax.servlet.http.HttpServletRequest;
        
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.DeleteMapping;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestBody;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RequestParam;
      import org.springframework.web.bind.annotation.RestController;
        
      import kr.or.connect.guestbook.dto.Guestbook;
      import kr.or.connect.guestbook.service.GuestbookService;
        
      @RestController
      @RequestMapping(path="/guestbooks")
      public class GuestbookApiController {
              @Autowired
              GuestbookService guestbookService;
                
              @GetMapping
              public Map<String, Object> list(@RequestParam(name="start", required=false, defaultValue="0") int start) {
                        
                      List<Guestbook> list = guestbookService.getGuestbooks(start);
                        
                      int count = guestbookService.getCount();
                      int pageCount = count / GuestbookService.LIMIT;
                      if(count % GuestbookService.LIMIT > 0)
                              pageCount++;
                        
                      List<Integer> pageStartList = new ArrayList<>();
                      for(int i = 0; i < pageCount; i++) {
                              pageStartList.add(i * GuestbookService.LIMIT);
                      }
                        
                      Map<String, Object> map = new HashMap<>();
                      map.put("list", list);
                      map.put("count", count);
                      map.put("pageStartList", pageStartList);
                        
                      return map;
              }
                
              @PostMapping
              public Guestbook write(@RequestBody Guestbook guestbook,
                                                      HttpServletRequest request) {
                      String clientIp = request.getRemoteAddr();
                      // id가 입력된 guestbook이 반환된다.
                      Guestbook resultGuestbook = guestbookService.addGuestbook(guestbook, clientIp);
                      return resultGuestbook;
              }
                
              @DeleteMapping("/{id}")
              public Map<String, String> delete(@PathVariable(name="id") Long id,
                              HttpServletRequest request) {
                      String clientIp = request.getRemoteAddr();
                        
                      int deleteCount = guestbookService.deleteGuestbook(id, clientIp);
                      return Collections.singletonMap("success", deleteCount > 0 ? "true" : "false");
              }
      }
    

카테고리:

업데이트:

댓글남기기