Study 2주차

이준우·2022년 11월 25일
0

Study

목록 보기
1/7

사전지식

Rest 아키텍쳐 스타일, 레이어드 아키텍쳐 패턴 등 우리가 백엔드에서 구현하는 아키텍처에 대해 공부할 것이다.

레이어드 아키텍쳐란 스프링 프로젝트 내부에서 어떻게 코드를 적절히 분리하고 관리할 것이냐에 대한 이야기이며, 코드베이스가 커질수록 그 효율이 증가한다.
코드베이스란 특정 소프트웨어 시스템, 응용 소프트웨어, 소프트웨어 구성 요소를 빌드하기 위해 사용되는 소스 코드의 모임을 말한다.

Rest 아키텍처는 클라이언트(브라우저)가 우리 서비스를 이용하려면 어떤 형식으로 요청을 보내고 응답을 받아야 하는지를 말한다.
클라이 언트는 몇개의 정해진 메소드로 서비스를 이용한다. 이렇게 REST 아키텍처 스타일을 설계하고 구현된 서비스를 RESTful 서비스라고 한다.

스프링은 위 두 아키텍처를 사용할 때 도움을 준다.

레이어드 아키텍처

애플리케이션을 구성하는 요소들을 수평으로 나눠 관리하는 것이다.
처음에는 하나의 클래스에서 하나의 메소드로 다음에는 하나의 클래스에서 메소드를 쪼개 작은 메소드로 만들어 깔끔하게 사용한다. 이렇게 사용하면 다른 클래스에서 작업이 필요한 곳 마다 이 메소드를 복사 붙여넣기를 해 사용해야하는 반복적인 활동을 해야한다.
이 메서드는 클래스로 따로 빼서 쓰는 게 더 낫다고 생각하게 되는데 이 작업을 국소적으로 레이어를 나눈다고 한다. 이렇게 레이어로 나눈다는 것은 메서드를 클래스 또는 인터페이스로 쪼개는 것이다. 이 레이어의 범위는 작게는 클래스를 여러 레이어로 나누는 것이고 크게는 다른 애플리케이션으로 레이어를 분리하는 것 까지 다양하다.

레이어 사이에는 계층이 있다. 그래서 레이어는 자기보다 한 단계 하위의 레이어만 사용한다. 물론 예외로 필요한 경우 같은 계층끼리 사용하기도 하고 레이어가 많은 경우 중간 레이어도 섞어 사용하는 경우도 있지만 기본적으로는 상위 레이어는 자신의 바로 하위 레이어를 사용하는 것이다.

모델, 엔티티, DTO


보통 자바로 된 비지니스 애플리케이션의 클래스는 두 가지 종류로 나눌 수 있다.
첫째는 일을 하는 클래스, 즉 기능을 수행하는 클래스,
두번째로는 데이터를 담는 클래스로 나눌 수 있다.
일을 하는 클래스는 컨트롤러, 서비스, 퍼시스턴스처럼 로직을 수행하는 클래스이다.
데이터를 담는 클래스는 말 그대로 데이터만 가지고 있는 클래스이다.
아무런 기능 없이 데이터베이스에 반환된 비즈니스 데이터를 담기 위한 클래스들을 기능에 따라 나눈 것이 엔티티, 모델 DTO(Data Transfer Object)가 있다.

모델과 엔티티

이 프로젝트에서는 모델과 엔티티를 한 클래스에 구현한다. 따라서 모델은 비즈니스 데이터를 담는 역활로가 데이터베이스의 테이블 스키마를 표현하는 두 역활을 한다.
큰 애플리케이션의 경우 모델과 엔티티를 따로 구현한다.

어노테이션

@Builder

오브젝트 생성을 위한 디자인 패턴중 하나.
롬복이 제공하는 @Builder 어노테이션을 사용하면 우리는 Builder 클래스를 따로 개발하지 않고도 Builde 패턴을 사용해 오브젝트를 생성할 수 있다.

생성자를 사용해 오브젝트를 생성하는것과 비슷하지만, 생성자와 달리 매개변수의 순서를 기억할 필요가 없다.

@NoArgsConstructor

매개변수가 없는 생성자를 구현해준다.

