ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [1단계] 마이 셀파 웹 앱 2일차. 프로젝트 구성과 화면 이동
    레거시/레거시-마이 셀파 리부트 2018. 6. 19. 14:07
    반응형

    *본 글은 필자가 진행한 프로젝트 '마이 셀파'의 작업 일기입니다. 재밌게 봐주세요.


    마이 셀파 리부트 1단계 Spring MVC를 활용한 웹 앱

    2일차 작업 프로젝트 구성과 화면 이동


    오늘은 스프링 이니셜라이저를 통해 인텔리제이 커뮤니티 에디션으로 프로젝트를 임포트한 후 간단하게 화면 이동 구현까지 작업하였습니다. 일단은 스프링 이니셜라이저 홈페이지로 이동하여 프로젝트를 생성해보겠습니다.


    스프링 이니셜라이저로 프로젝트 생성


    https://start.spring.io/


    위의 URL을 타고 가면 아래와 같은 홈페이지가 나옵니다. 




    여기서 자신이 빌드 도구, 언어, 스프링 부트 버전, 원하는 패키지(회사명), 아티팩트(프로젝트 명) 의존관계를 설정하고 프로젝트를 만들 수 있습니다. 스프링 이니셜라이저에서 프로젝트를 생성하는 방법은 다음과 같습니다.


    1. 먼저 빌드 도구를 Gradle project, 언어를 Kotlin 스프링 부트를 2.0.3을 선택합니다. 
    2. 그 후 원하는 패키지명, 아티팩트명을 입력합니다.
    3. Search for dependancies 입력란에 자신이 원하는 모듈을 타이핑합니다. 지금은 web, thymeleaf만 치고 넘어갑니다.
    4. Generate Project 버튼을 누르면 컴퓨터에 저장이 됩니다.


    참고! 

    원래는 프로젝트 구성 단계에서 설정한 모듈들, 예를 들면 저의 프로젝트에서는 web, thymeleaf, jdbc, security가 되겠죠? 이러한 모듈들을 프로젝트 생성 시에 의존관계를 설정하는게 일반적이지만 저는 그 날 필요한 모듈들을 그때 그때 의존관계를 설정하도록 하겠습니다.


    인텔리제이 커뮤니티 에디션 프로젝트 임포트


    이제 인텔리제이로 프로젝트를 열어보겠습니다. 원래 ultimate 버전에서는 STS(이클립스 스프링 툴)처럼 IDE 내에서 이러한 작업을 설정할 수있습니다. 그러나 community 버전은 스프링이 들어가있지 않기 때문에 스프링 작업을 하려면 이처럼 외부에서 gradle(build.gradle) 혹은 maven(pom.xml)을 빌드 도구로 하여 프로젝트를 설정 후 임포트 해야 합니다. 프로젝트를 여는 방법은 다음과 같습니다.


    1. 스프링 이니셜라이저로 만든 프로젝트를 적당한 디렉토리로 옮긴 후 압축을 풉니다.
    2. 인텔리제이를 키고 file->open->[압축을 푼 디렉토리]->프로젝트를 엽니다. 


    참고! 

    압축을 푼 디렉토리에서 프로젝트를 선택할 때 이러한 그림이 뜨면 제대로 임포트가 될 겁니다.




    이렇게 프로젝트를 열면 텔리제이 내의 그래들을 이용해 프로젝트에서 설정한 외부 프레임워크/라이브러리들을 알아서 가져오는데 이 때 시간이 조금 걸리니 기다려 주시거나 그냥 코드 작업을 하면 됩니다. 


    프로젝트 패키지 구성


    프로젝트를 구성하기 전에 제가 선택한 계층형 아키텍처를 간단하게 설명하겠습니다.


    계층형 아키텍쳐란?




    • 도메인 계층 
      도메인 계층은 애플리케이션 내에서 사용하는 모델이나 비지니스 로직이 아닌 데이터 자체, 그 데이터를 관리하는 성격의 컴포넌트가 존재하는 계층입니다. 이 계층은 프런트엔드에 의존하지 않는다는 특징이 있습니다.
    • 애플리케이션 계층
      애플리케이션 계층은 프런트엔드에서 비지니스 로직 실행 직전까지의 컴포넌트들이 존재합니다. 음 사용자 URL에 따라 페이지를 이동시키는 컨트롤러, html,css,javascript 등의 뷰가 이들입니다.


    팁! 만약 RESTful 웹 서비스를 만든다면 애플리케이션 계층을 빼고 도메인 계층을 그대로 쓰면 됩니다. 이는 나중에 더 알아보겠습니다.


    각 계층의 컴포넌트를 더 깊이 알아보겠습니다.


     계층명

     컴포넌트 명 

     역할 

     도메인

     모델

     애플리케이션에서 다루는 데이터를 표현

     레포지토리

     모델의 CRUD 작업을 수행

     서비스

     비지니스 로직 처리, 트랜잭션 관리 지점

     애플리케이션

     컨트롤러

     화면 이동, 데이터 입/출력 제어

     뷰

     유저 인터페이스, 화면등을 제공


    이제 다음처럼 우리 프로젝트의 패키지들을 계층형 아키텍처에 따라서 다음의 그림처럼 구성해주었습니다. 




    각 패키지 설명은 다음 표와 같습니다.


     각 계층 패키지

     설명

     하위 패키지

     설명

     src/main/kotlin/프로젝트

     /app

     이 패키지는 애플리케이션 계층의 컨트롤러들을 모아둔 패키지입니다.

     join

     회원가입 페이지를 연결시켜주는 컨트롤러

     login

     로그인 페이지를 연결시켜주는 컨트롤러

     search

     검색/상세 정보 페이지를 연결시켜주는 컨트롤러

     user

     유저 페이지를 연결시켜주는 컨트롤러

     src/main/kotlin/프로젝트

     /domain/model

     이 패키지는 도메인 계층의 데이터 엔티티 그러니까 POJO 클래스들의 집합체입니다. SQL에서 정의한 테이블과 유사합니다.

     

     src/main/kotlin/프로젝트

     /domain/repository

     이 패키지는 앱에서 model에 있는 엔티티와 SQL의 테이블을 연결시켜서 CRUD작업을 수행합니다.

     lot 주차장에 관련된 정보들을 CRUD
     user 유저에 관련된 정보들을 CRUD
     reservation

     주차 예약 정보들을 CRUD

     src/main/kotlin/프로젝트
     /domain/service

     이 패키지는 비지니스 로직을 수행합니다. 데이터들을 뷰/사용자가 원하는 형식으로 바꾸거나 존재하지 않을시에 컨트롤등을 이 패키지의 클래스들이 제어합니다.

     lot

     lot 레포지토리를 이용하여 주차장에 관련된 정보들을 서비스 

     user

     user레포지토리에 이용하여 유저에 관련된 정보들을 서비스

     reservation

     reservation 레포지토리를 이용하여 예약에 관련된 정보들을 서비스

     src/main/resource/templates

     이 패키지는 애플리케이션 계층의 뷰 컴포넌트들을 모아둔 패키지 입니다. Spring-Thymeleaf등의 웹 템플릿 엔진 모듈을 설정해 놓으면 컨트롤러에서의 매핑 메소드들 반환 값에 하위 패키지들의 html의 경로를 반환시키면 앱이 알아서 경로를 찾아갑니다. 

     join

     회원 가입 페이지에 보이는 뷰 html, css, js가 존재

     login

     로그인 페이지에 보이는 뷰 html, css, js가 존재

     search

     검색/상세정보 페이지에 보이는 뷰 html, css, js가 존재

     user

     유저 페이지에 보이는 뷰 html, css, js가 존재


    자 이렇게 페이지를 구성했으면 먼저 스프링 부트가 잘 설정되었는지 테스트해보기 위해 "/"에 해당하는 컨트롤러를 만들어주었습니다.


    컨트롤러 : 로그인 컨트롤러 
    src/main/koltin/project/app/login/LoginController.kt 

    package com.gurumee.myselpa.app.login

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

    @Controller
    class LoginController{
    @RequestMapping("/")
    fun getToLogin(model: Model): String {
    return "login/login.html"
    }
    }


    그리고 Artifact명에 따라 자동으로 생성된 src/main/kotlin/project/...Application.kt 저처럼 했다면 MySelpaApplication.kt를 실행한 후 localhost:8080/ 에 접속해보면 다음과 같은 에러 페이지가 뜬다면 제대로 동작하고 있음을 알 수 있습니다. 




    이 에러 페이지가 뜨는 이유는 src/main/resource/templates 디렉토리에 컨트롤러가 반환하는 URL의 해당 html파일이 없기 때문인데 다음과 같은 뷰를 만들어주면 다음의 페이지가 정상적으로 뜰 것입니다.


    뷰 : 로그인 뷰 

    src/main/resource/templates/login/login.html 

    <!DOCTYPE html>
    <html>
    <!-- URL : http://localhost:8080/ -->
    <head>
    <meta charset="UTF-8">
    <title>마이 셀파 리부트</title>
    </head>
    <body>
    로그인 페이지
    <a href="/user">마이 페이지</a>
    <a href="/join">회원 가입</a>
    </body>
    </html>


    간단한 화면 이동 작업


    그 후 간단하게 설계 단계에서 정의해두었던 페이지 이동에 따라서 간단하게 하이퍼링크를 걸어 GET 방식으로 이동하는 페이지와 컨트롤러를 만들었습니다.


    • 회원가입
      컨트롤러 src/main/koltin/project/app/join/JoinController.kt 
      package com.gurumee.myselpa.app.join

      import org.springframework.stereotype.Controller
      import org.springframework.web.bind.annotation.RequestMapping
      import org.springframework.web.bind.annotation.RequestMethod

      @Controller
      class JoinController{

      @RequestMapping("/join")
      fun getToJoin(): String = "/join/join.html"
      }
      뷰 
      src/main/resource/templates/join/join.html 
      <!DOCTYPE html>
      <html>
      <!-- URL : http://localhost:8080/join -->
      <head>
      <meta charset="UTF-8">
      <title>마이 셀파 리부트</title>
      </head>
      <body>
      회원가입 페이지
      <a href="/">로그인 페이지</a>
      </body>
      </html>
    • 검색
      컨트롤러 src/main/koltin/project/app/search/SearchController.kt 
      package com.gurumee.myselpa.app.search

      import org.springframework.stereotype.Controller
      import org.springframework.web.bind.annotation.RequestMapping
      import org.springframework.web.bind.annotation.RequestMethod

      @Controller
      @RequestMapping("search")
      class SearchController {
      @RequestMapping(method = arrayOf(RequestMethod.GET))
      fun getToSearch(): String = "search/search.html"

      @RequestMapping(value="{lot_no}", method=arrayOf(RequestMethod.GET))
      fun getToLotDetails() = "search/search_info.html"
      }
      뷰 
      • 검색 페이지 src/main/resource/templates/search/search.html 
        <!DOCTYPE html>
        <html>
        <!-- URL : http://localhost:8080/search -->
        <head>
        <meta charset="UTF-8">
        <title>마이 셀파 리부트</title>
        </head>
        <body>
        주차장 검색 페이지
        <a href="/">로그인 페이지</a>
        <a href="/search/1">주차장 상세 정보 페이지</a>
        <a href="/user">유저 페이지</a>
        </body>
        </html>
      • 상세 정보 페이지 src/main/resource/templates/search/search_info.html 
        <!DOCTYPE html>
        <html>
        <!-- URL : http://localhost:8080/search/{lot_no}-->
        <head>
        <meta charset="UTF-8">
        <title>마이 셀파 리부트</title>
        </head>
        <body>
        주차장 상세 정보 페이지
        <a href="/">로그인 페이지</a>
        <a href="/search">검색 페이지</a>
        </body>
        </html>
    • 유저
      컨트롤러 src/main/kotlin/project/app/user/UserController.kt 
      package com.gurumee.myselpa.app.user

      import org.springframework.stereotype.Controller
      import org.springframework.web.bind.annotation.RequestMapping
      import org.springframework.web.bind.annotation.RequestMethod

      @Controller
      @RequestMapping("user")
      class UserController{
      @RequestMapping(method=arrayOf(RequestMethod.GET))
      fun getToUser() = "user/user.html"
      }
      뷰 유저 페이지 src/main/resource/templates/user/user.html 
      <!DOCTYPE html>
      <html>
      <!-- URL : http://localhost:8080/user-->
      <head>
      <meta charset="UTF-8">
      <title>마이 셀파 리부트</title>
      </head>
      <body>
      유저 페이지
      <a href="/">로그인 페이지</a>
      <a href="/search">검색 페이지</a>
      </body>
      </html>

    SearchController를 만들 때 LoginController, JoinController와 달리 최상위 애노테이션에 파라미터 이름으로 매핑해주었습니다.


    @Controller

    @RequestMapping("search")

    class SearchController ...





    왜냐하면 상세 정보 페이지의 URL은 다음과 같기 때문입니다. 





    /search/{lot_no}


    이처럼 상세 정보 페이지는 search라는 URL 경로를 타고 들어가는 페이지이기 때문에 검색 페이지와 상세 정보 페이지는 SearchController에서 관리합니다. 참고로 {lot_no}는 검색 페이지에서 유저가 클릭하는 주차장에 따라서 lot_no가 동적으로 변경되기 때문에 이에 대응하기 위하여 getToLotDetails 함수의 어노테이션 RequestMapping에 {lot_no}를 value값으로 설정해주었습니다.


    class SearchController{

        ....

        @RequestMapping(value="{lot_no}" method=arrayOf(RequestMethod.GET))

        fun getToLotDetails() ...

    }

     
    팁! 
    만약 코틀린이 아닌 자바로 코드로 짠다면 

        RequestMapping(value="{lot_no}", method=RequestMethod.GET) 

    이렇게 바로 써도 됩니다. 처음에 코틀린에서도 이렇게 해봤는데 Type Miss Match 컴파일 에러가 떠서 자바 코드로 짠 후 코틀린으로 변경하니까 이유를 알 수 있었습니다. 코틀린에서는 무조건 arrayOf로 감싸서 Array<RequestMethod>로 애노테이션 파라미터를 만들어서 보내주어야 합니다.

     

    이제 다시 Application.kt를 실행해보면 걸어둔 하이퍼링크에 따라서 페이지가 이동하는 것을 확인할 수 있을 겁니다. 오늘 작업은 이로써 마치겠습니다. 내일은 DB생성과 결과로 표현하는 쿼리문 작성 작업을 할 예정입니다.


    참고 : 책 스프링 철저 입문 (출판사 위키북스)

Designed by Tistory.