웹 백엔드 기술 요구 사항

  • 제공된 SQL을 이용해서 테이블을 생성하고, 샘플데이터를 입력합니다.
  • maven을 이용해서 웹 어플리케이션 프로젝트를 작성합니다.
  • 학습했던 것처럼 controller,service,dao로 레이어드 아키텍쳐를 구성합니다.
  • spring JDBC를 이용하여 주어진 테이블로부터 입력, 수정, 삭제, 조회하는 DAO와 DTO를 작성합니다.
  • 서비스 인터페이스를 작성하고 해당 서비스 인터페이스에 비지니스 메소드를 작성합니다.
  • 서비스 인터페이스를 구현하는 클래스를 작성합니다.
  • 해당 구현 클래스의 메소드에 적절한 트랜잭션에 관련된 애노테이션을 사용합니다.
  • 클라이언트에게 Web API를 제공하기 위해 RestController 를 작성합니다.

3-1 프로젝트 개발 순서

  1. maven 프로젝트를 생성합니다.

    • groupId 는 com.naver.reservation, artifactId 는 reservation 으로 지정
  2. MySQL 에서 프로젝트에 사용할 database 와 사용자 계정을 생성합니다.

    • 생성한 데이터베이스와 계정정보는 src/main/resources/application.properties 파일에 설정함
  3. 생성한 데이터베이스에 접속하여 주어진 sql을 실행합니다.

    • 먼저 ddl.sql의 내용을 실행하여 테이블을 생성하고, dml.sql의 내용을 실행하여 샘플 데이터를 추가함
  4. sample이미지가 있는 압축파일인 img.zip을 webapp 폴더에 압축 해제합니다.

    • img 폴더를 포함한 앞으로 프로젝트에 있어서 필요한 모든 resource 파일들은 webapp/resources 에 위치함
  5. Spring MVC, Spring JDBC를 사용하기 위한 Spring설정 파일들을 작성합니다.

    • pom.xml 에 프로젝트에 필요한 의존성 라이브러리들을 설정 (servlet, jsp, jstl, mysql, datasource, jackson 등)
    • web.xml 기본 설정 (spring context 설정, DispatcherServlet 설정 등)
    • DispatcherServlet 가 읽어들일 설정 (WebMvcContextConfiguration.java)
      • addResourceHandlers: /css, /img 와 같은 URL 요청시 연결해줄 위치 설정
      • configureDefaultServletHandling: URL 에 요청 정보가 없을시 WAS 의 default servlet이 static한 자원을 읽어서 보여줌
      • addViewControllers: 특정한 URL 에 대한 처리를 controller 클래스를 작성하지 않고 매핑할 수 있도록 해주는 부분
    • ApplicationConfig 설정
      • @ComponentScan 의 basePackages 에 읽어올 컴포넌트들을 입력함
      • @Import({ DBConfig.class }) 로 데이터베이스 연결 정보를 사용

    pom.xml

    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.naver.reservation</groupId>
    <artifactId>reservation</artifactId>
    <packaging>war</packaging> 
    <version>0.0.1-SNAPSHOT</version>
    <name>reservation Maven Webapp</name>
    <url>http://maven.apache.org</url>
    
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>4.3.5.RELEASE</spring.version>
        <jackson2.version>2.8.6</jackson2.version>
    </properties>
    
    <dependencies>
        <!-- SPRING -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>
    
        <!-- MYSQL -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>
    
        <!-- basic data source -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.1.1</version>
        </dependency>
    
        <!-- Servlet JSP JSTL -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.3.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
    
        <!-- Jackson Module -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson2.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jdk8</artifactId>
            <version>${jackson2.version}</version>
        </dependency>
    
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    
    </dependencies>
    
    <build>
        <finalName>reservation</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    </project>

    web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app>
    
    <display-name>Spring JavaConfig Sample</display-name>
    <context-param>    
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.naver.reservation.config.ApplicationConfig
        </param-value>
    </context-param>
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
    
    <servlet>
        <servlet-name>mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param> 
            <param-name>contextClass</param-name>
            <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.naver.reservation.config.WebMvcContextConfiguration
            </param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter
        </filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    </web-app>
  6. 샘플 데이터를 읽어 들여 메인화면을 출력하기 위한 DTO, Controller, Service, Repository를 알맞게 작성합니다.

    • web API 스펙의 models 를 참고하여 dto 를 작성

    • web API 스펙의 models response 를 참고하여 각각의 dao 를 위한 sql 문 작성

      • categoryDaoSql:

        Category model response

        SELECT_CATEGORIES : category 를 모두 가져옴

      • productDaoSql:

        Product model response

        SELECT_PRODUCTS : 해당 category 에서 start 지점 부터 limit 개수만큼 product 를 가져옴

        COUNT_PRODUCTS : 모든 product 의 개수를 카운트

      • promotionDaoSql:

        Promotion model Response

        SELECT_PROMOTIONS : promotion 을 모두 가져옴

    • CategoryDao 와 PromotionDao 에서의 함수들은 GET 요청이기에 인자 값이 필요 없으나 ProductDao 의 selectProducts, countProducts 함수에서는 sql 문에 있는 :category_id, :start, :product_limit 에 들어갈 값들을 함수의 인자 값을 통해 넣어준다.

    • Category, Product, Promotion 각각에 대한 Service 인터페이스를 작성한다.

    • Service 에서는 model response 를 확인하고 dao 를 통해 나온 값들을 해당 String 값들에 맵핑 시킨다. (예를 들면, SELECT_PRODUCT 를 통해 얻은 리스트 값은 "items"에 맵핑 시키고 COUNT_PRODUCTS 를 통해 얻은 값은 "totalCount" 에 맵핑 시킨다.)

    • Controller 에서 MainController 를 통해서 처음에 path가 없을 때 "mainpage" 를 찾도록 했고, Category 와 Product, Promotion 각각 정보를 얻기 위한 REST API 주소 설정을 해주었다.

 

