세상에 나쁜 코드는 없다

프론트엔드 개발 이해를 돕기 위한 백엔드 지식 본문

웹개발/백엔드

프론트엔드 개발 이해를 돕기 위한 백엔드 지식

Beomseok Seo 2022. 12. 30. 18:29

0.인트로

0.1 소개

이 글은 웹 프론트엔드 개발자에게 “내가 작성한 코드가 어떻게 서버의 코드와 상호작용하여 사용자에게 전달되는가”를 설명하려는 목적으로 작성되었습니다.

글은 웹 프론트엔드 개발 입문자와, 프론트엔드 지식을 어느정도 갖고 있으면서 백엔드에 입문하고자 하시는 분들을 대상으로 작성되었습니다. 입문자를 기준으로 작성하였으므로 아는 분야의 지식이 나온다면 넘어가면서 읽으셔도 무방합니다.

글의 내용이 스프링부트에 의존하고 있습니다. 그럼에도 불구하고 최대한 일반적인 내용을 담을 수 있도록 해보겠습니다.

잘못된 내용이 있을 수 있습니다.

추가로, 거의 동일한 주제로 올라온 유튜브 영상이 있으니 참고하시면 도움이 될 것 같습니다.

웹 프론트엔드 개발자가 알아야할 최소한의 백엔드 지식과 코드 (API)
https://youtu.be/uIWl19relcc

0.2 글에서 다룰 기반 기술

HTML, Java 의 기본적인 문법을 알고있다는 가정하에 글을 작성합니다.

0.3 목차

1. 프론트엔드와 백엔드의 재정의

프론트엔드와 백엔드는 용어의 사용처가 많아지면서 다양한 의미로 쓰이고 있습니다. 따라서 글을 작성하기 전에 그 의미를 재정의할 필요가 있다고 생각합니다.

프론트엔드와 백엔드는 하나의 거대한 소프트웨어를 프레젠테이션 계층데이터 액세스 계층으로 관심사를 분리한 각각의 계층을 의미합니다.

프론트엔드는 소프트웨어에서 모든 사용자에게 보여지는 부분을 총칭합니다. 비단 웹 프로그래밍 뿐만 아니라, 다양한 소프트웨어에서 사용자에게 조금 더 가까운 영역의 프로그램 코드 계층을 이야기할 때 프론트엔드라는 용어를 사용합니다.

사용자로부터 감춰져있으면서 사용자로부터 요구되는 비즈니스 로직을 처리하는 부분을 백엔드라고 합니다.

예를 들어 컴파일러 아키텍처에서는, 하이레벨 언어 소스코드를 분석하여 문법이 올바르게 작성되었는 확인하는 과정을 프론트엔드라고 지칭합니다. 이후 최적화를 거친 후 해당 코드를 바이너리로 변환하는 과정을 백엔드라고 지칭합니다.

운영체제의 경우에도 사용자에게 보여지는 GUI 들은 프론트엔드이며, GUI 사용으로 인해 실행되는 내부 로직들을 백엔드라고 얘기할 수 있겠습니다.

하지만 이 글에선 웹의 프론트엔드와 웹 백엔드에 초점을 맞춰서 이야기합니다.

웹 프론트엔드는 HTML, CSS, Javascript 등의 기술을 사용하여 사용자가 웹브라우저를 통해 보게되는 웹 페이지를 개발하는 분야, 코드 그리고 개발자를 의미합니다.

웹 백엔드는 주로 Java, Javascript(Node.js), Python 등의 기술을 사용하여 사용자가 요청을 처리하고 그 응답을 반환하는 로직을 개발하는 분야, 코드 그리고 개발자를 의미합니다.

이 글에서는 다음과 같이 용어를 정의하겠습니다.

🗣
용어 재정의
  • 프론트엔드 : 사용자에게 보여지는 웹 페이지를 개발하는 분야
  • 프론트엔드 코드 : HTML, CSS, Javascript 등 프론트엔드 개발자들이 작성하는 정적 파일
  • 프론트엔드 개발자 : 웹 페이지를 개발하는 개발자
  • 백엔드 : … 분야
  • 백엔드 코드 : … 파일
  • 백엔드 개발자 : … 개발자

2. 웹 전반 이해를 위한 키워드

2.1 Server

서버의 의미는 넓게 사용되고 있지만, 여기서는 웹어플리케이션 서버로 한정하여 설명합니다.