@AllArgsConstructor

클래스의 모든 멤버변수를 매개변수로 받는 생성자를 구현 해준다.

@Data

클래스 멤버 변수의 Gatter/Setter를 구현.

DTO(Data Transition Object)

서비스가 요청을 처리하고 클라이언트로 반환할떄, 모델 자체를 그대로 리턴하는 경우는 별로 없다. 보통은 데이터를 전달하기 위해 사용하는 오브젝트인 DTO로 변환해 리턴한다.

변환하는 이유는
첫째, 비즈니스 로직을 캡슐화하기 위함이다. 모델은 DB 테이블 구조와 매우 유사하다. 모델이 가지고 있는 필드들은 테이블의 스키마와 비슷할 확률이 높다. 대부분의 회사들은 외부인이 자사의 DB스키마를 아는것을 원치 않으므로, DTO처럼 다른 오브젝트로 변환해 사용하여 서비스 내부의 로직, DB 구조를 숨길 수 있다.

둘째, 클라이언트가 필요한 정보를 모델이 전부 포함하지 않는 경우가 많기 때문이다. 예를 들어 에러 메시지가 있다. 모델은 서비스 로직과 관련이 없기에 DTO에 메세지 필드를 선언하고 DTO에 포함하면 된다.

REST API

REST(Representational State Transfer)는 아키텍처 스타일이다.
아키텍처 스타일은 아키텍처 패턴과 다른데, 패턴은 어떤 반복되는 문제 상황을 해결하기 위한 도구, 아키텍처 스타일은 반복되는 아키텍처 디자인을 의미한다.
REST 아키텍처 스타일은 6가지 제약 조건으로 구성되는데, 이 가이드 라인을 따르는 API를 RESTful API 라고 한다.

  • 클라이언트-서버
  • 상태가 없는
  • 캐시 가능한 데이터
  • 일괄적인 인터페이스
  • 레이어 시스템
  • 코드 온-디맨드(선택)

클라이언트 - 서버

리소스를 관리하는 서버, 다수의 클라이언트가 리소스를 소비하기 위해 네트워크를 통해 서버에 접근하는 구조.
여기서 리소스란 REST API가 리턴할 수 있는 모든것을 의미.(HTML, JSON 등)

상태가 없음

클라이언트가 서버에 요청을 보낼 때, 이전 요청의 영향을 받지 않음을 의미.
따라서 상태를 유지하기 위해 클라이언트는 서버에 요청을 날릴 때마다 요청에 리소스를 받기 위한 모든 정보를 포함해야함. 리소스를 수정 후 수정한 상태를 유지해야하는 경우는 서버가 아닌 DB같은 퍼시스턴스(영속적 상태)에 상태를 저장.
HTTP는 기본적으로 상태가 없는 프로토콜이다.

캐시 가능한 데이터

서버에서 리소스를 리턴할 때 캐시 가능한지 아닌지를 명시할 수 있어야한다.
HTTP에서는 cache-control이라는 헤더에 리소스의 캐시 여부를 명시할 수 있다.

일관적인 인터페이스

시스템 또는 애플리케이션의 리소스에 접근하기 위한 인터페이스가 일관적이어햐 한다는 뜻. 예로 URI의 일관성, 리턴타입의 일관성, 리소스에 접근하는 방식, 요청의 형석, 응답의 형식이 애플리케이션 전반에 걸쳐 URI, 요청의 형태와 응답의 형태가 일관적이어야 한다는 것.

레이어 시스템

클라이언트가 서버에 요청을 날릴 때, 여러 개의 레이어로 된 서버를 거칠 수 있다. 이 사이의 레이어들은 요청과 응답에 어떤 영향을 미치지 않으며 클라이언트는 서버의 레이어 존재 유무를 알지 못한다.

코드-온-디맨드(Code-On-Demand)

선택사항, 클라이언트는 서버에 코드를 요청할 수 있고, 서버가 리턴한 코드를 실행할 수 있다.

REST는 HTTP와 다르다. REST는 HTTP를 이용해 구현하기 쉽고, 대부분 그렇게 구현하지만 엄밀히 말하면 REST는 아키텍처이고, HTTP는 REST 아키텍처를 구현할 떄 사용하면 쉬운 프로토콜이다.

