[EC-Spring] 2주차-Controller, Repository, Service

umtuk·2022년 4월 2일
0

EC-Spring

목록 보기
2/6

Controller

웹 요청과 응답을 처리하는 Component로 보통 핸들러 메서드 @RequestMapping과 결합해 사용
선택적으로 Model 데이터를 채워서 응답
유저에 반환되는 데이터 포멧을 생성하기 위해 해당 응답의 웹 요청을 View에 전달
스프링 MVC에는 JSP, PDF, 엑셀 워크시트, XML, JSON Atom, RSS 피드, JasperReports 및 각종 서드파티 뷰 구현체 등 여러 가지 표현 기술별로 다양한 View가 준비
DispatcherServlet은 핸들러가 전달한 논리적인 뷰 이름을 실제로 렌더링할 뷰 구현체로 해석

@Controller를 적용한 클래스에 요청이 들어요면 스프링은 적합한 핸들러 메서드를 탐색
컨트롤러는 요청을 처리할 메서드를 하나 이상 매핑, 해당 메서드에 RequestMapping을 적용해 핸들러 메서드로 임명

핸들러 메서드의 시그니처는 정해진 규격이 없음
메서드명을 임의로 결정, 인수도 다양하게 정의, 애플리케이션 로직에 따라 모든 값 반환 가능

가능한 인수형

  • HttpServletRequest 또는 HttpRequestResponse
  • 임의형(arbitrary type) 요청 매개변수(@RequestParam 적용)
  • 임의형 모델 속성(@ModelAttribute 적용)
  • 요청 내에 포함된 쿠키값(@CookieValue 적용)
  • 핸들러 메서드가 모델에 속성을 추가하기 위해 사용하는 Map 또는 ModelMap
  • 핸들러 메서드가 객체 바인딩/유효성을 검증한 결과를 가져올 때 필요한 Errors 또는 BindingResult
  • 핸들러 메서드가 세션 처리를 완료했음을 알릴 때 사용하는 SessionStatus

컨트롤러는 우선 적절한 핸들러 메서드를 선택하고 이 메서드에 요청 객체를 전달하여 처리 로직 실행
대개 컨트롤러는 백엔드 서비스에 요청 처리를 위임하는게 보통, 핸들러 메서드는 다양한 타입의 (HttpServletRequest, Map, Errors, SessionStatus 같은) 인숫값에 어떤 정보를 더하거나 삭제하여 스프링 MVC의 흐름을 이어가는 형태로 구성

핸들러 메서드는 요청 처리 후, 제어권을 뷰로 전달
제어권을 넘길 뷰는 핸들러 메서드의 반환값으로 지정, (user.jsp나 report.pdf 같은) 직접적인 뷰 구현체보다 (user나 report처럼) 파일 확장자가 없는 논리 뷰로 표현해 유연성 강화

핸들러 메서드는 논리 뷰 이름에 해당하는 String형 값을 반환하는 경우가 대부분
반환값을 void로 선언하면 핸들러 메서드나 컨트롤러명에 따라 기본적인 논리 뷰가 자동 결정

컨트롤러 클래스는 뷰를 받고 뷰 리졸버(뷰 해석기)를 이용해 논리 뷰 이름을 (user.jsp나 report.pdf 같은) 실제 뷰 구현체로 해석
ViewResolver 인터페이스를 구현한 뷰 리졸버는 웹 애플리케이션 컨텍스트에 빈으로 구성하며 논리 뷰 이름을 받아 (HTML, JSP, PDF 등의) 실제 뷰 구현체를 반환

스프링 MVC 구조

예제

https://github.com/umtuk/ec-spring/tree/master/testcontroller

build.gradle