서버란 사용자로부터 요청을 받고 그에 대한 리소스를 반환할 수 있는 컴퓨터 시스템을 의미합니다. PC의 특정 포트를 외부인에게 접속할 수 있게 허용하고 웹어플리케이션을 실행시킨다면, 외부사용자는 서버 PC의 ip와 포트를 이용하여 웹어플리케이션에 요청을 보낼 수 있습니다.

  • 웹서버의 포트

    웹서버는 일반적으로 80번 포트를 이용합니다. 포트는 HTTP 요청을 보낼 때 http:// [host] : [port] / [directory] 와 같이 명시되며 , 포트를 명시하지 않을 경우 기본값인 80번 포트로 요청이 가게 됩니다. 이것이 우리가 웹에 접속할때 굳이 포트를 명시하지 않아도 접속이 됐던 이유입니다. HTTP에 대한 정보는 아래에서 더 설명합니다.

이후로 다루는 키워드인 WS와 WAS, HTTP, 그리고 API는 요청과 응답이 어떤식으로 진행되는지에 대한 추가적인 설명입니다.

2.2 WS 와 WAS

Web Server (이하 WS) 는 서버가 정적인 파일을 요청 받았을 때 해당 파일을 제공하는 역할을 합니다. 서버 PC에 저장되어있는 프론트엔드 코드들은 WS를 통해 사용자에게 전달될 수 있습니다.

위 사진의 경우 서버의 PC에 저장되어있는 프론트엔드 코드를 사용자가 가져온 경우로, WS 가 작동하였다고 볼 수 있습니다.

URL에 입력된 파일명은 서버 PC에서 미리 지정된 디렉터리에서 찾습니다. 이 디렉터리는 이후 스프링부트 프로젝트 디렉터리 구조에서 다룰 예정입니다.

Web Application Server (이하 WAS)는 서버가 동적인 페이지를 요청받았을 때 결과물을 생성하여 반환하는 역할을 합니다.

동적 페이지 요청 결과

위 페이지의 경우 원래의 HTML 파일은 아래와 같습니다.

itempage.html

이렇게 생긴 정적인 페이지를 WAS는 사용자로부터 요청받은 값을 처리하여 알맞은 데이터를 넣어준 새로운 HTML 파일을 사용자에게 전달합니다. 정적인 파일이 어떻게 DB의 데이터를 담은 후 사용자에게 전달되는지에 대해서는 템플릿 엔진에서 다룰 예정입니다.

2.3 HTTP

HTTP 는 웹 클라이언트와 웹 서버간에 요청과 응답을 할 때 사용하는 통신 규약입니다. 우리가 웹브라우저를 사용할 때 url 창에 입력하는 주소로 HTTP 요청을 보내고, 웹브라우저는 그에 대한 응답을 parsing 하여 사용자에게 보여줍니다.

2.3.1 구조 및 작동방식

HTTP 는 HTTP Header 영역과 HTTP Body 영역으로 나뉩니다.

각 영역에는 항목들이 매우 많습니다. 모든 것을 알기 어렵고 알 필요성도 크지 않습니다. 또한 자세한 내용들은 구글에 매우 많으므로 가장 핵심적인 부분만 다뤄보려고 합니다.

먼저 클라이언트가 서버로 HTTP 요청을 보낼 때, HTTP Header에는 에 주소 URL요청포트 그리고 HTTP 메서드가 담기게 됩니다. 만약 클라이언트가 서버에 전송해야하는 데이터가 있는 경우 (예를 들어 회원가입 요청 시 회원가입에 필요한 정보들) HTTP Body에 정보를 담아 요청이 전송됩니다.

이 HTTP 요청을 통해 주소 URL이 가리키는 서버 PC로 요청이 전송되고, 서버에서 URI와 HTTP 메서드에 해당하는 로직이 실행된 후, 서버는 HTTP Response를 생성하여 반환값을 담고 사용자에게 전달합니다. 여기서 어떻게 서버가 알맞은 요청을 찾아서 처리하는지에 대해서는 Controller에서 더 자세히 다룰 예정입니다.

HTTP Response에는 서버가 응답을 잘 처리했는지, 아니면 처리하지 못했는지, 처리하지 못했다면 어떤 이유로 처리하지 못했는지에 대한 정보가 담긴 상태코드가 HTTP Header에 담겨 있습니다. HTTP Body에는 서버에서 반환하는 값이 담겨있습니다. 서버에서 반환하는 값은 JSON 형태의 데이터일 수도, CSS 나 Javascript와 같은 정적인 파일일 수도,아니면 HTML 형태의 페이지일 수도 있습니다. (사실 html 도 정적인 파일과 결이 같지만 이해를 위해 나눠봤습니다.)

2.3.2 HTTP 메서드

HTTP는 여러가지 메서드를 갖습니다. HTTP 메서드는 요청헤더에 담겨서 전송되며, 이 메서드에 따라서 서버는 같은 URL 이라도 각기 다른 로직을 수행하게 됩니다.

  • GET : 서버에서 리소스를 받아오기 위해
  • POST : 서버에 리소스를 생성하기 위해
  • PUT, PATCH : 서버의 리소스를 변경하기 위해
  • DELETE : 서버의 리소스를 삭제하기 위해