컨트롤러 레이어 : 스프링 REST API 컨트롤러

HTTP는 메서드와 URI를 이용해 서버에 HTTP요청을 보낼 수있다.
서버는 이 요청을 받은 후 어떻게 처리 해야 할까?


http GET 메서드를 이용해 test라는 리소스를 요청한다는 뜻이다. 따라서 서버는 자신의 주소를 제외한 /{리소스} 부분을 이해하고, 또 이 요청이 어떤 HTTP 메서드를 이용했는지 알아야 한다. 그 후 해당 리소스의 HTTP 메서드에 연결된 메서드를 실행해야 한다.
스프링 부트 스타터 웹의 어노테이션을 이용하면 이 연결을 쉽게 할 수 있다.

TestController

@RestController 어노테이션을 이용해 이 컨트롤러가 RestController임을 명. 이 어노테이션을 이용하면 http 관련된 코드 및 요청/응답 매핑을 스프링이 알아서 해준다. 괄호안 URI경로를통해 mapping해준다.
@GetMapping 어노테이션을 이용해 이 메서드의 리소스와 HTTP 메서드를 지정한다.
클라이언트가 이 리소스에 대해 Get 메서드로 요청하면, @GetMapping에 연결된 컨트롤러가 실행된다. URI경로를 지정해 줄 수 있다. 단 @RestController에 이미 URI가 있을경우 그 URI경로 밑에 생성된다.
@PostMapping
@PutMapping
@DeleteMapping이 존재.

매개변수를 넘겨받는 방법

/test/{id}처럼 PathVariable 이나 /test?id=123 처럼 요청 매개변수를 받아야 하는경우.

@PathVariable

/{id}와 같이 URI와 같이 경로로 넘어오는 값을 변수로 받아올 수 있다.

/{id}는 경로로 들어오는 임의의 숫자 or 문자를 변수 id에 매핑하라는 뜻이다.
(required = false)는 이 매개변수가 꼭 필요한건 아니라는 뜻이다.

@RequestParam

?id={id}와 같이 요청 매개변수로 넘어오는 값을 변수로 받아올수 있다.

@RequestBody

보통 반환하고자하는 리소스가 복잡할 때 사용.
기본 자료형이 아닌 오브젝트처럼 복잡한 자료형을 통째로 요청에 보내고 싶은 경우 사용.

@RequestBody TestRequestBodyDTO testRequestBodyDTO 는 Request Body로 날아오는 JSON 을 TestRequestBody 오브젝트로 변환해 가져오라는 뜻이다.
다시말해 클라이언트는 요청 바디로 JSON 형태의 문자열을 넘겨준다. 이 JSON의 내부는 의미적으로 TestRequestBodyDTO와 같아야 한다.

@ResponseBody

@RestController를 이루는 두개의 어노테이션중 하나는 @Controller이고 다른 하나는 @ReponseBody이다. @Controller를 더 들여다 보면 @Component로 스프링이 이 클래스의 오브젝트를 알아서 생성하고 다른 오브젝트들과의 의존성을 연결한다는 뜻이다.
@ResponseBody는 이 클래스의 메서드가 리턴하는 것은 웹 서비스의 ResponseBody라는 뜻이다. 메서드가 리턴할 때 스프링은 리턴된 오브젝트를 JSON의 형태로 바꾸고 HttpResponse에 담아 반환한다는 뜻이다.

오브젝트를 저장하거나 네트워크를 통해 전달할 수있도록 변환하는 것을 Serialization이라 한다. 반대는 Deserialization이다.

ResponseEntity

HTTP응답의 바디뿐만 아니라 여러다른 매개변수들, 예를 들어 status나 header를 조작하고 싶을 때 사용한다.

서비스 레이어 : 비지니스 로직

서비스 레이어는 컨트롤러와 퍼시스턴스 사이에서 비즈니스로직을 수행하는 역활을 한다. 서비스 레이어는 HTTP와 긴밀히 연관된 컨트롤러에서 분리돼 있고, 또 DB와 긴밀히 연관된 퍼시스턴스와도 분리돼 있다. 따라서 서비스 레이어에서는 우리가 개발하고자 하는 로직에 집중할 수있다.