plugins {
    id 'org.springframework.boot' version '2.6.6'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'org.ec'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

GreetingController.java

package org.ec.testcontroller.greeting.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.Date;

@Controller
@RequestMapping("/greeting")
public class GreetingController {

    @GetMapping
    public String today(Model model) {
        Date now = new Date();
        model.addAttribute("now", now);
        return "greeting";
    }
}

greeting.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    Today is <div th:text="${now}"></div>
</body>
</html>

PersonController.java

package org.ec.testcontroller.person.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/person")
public class PersonController {

    @GetMapping
    public ResponseEntity<String> hello() {
        return ResponseEntity.ok("greeting");
    }
}

Repository

Domain-Driven Design에서 정의된 객체 컬렉션을 에뮬레이트하기 위한 저장 및 검색을 캡슐화
@Repository : 데이터 액세스와 관련

Spring Data JPA는 다른 스프링 모듈처럼 개발자가 어떤 목적을 달성하고자 정말 중요한 비즈니스 로직에만 집중할 수 있게 하며 가장 빈번하게 사용되는 데이터 액세스 메서드는 기본 구현체를 제공

CrudRepository<T, ID> : CRUD 연산과 관련된 메서드를 가지는 기본 구현체

	long count()
    void delete(T entity)
    void deleteAll()
	void deleteAll(Iterable<? extends T> entities)
    void deleteAllById(Iterable<? extends ID> ids)
    void deleteById(ID id)
    boolean existsById(ID id)
    Iterable<T> findAll()
    Iterable<T> findAllById(Iterable<ID> ids)
    Iterable<T> findById(ID id)
    S save(S entity)
    Iterable<S> saveAll(Iterable<S> entities)

PagingAndSortingRepository<T, ID>

public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {

  Iterable<T> findAll(Sort sort);

  Page<T> findAll(Pageable pageable);
}

기본 구현체에서 선택적으로 메서드를 가져오는 방법

@NoRepositoryBean
interface MyBaseRepository<T, ID> extends Repository<T, ID> {

  Optional<T> findById(ID id);

  <S extends T> S save(S entity);
}

interface UserRepository extends MyBaseRepository<User, Long> {
  User findByEmailAddress(EmailAddress emailAddress);
}

JpaRepository<T,ID> : Repository를 JPA에 특화되게 확장

    void deleteAllByIdInBatch(Iterable<ID> ids)
    void deleteAllInBatch()
    void deleteAllInBatch(Iterable<T> entities)
    <S extends T> findAll(Example<S> example) 
    List<S>	findAll(Example<S> example) 
    <S extends T> findAll(Example<S> example, Sort sort) 
    List<S>	findAll(Example<S> example, Sort sort) 
    List<T>	findAll(Sort sort) 
    List<T>	findAllById(Iterable<ID> ids) 
    void flush()
    T getById(ID id)
    <S extends T> saveAll(Iterable<S> entities)
    List<S>	saveAll(Iterable<S> entities) 
    <S extends T> saveAllAndFlush(Iterable<S> entities)
    List<S>	saveAllAndFlush(Iterable<S> entities)
    <S extends T> saveAndFlush(S entity)
    S saveAndFlush(S entity)

Service

Domain-Driven Design에서는 캡슐화 상태가 없는 단일 인터페이스로 제공되는 작업으로 정의
Business Service Facade를 가리키는 객체
해당 어노테이션은 범용적인 stereotype이며 각 팀별로 의미를 좁혀 적절하게 사용이 가능

비즈니스 로직을 보유하고 있음을 나타내기 위해 @Service로 빈을 표시

스프링 MVC에서의 비즈니스 로직

전통적으로 사용되는 MVC에서의 비즈니스 로직

@Controller는 MVC 패턴의 C, @Service는 비즈니스 로직,
@Repository는 데이터 액세스 레이어를 담당
즉, @Service, @Repository는 MVC 패턴의 M, 여러 View들은 MVC의 V를 담당

@Controller 클래스는 @Service 클래스만을 액세스하고 @Service 클래스는 @Service@Repository 클래스만을 액세스

스프링 웹 애플리케이션의 가장 큰 결함

스프링 프레임워크는 단일 책임 원칙 및 관심사 분리와 같은 이점을 활용하기에는 좋지 않음

오랫동완 관습적으로 수행되어온 방식
1. 도메인 모델 객체는 데이터를 저장하는 데만 사용
1. 비즈니스 로직은 도메인 객체의 데이터를 관리하는 서비스 레이어에 존재
1. 서비스 레이어는 애플리케이션의 각 엔티티당 하나의 서비스 클래스가 존재

주요 문제점
1. 서비스 계층에서 애플리케이션의 비즈니스 로직을 탐색
- 비즈니스 로직이 서비스 계층 주위에 흩어져 있기 때문
- 유지 보수의 어려움
1. 서비스 계층은 각 도메인 모델 클래스 당 하나의 서비스 클래스가 존재
- 단일 책임 원칙을 위반 : 모든 클래스가 단일 책임을 가져야 하며 책임은 클래스에 의해 완전히 캡슐화

보완 방법
1. 애플리케이션의 비즈니스 로직을 서비스 레이어에서 도메인 모델 클래스로 옮기기
- 서비스 레이어는 애플리케이션 로직을 처리하고 도메인 모델 클래스는 비즈니스 로직을 처리
- 비즈니스 로직을 한 곳에서 탐색 가능, 비즈니스 규칙이 구현되는 방법을 확인해야 하는 경우 항상 찾아야 할 위치를 알고 있음
- 서비스 레이어의 소스 코드는 간결해지며 복사 붙여녛기 코드가 포함되지 않음
1. 엔티티 특정 서비스를 단일 목적으로만 제공하는 더 작은 서비스로 분할

DTO, VO

Data Transfer Object(DTO) : 레이어 간 데이터 교환을 위해 사용되는 객체
Value Object(VO) : 값 그 자체를 표현하는 객체

참고

스프링 5 레시피(4판)
스프링 인 액션
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/stereotype/Controller.html
https://catsbi.oopy.io/f52511f3-1455-4a01-b8b7-f10875895d5b
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/stereotype/Repository.html
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/
https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/stereotype/Service.html
https://www.baeldung.com/spring-component-repository-service
https://stackoverflow.com/questions/25355385/where-to-put-business-logic-in-spring-mvc-framework/25355411
https://www.javacodegeeks.com/2013/06/the-biggest-flaw-of-spring-web-applications.html

profile
https://github.com/umtuk

0개의 댓글