일반적으로 웹브라우저의 주소창에 주소를 입력할 때 해당 주소로 GET 요청을 하게 됩니다. 회원가입 버튼을 누를때는, form 의 method=”post” 라는 속성이 action 속성의 값 주소로 input의 데이터들을 body에 담은 HTTP POST 요청을 만들어 전송하게 됩니다.

  • 로그인은 왜 post 메서드를 사용하나요
  • post와 patch의 차이점은 뭔가요

2.3.3 특징

HTTP는 일회성 통신과 stateless라는 특징을 갖고 있습니다. 이러한 이유로 HTTP 를 통해서는 한번에 한가지의 리소스밖에 요청하지 못합니다. 만약 여러개의 리소스를 받아야하는 웹페이지를 요청한다면, 먼저 웹 뼈대 HTML이 불려오고, HTML에 link되어있는 정적파일이나 이미지들을 HTTP 요청으로 각각 요청하여 결과를 받아와 HTML이 렌더링이 되는 과정을 거치게 됩니다. 주로 css 를 연결하는 link 태그, javascript 태그, img 태그의 src 속성의 값을 URL 주소로 하여 요청합니다.

특정 페이지를 요청한다면, 위와 같이 해당 페이지에 필요한 리소스들을 받아오기 위해 순차적으로 HTTP 요청을 보내 정보를 받아온 뒤 HTML에 값이 들어가게 됩니다.

2.4 API

API 란 어떤 어떤 HTTP 요청이 어떤 응답을 반환할지에 대한 규약입니다. 이 규약은 프론트엔드 개발자 - 백엔드 개발자 간의 인터페이스로 작동합니다.

API 는 다음과 같은 속성을 갖는 약속의 목록입니다.

  1. 어떤 주소값을 갖는가
    1. ex. www.peteworld.shop/api/users
    1. ex. www.peteworld.shop/api/item/{itemId}
  1. 어떤 HTTP 메서드를 사용해야 하는가
    1. get? post? patch?
  1. 조건값을 어떻게 전달해야하는가
    1. 조건값 전달 방식 : Query String vs Http Body vs Path Variable
      1. Query String은 요청 URL 주소에 조건 정보를 담는 방식입니다.
        1. 주로 GET 방식에서 사용합니다.
        1. URL 뒤에 ? 를 붙인 후 key=value의 정보를 담고, 정보들은 & 로 구분합니다.
        1. ex. www.peteworld.shop/api/items?minPrice=100000&category=1 (최소금액이 100000만원이고 카테고리가 1인 아이템의 정보를 주세요)
      1. HTTP Body 는 말 그대로 조건 정보를 Body에 담는 방식입니다.
        1. 주로 POST 메서드에서 사용합니다.
        1. HTTP Body에 username=pete&phoneNumber=01042645540 과 같은 형식으로 담겨져 전송됩니다.
      1. Path Variable은 Query String과 동일하게 URL 주소에 조건 정보를 담아야 하지만 ?& 같은 키워드를 사용하지 않고 주소에 바로 정보를 담습니다.
        1. ex. www.peteworld.shop/api/items/4 (Id 가 4인 아이템 정보를 주세요)
    1. 조건값의 형식 : JSON? XML? plain text?
      1. Body 영역에 데이터를 넣을 때 순수한 텍스트로 넣어주면 되는지, JSON 형식으로 변환하여 넣어줘야하는지 등에 대한 규약이 작성되어야 합니다.
    1. 조건값이 필수인지 아닌지
      1. 주로 required 라는 키워드를 사용합니다.
  1. 위 조건을 만족하여 요청을 보낸다면, 어떤 응답을 받게 되는가
    1. HTML? JSON? BINARY?

예시

  • 예시 1. id에 해당하는 상품 정보 가져오기

    위 사진은 api 문서의 예시입니다. 위 API 는 다음과 같은 정보를 담고 있습니다.

    1. 어떤 주소로 요청을 보내야하는가 : www.peteworld.shop/api/items/{itemId}
    1. 어떤 메서드를 사용해야 하는가 : GET
    1. 조건값에 대한 정보
      1. Path Variable이다.
      1. required 필수로 들어가야한다.
      1. integer 값을 {itemId} path 에 넣어줘야한다.
    1. 올바른 요청시 결과에 대한 정보

    Item에 대한 정보를 JSON 형태로 반환할 것임을 알 수 있습니다.

  • 예시 2. 회원 아이디 찾기

    아이디 패스워드 찾기 페이지에서 사용하는 API 입니다.

    위 API 는 다음과 같은 정보를 담고 있습니다.

    1. 어떤 주소로 요청을 보내야하는가
      1. www.peteworld.shop/api/users/findID
    1. 어떤 메서드를 사용해야하는가
      1. GET
    1. 조건값을 어떻게 전달해야하는가
      1. query → Query Parameter 형식으로 전달해야한다.

    위 조건들을 만족시키며 요청을 하면 다음과 같은 결과를 받을 수 있습니다.

    String 값이 나온다는 것을 확인하여 프로그래밍에 활용할 수 있습니다.

  • 예시 3. 회원가입
    1. 어떤 주소로 요청을 보내야하는가
      1. www.peteworld.shop/users
    1. 어떤 메서드를 사용해야하는가
      1. POST
      1. POST 메서드를 보내는 일반적인 방식은 Form입니다.
    1. 조건값을 어떻게 전달해야하는가
      1. address, addressCode, …userId, userName 의 정보를 담아야 한다.
      1. POST 이므로 HTTP Body 에 담기게 된다.
      1. 필수값이 각각 명시되어있다.