@Service 어노테이션은 스테레오타입 어노테이션이다. 내부에는 @Componenet 어노테이션을 가지고 있는데, @Component 어노테이션과 비교했을 때 특별한 기능 차이는 없다. 단지 이 클래스는 스프링 컴포넌트이며 기능적으로는 비즈니스 로직을 수행하는 서비스 레이어임을 알려주는 어노테이션이다.

@RestController와 @Service 모두 내부에 @Component 어노테이션을 가지고 있어서 둘다 모두 자바 빈이고 스프링이 관리한다.
스프링은 TodoController 오브젝트를 생성할 때, 내부에 선언된 TodoService에서 @Autowired 어노테이션이 붙어있다는 것을 확인한다. @Autowired은 알아서 빈을 찾아서 그 빈을 이 인스턴스 멤버변수에 연결하라는 뜻이다.
그러므르 TodoController를 초기화할 때 스프링은 알아서 TodoService를 초기화 또는 검색해 TodoController에 주입해준다. 우리가 아무것도 할 필요 없이 말이다.

퍼시스턴스 레이어 : 스프링 데이터 JPA

우리 애플리케이션 Todo 아이템을 DB에 저장해야한다. 저장 장소로 관계형 DB를 선택했다. 우리는 select, 테이블 생성, 테이블 엔트리 추가, 수정, 삭제 하는것을 자바 애플리케이션 내에서 사용해야한다.

위를 가능케 하기 위해서는 JDBC 드라이버가 있어야한다. JDBC 드라이버는 자바에서 DB에 연결할 수 있도록 도와주는 라이브러리이다. 쉽게 말하면 MySQL 클라이언트 같은 것이다.

JDBC 커넥션인 Connection을 이용해 DB에 연결하고 sqlSelectAllTodos에 작성된 SQL을 실행 후 ResulteSet을 Todo 오브젝트로 바꿔준다. 이 과정을 ORM(Object-Relation Mapping)이라고 한다.

본래는 이 작업을 엔티티마다 해줘야한다. 즉 엄청나게 많은 반복활동이 일어나는데 이것을 줄이기 위해 JPA나 스프링 데이터 JPA같은 도구들이 개발됐다.

JPA는 반복해서 DB 쿼리를 날리고 ResultSet을 파싱해야 하는 개발자들의 노고를 줄여준다. JPA는 스펙(Specification)이다. 스펙이라는 것은, JPA를 구현하기 위한 가이드라인이다. 스펙이 명시 한 대로 동작한다면 그 내부의 구현 세부 사항은 구현자의 마음이다. 이러한 구현자를 JPA Provider라고 한다. 그중 대표적인게 Hibernate이다.

스프링 데이터 JPA는 JPA를 더 사용하기 쉽게 도와주는 스프링의 프로젝트이다. 기술적인 말로 추상화했다고 한다. 사용하기 쉬운 인터페이스를 제공한다는 것이다. 이 프로젝트에서는 그런 인터페이 중 하나인 JpaRepository를 사용한다.

데이터베이스와 스프링 데이터JPA 설정

DB에 연결하기 위해서는 먼저 DB가 필요하다.
H2는 In-Memory DB로, 로컬 환경에서 메모리상에 DB를 구축해준다.
따로 DB 서버를 구축할 필요가 없어, 초기 개발 시 많이 사용한다.
build.gradle에 h2를 디펜던시로 설정하면, @SpringBootApplication 어노테이션의 일부로 스프링이 알아서 애플리케이션을 H2 DB에 연결해준다.
이때 In-Memory로 설정시 애플리케이션 실행시 테이블을 생성하고, 종료시 소멸된다.
또 스프링 데이터 JPA를 사용하기 위해서는 spring-boot-starter-jpa 라이브러리가 필요하다.

H2가 동작하는지, 스프링 데이터 JPA가 동작하는지는 애플리케이션 실행 시 출력되는 로그를 보면 확인할 수 있다.

JPA 생성 사실과, JPA Provider로 Hibernate ORM을 사용, H2 DB를 사용한다는 것을 알 수 있다.

TodoEntity

