웹 백엔드 기술 요구 사항
- 제공된 SQL을 이용해서 테이블을 생성하고, 샘플데이터를 입력합니다.
- maven을 이용해서 웹 어플리케이션 프로젝트를 작성합니다.
- 학습했던 것처럼 controller,service,dao로 레이어드 아키텍쳐를 구성합니다.
- spring JDBC를 이용하여 주어진 테이블로부터 입력, 수정, 삭제, 조회하는 DAO와 DTO를 작성합니다.
- 서비스 인터페이스를 작성하고 해당 서비스 인터페이스에 비지니스 메소드를 작성합니다.
- 서비스 인터페이스를 구현하는 클래스를 작성합니다.
- 해당 구현 클래스의 메소드에 적절한 트랜잭션에 관련된 애노테이션을 사용합니다.
- 클라이언트에게 Web API를 제공하기 위해 RestController 를 작성합니다.
3-1 프로젝트 개발 순서
-
maven 프로젝트를 생성합니다.
- groupId 는 com.naver.reservation, artifactId 는 reservation 으로 지정
-
MySQL 에서 프로젝트에 사용할 database 와 사용자 계정을 생성합니다.
- 생성한 데이터베이스와 계정정보는 src/main/resources/application.properties 파일에 설정함
-
생성한 데이터베이스에 접속하여 주어진 sql을 실행합니다.
- 먼저 ddl.sql의 내용을 실행하여 테이블을 생성하고, dml.sql의 내용을 실행하여 샘플 데이터를 추가함
-
sample이미지가 있는 압축파일인 img.zip을 webapp 폴더에 압축 해제합니다.
- img 폴더를 포함한 앞으로 프로젝트에 있어서 필요한 모든 resource 파일들은 webapp/resources 에 위치함
-
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>
-
샘플 데이터를 읽어 들여 메인화면을 출력하기 위한 DTO, Controller, Service, Repository를 알맞게 작성합니다.
-
web API 스펙의 models 를 참고하여 dto 를 작성
-
web API 스펙의 models response 를 참고하여 각각의 dao 를 위한 sql 문 작성
-
categoryDaoSql:
SELECT_CATEGORIES : category 를 모두 가져옴
-
productDaoSql:
SELECT_PRODUCTS : 해당 category 에서 start 지점 부터 limit 개수만큼 product 를 가져옴
COUNT_PRODUCTS : 모든 product 의 개수를 카운트
-
promotionDaoSql:
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 을 처음 접해보았는데 알아갈수록 정말 매력적이고 잘 만든 튼튼한 프레임워크인 것 같다. (열심히 공부해봐야징)
이번 과제를 하면서 내 머리를 가장 쥐어짰던 부분은 바로 변수명, 함수명 설정이였다. 내가 이렇게 작명 센스가 없는 줄 몰랐다. 그래서 구글에 네이밍 하는 법을 찾아보면서 최대한 적용을 해보았다. 다음 번 과제에서는 처음부터 통일성 있게 변수명, 함수명들을 설정하여 가독성을 높여보고 싶다.
'부스트 코스' 카테고리의 다른 글
[부스트코스] 프로젝트4-2.예약:상세:FE 리뷰 후기 (0) | 2019.08.26 |
---|---|
[부스트코스] 프로젝트4-1.예약:상세:BE 리뷰 후기 (0) | 2019.08.14 |
[부스트 코스] 쿠키 세션 예제 (0) | 2019.08.12 |
[부스트코스] 프로젝트3-2.예약:메인:FE 리뷰 후기 (0) | 2019.08.11 |
프로젝트 중 찾아본 것들 정리 (0) | 2019.07.29 |