API 문서를 통해 실제 서버의 로직이 구현이 되어있는지와 별개로 프론트엔드 개발자들은 API를 참고하여 프론트엔드 코드를 작성할 수 있습니다. 백엔드 개발자 역시 프론트엔드 코드 구현에 의존하지 않고 API 라는 중간자 로직만을 구현하여 관심사의 분리를 확실하게 만들어 낼 수 있습니다.

  • What’s REST API

3. 프론트엔드 개발의 이해를 위한 백엔드 키워드

3.1 스프링부트 프로젝트 디렉터리 구조

스프링부트 프로젝트의 디렉터리 구조는 다음과 같습니다.

Project 프로젝트 전체 파일을 갖고 있으며, github 에 올라가게되는 root 폴더입니다.
ETC..프로젝트의 빌드 정보 등 프로그램 소스파일과 상관 없는 내용들을 담고 있는 폴더들입니다.
src프로그램을 돌릴 때 필요한 소스파일이 들어있는 폴더입니다.
test테스트 코드가 들어있는 폴더입니다.
main실질적인 프로그램의 코드가 담겨 있습니다.
java Java 코드가 있습니다.
resources프로그램에 필요한 자원들(html, css, img, 템플릿 엔진 등)이 들어있습니다.
statichtml css javascript 등 정적인 파일이 들어있습니다.
template템플릿 엔진 코드들이 들어있습니다.

프론트엔드 개발자가 작성한 코드는 static 폴더 하위에 존재하게 됩니다. 스프링부트는 기본적으로 static 폴더를 루트 폴더로 하여 정적자원을 클라이언트에게 제공합니다.

예를 들어 …/static/home/home.html 이라는 파일은 http://www.peteworld.shop/home/home.html 로 요청을 보냄으로써 받을 수 있습니다. 프로젝트 내 이미지, css나 javascript의 정보를 받을 때도 위와 같은 방식을 사용합니다.

3.2 MVC 패턴

MVC 패턴은 소프트웨어를 Model, View, Controller 라는 계층으로 나누는 디자인 패턴입니다. 소프트웨어 전반에 적용될 수 있는 개념이고, 특히 웹 분야에서 적극적으로 채택하여 사용하고 있습니다. 현재 대다수의 백엔드 언어는 MVC 패턴을 기반으로 제공되는 프레임워크가 존재합니다. Java 의 Spring 과 Python 의 Django 가 그 대표적인 예 입니다. 백엔드의 흐름을 이해하기 위해서는 MVC 패턴에 대한 이해가 필요합니다. 이 아래에서는 각각의 계층이 어떤 일을 하는지에 대해 일반적인 이야기를 한 후, 스프링에서 이 개념이 어떻게 구현됐는지 3.4 스프링부트에서의 MVC 패턴 구현에서 코드로 살펴보겠습니다.

3.2.1 Model

데이터비즈니스 로직을 담당하는 계층이 Model입니다. 소프트웨어 내의 변화하는 주요 데이터를 처리하고 관리하며 다른 계층에서 요청이 있다면 데이터를 가공하여 반환합니다. Model은 데이터를 처리하는 역할과 책임을 갖기 때문에, 다른 계층에서 어떤 일이 일어나는지 알 필요가 없습니다. 그저 데이터 요청이 온다면 데이터를 반환하고, 데이터를 변경하라는 요청이 오면 로직에 맞게 데이터를 변경하여 보관할 뿐입니다.

3.2.2 View

View 는 사용자에게 보여지는 부분을 담당하는 계층입니다. 이런 의미에서 프론트엔드와 가장 연관이 깊은 계층이라고 할 수도 있겠습니다. View는 소프트웨어 내에서 오직 사용자에게 보여줄 페이지를 생성하는데에만 집중합니다. 뷰는 내부 데이터 모델이 유효한 값인지, 이 요청에 이 페이지를 반환하는것이 맞는 로직인지 신경쓰지 않습니다. 오직 자신의 페이지에 전달받은 값들을 잘 배치하여 사용자에게 반환하는 역할만을 갖습니다.