보통 DB 테이블마다 그에 상응하는 엔티티 클래스가 존재한다. 하나의 엔티티 인스턴스는 DB 테이블의 한 행에 해당한다.

엔티티 클래스는 클래스 그 자체가 테이블을 정의해야 한다. ORM이 엔티티를 보고 어떤 테이블의 어떤 필드에 매핑해야 하는지 알 수 있어야 한다는 뜻이다. 또 어떤 필드가 기본 키인지 외래 키인지 구분할 수 있어야한다. 이런 DB 테이블 스키마에 관한 정보는 Javax.persistence가 제공하는 JPA 관련 어노테이션을 이용해 정의한다.

자바 클래스를 엔티티로 정의할 때 주의해야 하는 점 몇 가지 있다.

  • 클래스에는 매개변수가 없는 생성자, NoArgsConstructor가 필요
  • Getter/Setter가 필요
  • 기본키를 지정

자바 클래스를 엔티티로 지정하고 싶으면 @Entity를 추가. 이름을 부여하고 싶으면 @Entity("name")처럼 매개변수를 넣어 줄 수 있다.

테이블 이름을 지정하기 위해서는 @Table(name = "NAME") 어노테이션을 추가한다. 이 엔티티는 DB의 NAME 테이블에 매핑된다는 뜻이다.
만약 @Table을 추가하지 않거나 추가해도 name을 명시하지 않는다면 @Entity의 이름을 테이블 이름으로 간주한다. 만약 @Entity에 이름을 지정하지 않은 경우 클래스의 이름을 테이블 이름으로 간주한다.

@Id는 기본키가 될 해당 필드 위에 작성한다. Id필드는 우리가 오브젝트를 DB에 저장할 때마다 생성할 수 있지만. @GeneratedValue 어노테이션을 이용해 자동으로 생성할 수도 있다. 매개변수인 generator로 어떻게 ID를 생성할지 지정할 수 있다. system-uuid는 @GenericGenerator에 정의된 generator의 이름이다. @GenericGenerator는 Hibernate이 제공하는 기본 Generator가 아닌 나만의 Generator를 사용하고 싶을 때 이용한다.
기본 Generator로 INCREMEBTAK, SEQUENCE, IDENTITY 등이 있다.문자열 형태의 UUID를 사용하기 위해서는 커스텀 generator를 만드는 것이있. UUID를 사용하기 위해 GenericGenerator의 매개변수 strategy로 "uuid"를 넘겼다. 이렇게 uuid를 사용하는 "system-uuid"라는 이름의 GenericGenerator가 만들었고, 이 Generator는 @GeneratedValue가 참조해 사용한다.

TodoRepository

JpaRepository는 인터페이스이다. 이 인터페이스를 사용하기 위해서는 새 인터페이스를 작성해 JpaRepository를 확장해야한다. 이때 JpaRepository<T,ID>이 Genetic Type을 받는 것을 주의한다.

  • T는 테이블에 매핑할 엔티티 클래스이다.
  • ID는 엔티티의 기본키 타입이다.

@Repository 어노테이션 또한 Component 어노테이션의 특별 케이스이다. 따라서 스프링이 관리한다.

기본 쿼리와 쿼리 작성 방법

JpaRepository는 기본적인 DB 오퍼레이션 인터페이스를 제공한다. save, findByID, findAll등이 기본적으로 제공되는 인터페이스에 해당한다.
구현은 스프링 데이터 JPA가 실행 시 알아서 해준다.

JpaRepository는 추상클래스나 인터페이스는 반드시 구현하는 클래스가 있어야한다는 법칙을 무시하는 것 같다. AOP(Aspect Oriented Programiming)

기본적인 쿼리가 아닌 쿼리일 경우,


findByUserId 메서드를 작성하면 스프링 데이터 JPA가 메서드 이름을 파싱해서 'select * from Todo where userId = '{userId}'와 같은 쿼리를 작성해 실시한다. 메서드의 이름은 쿼리, 매개변수는 쿼리의 Where문에 들어갈 값을 의미한다. 더 복잡한 쿼리는 @Query 어노테이션을 이용해 지정할 수 있다.

profile
잘 살고 싶은 사람

0개의 댓글