/api/products : 설정해놓은 product 정보들을 가져옴
/api/products?start=0 : 카테고리ID 가 없으면 전체리스트, start 는 더 불러올때 시작 지점을 의미
/api/products?categoryId=1&start=0 : 해당 카테고리ID 의 product 들을 가져옴, start 는 더 불러올때 시작 지점을 의미
/api/promotions : promotion 정보들을 가져옴
/api/categories : category 정보들을 가져옴

 

 

 

이제 기능적인 부분을 보자,

 

 

카테고리노출영역(탭UI)

  • 전체리스트가 Ajax를 통해서 화면에 4개의 아이템이 노출된다.
  • 탭별로 전체갯수가 상단에 노출되야 한다.
  • 각 아이템(전시상품)은 이미지/제목/장소/설명이 노출되야 한다.
  • 탭을 누르면 다른 카테고리 콘텐츠 4개가 다시 노출된다.
  • 더보기를 누르면 4개씩 노출되야 한다. 4개보다 적으면 적은 만큼 노출되야 한다.
  • 더보여줄 데이터가 없다면 더보기는 사라진다.
  • TOP영역이 선택되면, 화면 맨 위로 이동한다.

전체적으로 함수들을 크게 이렇게 나눠보았다.

document.addEventListener("DOMContentLoaded", () => {
  initSlidePromotion();
  initTabCategory();
  viewTopBtn();
});

initSlidePromotion() 부터 살펴보자.

  • 우선 initSlidePromotion() 에서 ajax 로 promotions 정보를 불러온 뒤 아래 함수들을 사용
  • createPromotionTemplate() : mainpage.jsp 파일에 만들어 놓은 promotionTemplate 에 위에서 받은 정보들을 넣는 함수
  • slidePromotion() : promotion 이미지들이 자동 슬라이딩 할 수 있도록 하는 함수

 

 

함수들을 최대한 기능 별로 분리해보았다. 적절히 분리하고 나니 확실히 분리하기 전 보다 가독성이 좋았다.


PROMOTION_IMAGE_WIDTH, SLIDE_SPEED 같은 값들은 상수로 따로 빼주었다.

 

 

 

initTabCategory() 를 살펴보자.

  • requestIsOk : httpRequest.onload 안에 조건문에 들어갈 함수. ajax 연결이 되면 true, 안되면 false
  • appendCategory : category 가 들어가야 할 곳에 템플릿으로 만든 데이터를 넣는 함수
  • createCategoryTemplate : mainpage.jsp 에 category Template 에 정보를 넣는 함수
  • initProductList : 다른 category 탭을 선택했을 때 product list 를 초기화 해주는 함수
  • getProductData : product 정보들을 가져오는 함수
  • categoryClickEvent : category 탭을 클릭했을 때 실행할 함수
  • changeActiveCategory : category 탭을 클릭했을 때 클릭한 탭의 class 에 active 를 추가하는 함수(해당 category 탭이 녹색으로 변함)
  • initProductData : product 부분의 데이터들을 비워주는 함수
  • visibleMoreBtn : 맨 아래 더보기 부분을 나타낼지 말지 정하는 함수(인자값 true 는 보임, false 는 안보임)
  • updateProductTotalCount : mainpage.jsp 에서 product 들의 총 개수를 업데이트 하는 함수
  • updateProductList : mainpage.jsp 에서 product 들의 정보들을 업데이트 하는 함수
  • isEven : 홀수인지를 판별하는 함수
  • createProductTemplate : mainpage.jsp 에 product Template 에 정보를 넣는 함수

 

 

initTabCategory 부분도 최대한 기능별로 함수를 나눠 보았다. 처음에는 너무 사소한 것들 까지 함수로 나누었더니 오히려 더 보기 불편했던 적이 있다. 함수로 나눌 때는 알맞은 기준을 가지고 적당한 크기로 나누는 것이 좋은 것 같다.

 

 

 

 

두번째로 PASS 를 하였는데, 처음에 제출했던 과제에서는 BE, 말그대로 백엔드 부분만 제출하는 줄 알고 기능적인 부분을 구현하지 않고 제출하였다... 평가기준표를 제대로 보지 않은 내 잘못이다 ㅠㅡㅜ

 

 

두번째 제출에서 받은 피드백들이다.

 

쿼리문을 통해서 받아온 정보들을 swagger 에 명시되어 있는대로 만드는 과정에서 Map 을 사용하였는데, 리뷰어 분 께서 좋은 방법이 아니라고 하셨다. dto 를 만든 것 처럼 api 응답 모델마다 클래스들을 만들라는 말씀이신 것 같다. 

 

 

리뷰어님 말씀대로 selectProductCount 라고 짓는 것이 훨신 좋은 방법인 것 같다.

 

 

부스트 코스 과정을 하면서 Spring 을 처음 접해보았는데 알아갈수록 정말 매력적이고 잘 만든 튼튼한 프레임워크인 것 같다. (열심히 공부해봐야징)

 

이번 과제를 하면서 내 머리를 가장 쥐어짰던 부분은 바로 변수명, 함수명 설정이였다. 내가 이렇게 작명 센스가 없는 줄 몰랐다. 그래서 구글에 네이밍 하는 법을 찾아보면서 최대한 적용을 해보았다. 다음 번 과제에서는 처음부터 통일성 있게 변수명, 함수명들을 설정하여 가독성을 높여보고 싶다.

 

+ 따끈한 최근 게시물