3.2.3 Controller

컨트롤러는 프로그램의 주요 흐름을 담당하는 계층입니다. Controller 는 사용자에게 요청이 들어오면, 요청을 분석하여 어떤 비즈니스 로직이 실행되어야 하고 어떤 뷰를 반환해야하는지 선택합니다. 컨트롤러는 일종의 중간자로서, Model과 View 사이에서 각각 필요한 요청을 하고 반환값을 전달해주는 역할을 합니다.

3.3 템플릿 엔진

스프링부트 MVC 패턴 구현 코드를 보기 전에, 템플릿 엔진에 대한 이해가 필요할 것 같아 중간에 끼워넣었습니다. 템플릿 엔진이란 프론트엔드 개발자가 작성한 코드에 동적으로 값을 넣어 그 내용을 바꿔주기 위한 도구입니다. 템플릿 엔진은 HTML 과 같은 정적 파일의 코드 중 일부를 변경하여 사용자에게 전달하는 역할을 합니다. 이 결과 프론트엔드 개발자가 작성한 하나의 HTML파일로 사용자의 요청에 따라 다른 정보를 담은 페이지를 반환할 수 있게 됩니다.

템플릿 엔진에도 종류가 여러가지가 있습니다. Mustache, Thymeleaf 등이 주로 사용되고 있습니다. 템플릿 엔진을 자세히 설명하기 위해 Thymeleaf 가 어떻게 구성되어있는지 코드를 보겠습니다.

<!-- search.html 의 일부 -->
...
<div class="item">
	<a href="/item/1">
		<img src="img/item/02d783f2-93c6-4a64-ae4f-fa055b24ebe0.jpg">
			<div class="des">
				<div class="itemWrap">
					<h4>#쇼핑몰 이름입니다#</h4>
					<h4>#상품 이름입니다#</h4>
				</div>
			<h4>#상품 가격입니다#</h4>
		</div>
	</a>
</div>
...

순수한 HTML 코드입니다. 아이템 하나의 정보를 담고 있는 엘리먼트입니다. 이 코드를 Thymeleaf 를 사용하여 아래와 같이 작성할 수 있습니다.

<!-- thymeleaf search.html -->
...
<html lang="en" xmlns:th="http://thymeleaf.org">
...
<div class="item" th:each="item : ${items}">
	<a th:href="@{/item/}+${item.id}">
		<img th:src="@{/img/item/}+${item.img[0]}+'.jpg'">
		<div class="des">
			<div class="itemWrap">
				<h4 th:text="${item.seller.name}">#쇼핑몰 이름입니다#</h4>
				<h4 th:text="${item.name}">#상품 이름입니다#</h4>
			</div>
		<h4 th:text="${item.price}+'원'">#상품 가격입니다#</h4>
	</div>
	</a>
</div>
...

Thymeleaf 는 html 에 xmlns:th="http://thymeleaf.org" 라는 속성을 주어 만들 수 있습니다. 이 경우 이 html은 더 이상 정적파일이 아닌 Thymeleaf 에 제어되는 동적 html 파일의 템플릿이 됩니다. 타임리프는 th 라는 키워드를 통해 동적인 코드를 구성합니다. 아래 목록은 위 코드를 이해하기 위해 필요한 키워드입니다.

  1. 키워드 ${..}

${변수명} 이라는 키워드를 통해 Thymeleaf는 전달받은 변수를 사용할 수 있습니다.

  1. 키워드 @{…}

@{PATH} 를 통해 Thymeleaf는 링크를 표현할 수 있습니다.

  1. th:each

each = “ 변수명 : ${리스트변수}” 를 통해 리스트를 iterate 하면서 HTML 엘리먼트를 생성합니다. 몇몇 언어에서 볼 수 있는 Enhanced For문과 비슷한 문법이라고 생각하시면 됩니다.

  1. th:text

th:text 를 통해 속성값을 tag 내부의 값으로 배치할 수 있습니다.

위 Thymeleaf 코드는 Java 코드에 의해서 items 라는 변수명을 가진 아이템 리스트를 전달받습니다. 이후 Thymeleaf는 리스트의 각 상품의 요소 값들을 iteration을 돌며 th:each 가 감싸고 있는 내부 태그에 배치합니다. 엘리먼트는 리스트의 크기 만큼 생성될 것입니다. 이 결과로 완성된 동적 HTML 파일이 최종적으로 사용자에게 전달됩니다. 프론트엔드 코드는 위와 같은 과정을 거쳐 사용자에게 전달되게 됩니다.

이 과정에서 items 라는 변수가 Model이고, Model을 전달해주는 Java 코드가 Controller 입니다. Thymeleaf 사용과 같이 최종적으로 반환할 HTML을 생성하는 부분이 View 입니다. 이로써 MVC 각 요소를 조금더 구체화한 방식으로 알게 되었습니다. 아래에서는 MVC 패턴의 코드를 직접 살펴보겠습니다.

3.4 스프링부트에서의 MVC 패턴 구현

스프링부트의 코드를 직접 보며 MVC 패턴이 백엔드에서 어떻게 구현이 되는지 알아보겠습니다.

3.4.1 Model

모델은 모든 데이터를 저장하는 객체와 비즈니스 로직이 있는 객체를 포함합니다

Entity

@Entity
public class Item {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "seller_id")
    private Seller seller;

    private int price;
	......

}

Entity 는 데이터베이스에서 레코드 별로 불러오는 정보를 담는 객체입니다. 데이터베이스 테이블의 constraints 들이 어노테이션을 통해 관리되는것을 확인할 수 있습니다. 사용자에 의해서 생성되는 수많은 데이터들이 위와 같은 객체 형태로 서버에서 관리되는구나 정도를 이해하시면 좋습니다.

Service

public interface ItemService {

    /*
        List<Item> getHotItems : 누적 판매량이 높은 순서대로 8개의 상품을 반환한다.
    */

    List<ItemDto> getHot8Items(Long categoryId);

    /*
     *  Item get : 아이디에 해당하는 상품을 반환한다.
     * */

    ItemDto get(Long itemId) throws NoSuchElementException;

    Item update(ItemForm form, Long itemId);

    /*
    * getItemsByConditions : 인자로 넘어온 조건에 따라 상품 리스트를 반환한다.
    * page = 페이지
    * List<Long> categoryIds : 카테고리가 여러개 선택된 경우 전처리를 통하여 리스트 형식으로 카테고리 아이디를 받는다.
    * int minPrice, maxPrice : 가격의 상하한선을 설정한다. maxPrice가 없는 경우 40000원 이상의 모든 상품을 반환한다.
    * List<Long> sellerIds   : 브랜드가 여러개 선택된 경우 전처리를 통하여 리스트 형식으로 seller 아이디를 받는다.
    * String sortedBy        : String 형식으로 정렬 기준을 받는다 . "인기순" "낮은 가격순" "높은 가격순" "리뷰순"
    *
    * 모든 인자는 null이 들어올 수 있으며, null 인 경우 해당 조건을 제외 한 나머지로 필터링한 결과를 반환한다.
    * */
    Page<ItemDto> getItemsByConditions(ItemRequest request);
}

서비스라고 불리는 계층은 주로 DB에서 불러온 값들에 대해 비즈니스 로직을 처리하고 Controller 에게 넘겨주는 역할을 합니다. 위 코드는 ItemService 의 명세서인데, ItemService 는 누적판매량이 높은 순서로 8개의 상품을 반환하는 기능, Id별 상품 1개를 반환하는 기능, 조건에 의해 필터링한 상품의 리스트를 반환하는 기능 등을 제공합니다.

3.4.2 Controller

대망의 컨트롤러입니다. 사실상 이 부분을 이해하는 것이 전체 백엔드 흐름을 잡는데에 핵심적이 부분입니다. 몇부분의 코드를 분석하면서 Controller 에 대한 이해도를 높여보겠습니다.

@GetMapping("/home")
public String homeHandler(Model model,
             @RequestParam(required = false,value="search") String searchWord) {

    if(searchWord == null) {
        List<ItemDto> hotItems = itemService.getHot4Items();
        List<ItemDto> recommendedItems = recommendedItemService.get4RecommendedItems(0L);
        model.addAttribute("hotItems", hotItems);
        model.addAttribute("recommendedItems",recommendedItems);

        return "home/home";
    } else {
        ItemRequest itemRequest = new ItemRequest();
        itemRequest.setName(searchWord);
        Page<ItemDto> items = itemService.getItemsByConditions(itemRequest);
        List<ItemDto> content = items.getContent();
        model.addAttribute("items", content);
        model.addAttribute("searchWord", searchWord);
        return "home/search";
    }
}

위 코드는 HomeController.java 에 있는 메서드입니다.

가장 윗줄에 있는 @GetMapping(”/home”) 은 “ /home 주소로 Get 요청이 왔을 때 이 메서드를 실행하겠다” 를 의미합니다. 이 메서드를 실행시키려면 브라우저 주소란에 “www.peteworld.shop/home” 을 입력하면 될 것입니다.

메서드 homeHandler 는 2개의 인자를 받고 있습니다.

첫번째 인자는 Model 로, 이 객체에 addAttribute(”someKey”, someValue) 형태로 코드를 작성한다면 someKey 라는 변수명을 가진 someValue 객체를 View에게 넘겨주게 됩니다. 아래에서 5번째 줄의 model.addAttribute(”items”,content); 는 View에게 items 라는 이름을 가진 content 객체를 전달합니다. content 객체는 getItemByConditions 라는 ItemService의 메서드로부터 받아온 상품 리스트입니다. 위 Thymeleaf예에서 봤던 th:each=”item : ${items}” 부분이 이 코드와 정확히 매칭되는 코드입니다. 컨트롤러는 이러한 방식으로 View에게 Model을 넘겨줍니다.

두번째 인자는 String searchWord 인데, 앞에 붙은 뭔가가 많습니다. @RequestParam 은 사용자에게 Query Parameter 형식으로 조건값을 받겠다는 의미입니다. Query Parameter 가 뭔지 헷갈리신다면 2.4 API 를 다시 보고 오신다면 도움이 될 것입니다. @RequestParam 의 괄호에 들어있는 속성은 조건의 추가적인 특성입니다. required=false 를 통해 이 조건값이 API에 있어서 필수적인 값이 아니라는것을 설정해주고 있고, value=”search” 를 통해서 요청시 변수 이름을 search로 해야한다는 것을 정해주었습니다. 사용자가 peteworld.shop/home?search=고무신 으로 Get 요청을 보낸다면, String searchWord 에는 “고무신”이라는 값이 담긴채로 비즈니스 로직이 실행될 것입니다.

위 코드에서는 비즈니스 로직을 수행한 후 View에게 전달할 값을 Model에 넣은 후 메서드는 home/home 혹은 home/search 라는 문자열을 반환합니다. 이 반환된 문자열은 스프링의 코드들에 의해서 Model과 함께 View라는 객체에게 전달된다고 생각하시면 됩니다. View는 전달받은 문자열을 기준으로 template 파일을 찾습니다. home/home 을 전달받은 View는 …/resources/template/home/home.html 이라는 Thymeleaf 파일을 찾아서 Model 값을 바인딩 해준 뒤, 최종적으로 사용자에게 HTML 을 반환합니다. 마찬가지로 home/search 라는 문자열을 받은 View 는 …/resources/template/home/search.html 이라는 Thymeleaf 파일을 찾아서 Model 값을 바인딩 해준뒤 HTML 을 사용자에게 전달합니다. 컨트롤러가 반환하는 문자열은 사실 [템플릿 엔진의 디렉터리/파일명]이었습니다.

다른 컨트롤러의 흐름을 보며 이해도를 더욱 높여보겠습니다.

@PostMapping("/login")
public String loginHandler(Model model,
 @ModelAttribute LoginForm form, HttpSession session, RedirectAttributes redirect) {
    try {
        AuthInfo login = loginService.login(form, session);
    } catch (LoginFailedException e) {
        MessageDto message = new MessageDto(e.getMessage(),"/login", RequestMethod.GET,null);
        return MessageHandler.showMessageAndRedirect(message,model);
    }
    // 로그인 성공시
    List<ItemDto> hotItems = itemService.getHot4Items();
    redirect.addFlashAttribute("hotItems",hotItems);
    return "redirect:/home";
}

위 코드는 AccountController.java 에 있는 코드입니다. 로그인 처리를 할 때 실행되는 메서드 입니다.

@PostMapping(”/login”)www.peteworld.shop/login 의 주소로 POST 요청을 받았을 때 실행돼야하는 메서드라는 것을 설정해줍니다.

역시 String을 반환하는 메서드입니다. String을 반환하는 것이 무엇을 반환한다는 것인지는 위에서 알아본 내용입니다.

인자로 Model 과 , LoginForm form, HttpSession session, RedirectAttribute redirect 를 받고 있습니다.

Model은 첫번째 예제에서 본것과 동일하게 View에게 값을 전달해주기 위한 객체입니다.

LoginForm 객체 앞에도 무언가가 붙어있습니다. @ModelAttribute 는 사용자의 HTTP 요청의 Body에 담겨있는 정보를 이 객체에 바인딩하겠다는 것을 의미합니다. 깊은 이해를 위해 LoginForm 객체의 코드를 보겠습니다.

@Data
@AllArgsConstructor
public class LoginForm {
    private String userId;
    private String password;
}

LoginForm 객체는 두개의 멤버변수를 갖고 있습니다. 사용자가 로그인 창의 아이디란과 비밀번호란에 입력한 정보는 HTTP Body에 담겨 서버로 전달됩니다. 이 HTTP Body를 스프링이 파싱하여 이 LoginForm이라는 객체에 넣어주어서, 백엔드에서는 조금더 편하게 프로그래밍을 할 수 있습니다. 이 과정에서 HTML 의 input 태그의 name 속성의 값이 LoginForm의 변수명에 바인딩되게됩니다.

<form action = "peteworld.shop/login" method="post">
	<input ... name="userId" (value="사용자 입력값1")... >
	<input ... name="password" (value="사용자 입력값2")... >
	<button type="submit">
</form>

위 form 은 userId와 password라는 name을 가진 2개의 input이 있습니다. form의 버튼을 누르면 HTTP Body 에 userId=사용자 입력값1&password=사용자 입력값2 과 같은 형태로 담기게 되고, 이것은 서버에서 각각 LoginForm의 같은 변수명에 할당되게 됩니다. 이렇게 때문에 name에 어떤 값을 넣어야 하는지, value는 어떤 형식이어야 하는지 알기 위해서 API 문서를 확인해야하는 것입니다. 이 모든것은 약속으로 알파벳 하나만 틀리더라도 올바르게 작동 되지 않을 수 있습니다.

다시 돌아와, 남은 인자들을 살펴보면 HttpSession과 RedirectAttributes 가 있는데 대충 세션을 관리하는 것이구나~ Redirect를 관리하는 것이구나~ 정도만 이해하시면 충분합니다.

바인딩을 마친 컨트롤러는 비즈니스 로직을 실행합니다. 사용자에게 입력받은 LoginForm 데이터와 실제 DB데이터를 비교하여 사용자가 올바른 아이디와 비밀번호를 작성했는지 확인합니다. 만약 여기서 올바르지 않은 데이터가 들어왔다면, 틀렸다는 문구를 반환하고 로그인 페이지로 되돌아갑니다. (이 부분은 조금더 심화된 코드로 작성돼있으므로 생략합니다.)

만약에 로그인에 성공한다면, /home 주소를 문자열로 반환해주면서 로그인 페이지가 아닌 메인 페이지를 사용자에게 보내주도록 합니다. 이 과정에서 메인페이지에서 필요한 아이템에 대한 데이터를 서비스 계층에서 가져와서 Model에 넣어줍니다. 위 코드는 Redirect 를 해야하는 경우이기 때문에 RedirectAttribute라는 객체에 값을 넣어주었는데, 본질적으로 Model 과 동일한 경우라고 생각하셔도 됩니다.

마지막 예시입니다. 아이디로 상품을 가져오는 메서드입니다.

@GetMapping("/api/items/{itemId}")
@ApiOperation(value = "id에 해당하는 상품 정보 가져오기", notes="id에 해당하는 상품 정보를 얻을 수 있습니다.")
public ItemDto getItem(@PathVariable("itemId") Long itemId) {
    try {
        ItemDto item = itemService.get(itemId);
        return item;
    } catch (NoSuchElementException e) {
        return new ItemDto().builder()
                .name("정보 없음")
                .id(0L)
                .build();
    }
}

맨 윗줄로부터 /api/items/{itemId} 주소로 GET 요청을 할 때 이 메서드가 실행될 것임을 알 수 있습니다.

이 메서드는 ItemDto를 반환하는 getItem이라는 메서드이며, Long itemId를 인자로 갖습니다.

ItemDto는 Item Entity 를 사용자에게 전송할때 쓰는 객체입니다. 일반적인 Entity는 DB의 값을 그대로 가져온 것이므로 사용자에게 보여져서는 안되거나 불필요한 데이터가 있을 수 있기 때문에 별도의 객체를 만들어서 필요한 정보만 반환하기도 합니다.

Long itemId 의 앞에 붙어있는 @PathVariable(”itemId”) 는 요청 주소에 있는 {itemId} 부분을 인자로 가져오겠다는 것을 의미합니다. 이 코드로 인해 /api/items/5 라는 요청을 받은 경우 itemId는 5라는 값을 가지게 됩니다.

비즈니스 로직으로는 itemService 에서 상품 식별 코드를 넘겨 아이템 정보를 가져온 후 반환하는 것입니다. 만약에 해당 Id를 가진 상품이 없을 경우 상품이 없음을 알리는 객체를 반환합니다. 이 과정에서 기존과는 다르게 String이 아닌 객체를 반환하는데, 이 경우 String을 View에게 넘기며 페이지를 반환하는 것이 아니라 ItemDto 객체를 JSON 으로 변환하여 반환하게 됩니다.

  • String을 반환하면 문자열 디렉터리의 템플릿 엔진과 연동되어 페이지가 반환된다.
  • 객체를 반환하면 해당 객체가 JSON 으로 변환되어 반환된다.

위와 같이 JSON 으로 반환되는 요청들은 프론트엔드 개발자가 Javascript 를 사용하여 동적으로 페이지를 렌더링 할 수 있는 기반 API 가 됩니다.

  • @ResponseBody 어노테이션

3.4.3 View

사실 View 객체는 DispatcherServlet 이라는 스프링 서블릿 중간자에 의해 관리되고 있어서 굳이 꺼내지 않는 이상 볼 일이 없습니다. 그럼에도 불구하고 위 설명을 통해 View가 어떤 것이고 어떤 동작을 하는지에 대해 충분히 이해했으리라 생각합니다.

4. 끝

log

최초 작성 2022-11-08


Uploaded by N2T