본문 바로가기
프로젝트

MSA & Hexagonal를 적용한 웹소설 플랫폼 팀프로젝트 회고록

by Ropung 2023. 7. 25.
깃허브:
    FE-레포지토리(클릭)   
    BE-레포지토리(클릭)  
관리:  웹소설 플랫폼 노션페이지(클릭)  
화면설계 & 데이터설계:  피그마(클릭)

 

사용기술:

1. Project 소개

웹 소설 플랫폼

유저가 웹소설을 작성하면 웹 소설 플랫폼은 보여줄 수 있는 서비스를 제공하며, 유저에게 추천작을 보여주거나 다른 유저가 쓴 웹 소설을 보여주는 사이트이다.

유저는 다양한 웹 소설을 보거나 써 볼 수 있어 유저들이 직접 웹 소설 생태계를 만들어가는 웹 플랫폼 사이트다. 🙂

2. 개요

SES(Soft Engineer Society) 학원에서 진행하게 된 프로젝트에서 부족하지만 팀장역을 맡게 됐다.

1차(04.01 ~ 04.28)는 총 3인으로 진행되었지만 2차 진행 전 팀원들이 중도포기를 하여 혼자 프로젝트를 진행하였다.

2차(06.01 ~ 06.30) 때는 다른 조와 병합하여 진행 당시 총 4명으로 프로젝트를 진행했다.

프로젝트를 진행하며 느낀 점학습과정 등을 수록해보려고 한다.

3. 주제 선택

(팀 주제 vs 기업 주제) 중 팀원들과 회의를 통해 관심도, 기술, 취업을 기준으로 기업의 주제를 선택했다.

팀원 중 웹 소설 플랫폼을 자주 사용하던 팀원이 있었고 관심도가 높으니 기획도 잘 뽑힐 거라 생각했고 기업이 요구하는 기술과 학원에서 배운 기술의 양상이 많이 달랐지만 많이 쓰이고 사용하려는 기술, 언어, 생태계가 잘 구성되어 있어 조 xx기업에서 기술핏이 맞아 기업을 선택했고 요구하는 기능 등을 고려했을 때 다른 기업에도 공통되는 부분도 있고 학습하기에도 적절하다 판단되어 이를 바탕으로 최종적으로 프로젝트를 선택하고 진행하게 되었다.

요구사항 : Notion: 조xx 요구사항 정리 페이지(클릭)

4. 기획 & 설계

기획

경험있는 사람이 없었고 나 또한 계약직(FE)일 때 어깨 너머로 잠깐 들은 게 전부지만 기획의 중요성을 뼈져리게 알고 있었기 때문에 구글링과 유튜브로 기획하는 법을 짧게 학습했고 미약하나마 3개의 키워드(목표, 문제점, 해결책)로 나누었다.

목표:

우리는 웹 소설 플랫폼을 만드는 목적이 있었지만 어떻게 만드는지 도메인 지식이 전무했기 때문에 참고할 수 있는 웹 소설 플랫폼을 벤치마킹하고 이를 피그마를 사용해 프레임(틀)을 구성했다.

비유하자면 자동차도 버거워서 자전거를 만드려 했고 자전거도 생각보다 벅차서 자전거 바퀴(메인화면)를 잡고 프레임(화면)과 고무(기능)를 원자처럼 작게 쪼개서 하나하나씩 만들었다.

문제점:

  • 수동적인 팀원 및 갈등해결 (프로젝트 진행저하)
  • 새로운 기술도입에 대한 스트레스 및 적응도(중도포기)
  • 경험을 베이스로 판단할 수 없어 의견조율이 힘든 문제점들(잦은 회의가 발생)
  • 설계 오류로 인한 테이블 구조의 문제점들(테이블 변경)
  • 개발 환경에서 발생하는 문제점들 (비일관성, OS별 프로젝트 관리)
  • 프로젝트 완성에 대한 압박 & 데드라인에 대한 걱정(부족한 시간과 인원)
  • 등등 엄청나게 많았다.

해결책:

문제를 쉽게 이해할 수 있게 최대한 환경을 만들어주고 협업관점에서 컨벤션이나 통일되게 쓸 수 있는 방법을 단톡방이나 커뮤니티 혹은 사이드프로젝트에서 사용하는 방법들을 가져와 적용시켰다.

  • 수동적인 팀원 ->
    • Slack툴로 언제? 어디서? 무엇을? 어떻게? 할 것인지에 대한 숙제를 오전 9시에 회의를 하며 팀원에게 인지시키고 작업을 데일리 스크럼을 진행하였다.(이 시기에 팀원 한명이 못 버티고 나가버렸다.)
  • 잦은 회의 ->
    • Notion 으로 만들어야 하는 Notion: API기능 리스트(클릭)와 Notion: Page URL(클릭)를 만들고 관리했다.
    • Slack으로 만드는 중 인 것을 모니터링했다. (FE 경우 리액트를 할 줄 아는 팀원이 없어 혼자 작업했기 때문에 빠르게 만들 수 있는 쪽으로 협의했다.)

  • 테이블 설계 ->
    • 1차때는 최대한 확장을 위한 설계를 지향하였고 2차 때도 쌓아둔 공부를 통해서 최대한 만들려고 노력했다.
  • 환경 문제 ->
    • Docker-compose: 라는 툴을 알게 되었고 이미지를 통합적으로 환경을 관리했다.
    • GitHub: 버전관리와 협업을 위해 브랜치단위로 협업하였다.
  • 컨벤션 문제 ->
    • 초기에는 구글링을 해서 코딩 컨벤션을 맞추려고 하였으나 잘 지켜지지 않아서 Notion: 네이밍 컨벤션(클릭)을 만들었다.
    • 중간중간 팀원이 에러가 발생했을때 같이 해결하며 도중에 보이는 컨벤션을 같이 잡았다.(이때 갈등이 생겼지만 협업관점에서 왜 그렇게 해야 하는지 납득시켰다.)
  • 시간문제 ->
    • 주말을 활용

설계

시간에 쫓기다 보니 데이터 설계는 막히는 것을 학습해 가며 만들었다.

모두 경험이 없는 상태에서 머리를 맞대서 만들다 보니 ERD: 1차 때 설계했던 부분(클릭)이 상당 부분 바뀌었다.

2차 프로젝트가 진행 됐을 때도 설계오류 때문에 여러 번 ERD: 2차 데이터설계(클릭)가 상당 부분 바뀌었다.

5. 프로젝트 개선점

  • 팀원들의 정보를 매번 물어보는 시간이 리소스 낭비라고 생각하여 이메일과 깃허브 주소를 적어서 관리했다.
  • 예비군 등 일정이 생긴 팀원을 위해 Notion으로 세부 변경사항 관리를 했다. (Slack으로 변경사항 전달)

프로젝트 노션페이지(클릭)

1차 프로젝트 BE 적용사항

Docker-compose 적용

적응을 힘들어하는 팀원과 노트북(로컬) 환경을 맞출 수 있는 방법이 필요했다.
로컬 환경을 동일하게 맞추는 과정을 명령어 한 줄로 쉽게 가능한 Docker-compose를 알게 되었다.
컨테이너에 관심이 있었지만 시간과 인원이 부족했고 Docker를 다루기엔 학습비용(러닝커브)이 걱정됐다.

Docker보단 쉽게 사용 가능한 docker-compose를 적용하게 되었고 어떤 노트북이든 변경이 생겨도 yaml 파일만 동일하게 관리하고 공유(Slack)하면 되었기에 관리비용이 줄었다.

 

properties -> yml 변경

적용 후 계층구조를 한눈에 파악하여 가독성이 개선되었다.

 

개발환경 배포환경 분리

현 프로젝트에서 배포는 하지 않을 거지만 추후 편의성을 생각하여 분리했다.

 

파일구조 개선

기존의 구조는 컨트롤러 안에 모든 도메인의 컨트롤러를 나 두었다.
프로젝트 규모가 커질수록 팀원이 어디에 만들었는지 관리가 힘들어졌다. 구조가 엉망이라고 느꼈고, 구조를 개선했다.

구조개선을 위해 역할단위에서 도메인단위로 변경하였다. 패키지가 늘어난다는 단점이 있었지만,
어느 도메인에 무엇이 있는지에 대한 파악이 쉬워졌고 어떤 사람이 만들던 찾기가 쉬워져서 작업속도가 더 빨라졌다.

 

JDK1.8 -> JDK17 적용

초기 프로젝트를 진행했는데 팀원이 Spring Boot 3.x 버전으로 세팅하는 상황이 발생했다.
기존 JDK1.8이 작동은 하였으나 몇몇 개 안되거나 에러가 뜨는 문제가 발생했다.(찾아보니 V3.x부터는 자바 17을 요구한다.)

프로젝트의 목적에 학습도 포함되어 있으니 우리 팀은 Java 17을 사용하기로 했고 그중 record 문법을 알게 되었다.
이를 활용해 기존 dto클래스 -> record dto클래스를 만들었을 때 최소 2~3배 이상의 코드수가 줄어서 가독성이 좋아졌고 유지보수가 편해진다는 장점이 있었다.

Collectors.toList() -> toList()로 변경되어 가독성이 개선됐다.
또한 Java 14에서 개선된 switch을 사용하여 변수선언에 필요한 코드 및 break, retrun 도 생략하여 가독성이 개선됐다.

Java 17을 쓴다고 Spring Boot 3.x을 강제로 사용해야 하는 건 아니었고 3 버전의 경우 아직 버전이슈 대응이 힘들 것 같아서 본 프로젝트의 버전을 2.x 버전으로 마이그레이션 했다.

그 밖에 Sealed, instanceof 패턴매칭, 등이 있지만 저런 게 추가되었구나 하는 정도로만 보고 필요할 때 추후 학습할 예정이다.

 

Autowired -> RequiredArgsConstructor 개선

학원에서 배웠던 Autowired의 개념을 배웠지만 스프링팀에서 지양한다는 사실을 알게 되었다.
필드주입의 문제점 및 순환참조의 문제점 등이 있지만 경험해보진 못했고 @RequiredArgsConstructor를 사용하여 기존보다 가독성이 개선되었다.

 

상수관리 & Enum 휴먼에러 개선

프로젝트를 진행하던 중 에러가 떠서 원래 작동하던 동작이 안되기 시작했다.
(3시간 동안 이 오류를 찾기 위해 고생했다.)

구글링과 스택오버플로우를 뒤져봤고 결국은 gpt로 해결했다.(원인은 오타..)

이러한 문제를 개선하기 위해 IDE 레벨에서 잡아줄 수 있게 상수처리와 Enum을 사용했다.
IDE 레벨에서 컴파일 때 잡아주기 때문에 이로 인한 문제점이 많이 개선되었다.

상수관리
Enum

 

MyBatis -> JPA(Java Persistence API) 변경

학원에서는 MyBatis를 사용하여 SQL 쿼리를 직접 작성하고 Dao를 이용해 수동으로 매핑하는 방식으로 진행했다.
ORM 프레임워크 중 Hibernate 가 표준이기도 하고 학습이 목적이었으므로 JPA를 적용하기로 하였다.

JPA을 사용하며 SQL문을 만드는 리소스가 적어졌다.(필요하면 @Query 문을 추가했다. <- 최대한 지양)
비즈니스 로직 구현에만 집중할 수 있기때문에 작업 속도가 향상되었다.

추후 DB가 추가된다고 해도 ORM을 사용하기 떄문에 확장성에도 용이하다는 장점이 있었다.

 

JPA Projection 적용

프로젝트의 규모가 커지다 보니 데이터를 뿌려줄 때 필요 없는 데이터까지 뿌려주면 성능상 좋지 않을 것이라 생각했다.

JPA를 파보던 중 Dto-Projection(Closed Projections)을 알게 되었고 원하는 데이터만 받아서 쿼리 최적화를 해준다면 DB병목도 해소되고 트래픽도 줄어들 것이라 생각하여 Dto-Projection를 적용하게 되었다.
(성능을 측정하기엔 너무나 명확하게 보이기도 하고 성능측정 하는 방법은 후순위라 판단하여 측정해보진 않았다.)

 

Flyway 마이그레이션 도구 적용 & DB 관리

DB의 경우 DB통합 툴인 DBeaver를 통해 관리하였다.
DB는 PostgreSql을 사용했는데 기존 학원에서 Mysql과 Oracle을 다뤄봤고 학습을 위해 좀 더 다양한 DB를 써보고자 선택하였다.(무료)

docker-compose 이미지로 PostgreSql을 사용하기 때문에 SQL문을 따로 쓸 수 있는 방법이 없고 DBeaver로도 가능하긴 하지만 Flyway를 사용해 도메인 별로 나누어 형상관리(DDL)를 하였다. (DML책임 = JPA)

직관적으로 DB목록과 칼럼을 확인할 수 있어 ERD-Cloud를 왔다 갔다 하며 확인하지 않아도 확인할 수 있어 작업이 용이해졌다.

Flyway DDL, DML

 

2차 프로젝트 BE 적용사항

Notion: 프로젝트 구조보기(클릭)

 

Hexagonal Architecture

프로젝트 모음 워크스페이스 (BE)

worried-parrotfish-2f5.notion.site

Monolithic -> MSA & Hexagonal 아키텍처 적용

기존 프로젝트의 규모나 확장성을 봤을 때 서비스를 하는 프로젝트가 아니지만 근래 기업에서도 MSA도입을 위한 과도기라고 생각이 들어 학습을 위해 모놀리식 -> MSA를 적용해 보기로 했다.

헥사고날 아키텍처의 경우 구현부 코드가 늘어난다는 단점 있고 모놀리식에서 헥사고날은 적합하진 않지만 MSA에서 장점이 명확하기에 MSA & 헥사고날 조합으로 2차 프로젝트를 시작하기로 했다.

프로젝트를 하다 보면 요구사항은 매번 동적으로 바뀌게 된다.
이 프로젝트만 하더라도 설계만 7번 넘게 새로 만들고를 반복했다.
(이러한 작은 프로젝트에서도 변경이 많은데 실무에서는 더 많은 비즈니스 요구사항이 더 많을 것이라 생각된다.)

헥사고날의 장점은 다른 코드에 일절 영향을 주지 않으면서 변경이 생기는 부분에 대해서만 개선하여 깔끔하고 쉽게 관리가 가능하다.
(초기구성 때는 코드량이 많지만 만들고만 나면 어댑터만 바꾸면 되기 때문에 관리가 쉬워진다는 장점이 있다.)
또한, 이 프로젝트에서는 JPA Entity와 Domain Model을 나누어서 관리하는 방식으로 도입했다. 이로써 DBA의 자유도를 보장할 수 있고 DB 스키마 구조에 관계없이 소스 코드 작성은 어댑터 빼고 유지가 가능하기 때문에 유용하다.

MSA마다 담당하는 리소스 데이터들에 대한 관리 책임을 온전히 가져가고, 데이터 베이스도 다 나뉘는 게 원칙이지만 초창기에는 DB를 통합하여 관리하되, 점진적으로 디비를 나눠가는 것으로 정했다.
(대신 초기방식을 유지하는 동안 모든 서버가 재배포되어야 한다.)
추가로 아키텍처 패턴도 여러 가지 도입도 가능하다.

 

Simple CQRS 패턴 적용

컨트롤러에서 API가 많아지다 보니 조회(R)를 하는 매서드와 명령처리(CUD)하는 메서드의 위치를 파악하기 힘들었다.
(팀원들과 협업을 하다 보니 복잡도가 증가해 유지보수를 쉽게할 수 있는 방법을 찾게 되었다.)

MSA를 하다보니 분산 데이터관리를 위한 CQRS 패턴과 이벤트 소싱을 알게 되었다.
궁극적으로 DB의 성능을 개선하기 위한 CQRS의 목적이 더 크지만 이번 프로젝트에서 DB(SQL)를 늘리고 이벤트 소싱까지 적용하기에는 팀원의 JPA, 헥사고날에 대한 학습적응이 힘들 것 같았다. 시간적으로도 부족해서 너무 아쉬웠지만 추후 적용을 위해 책임만 나누는 선에서 컨트롤러를 나누었다. (향후 기회가 된다면 이벤트 소싱 적용 및 DB도 추가할 예정이다.)

적용 후  책임이 명확하게 나누어져 코드량이 조금 늘어난다는 단점이 있지만 유지보수 관점에는 오히려 편해졌다.

Gradle Api 사용

api의 경우 비권장되는 방법이지만 커스텀한 통합 flyway모듈을 사용하려면 반드시 flyway core 모듈도 함께 implementation 해야 하는 관계이기 때문에, 둘을 따로 관리하도록 하는 것보다는 api로 제공했다.

통합 flyway 모듈은 이 모듈을 이용하는 모든 서비스에 동일한 버전의 flyway core를 제공하는 게 바람직하고
통합 flyway 모듈 따로 flyway core 따로 implementation 하는 게 오히려 위험하고, 그냥 flyway core 버전의 관리 주체를 통합 flyway 모듈로 하는 효과도 있다.
(현재 프로젝트에서는 버전을 명시하지않고 디펜던시 매니저가 해 주는 걸로 했지만, 만약 버전을 명시하는 쪽으로 관리하는 경우가 생겨도 통합 flyway 모듈을 사용하는 서비스 모듈은 모두 동일한 버전을 제공받게 된다.)

이러한 이유때문에 통합 flyway모듈에서 core를 api로 제공했고 다른 모듈은 커스텀한 통합 flyway 모듈 하나만을 바라보기떄문에 오히려 버전 관리도 쉬운것 같다고 느꼈다.

추가적으로 API 노출을 최소화하고 의존성을 잘 격리하는 것이 좋은 설계라고 공부했지만, api 대신 implementation 쓰라고 하는 이유를 더 정확히 이해해보는 공부가 필요해 보였다.
(api 대신 implemenation 쓰라는 건 건너건너에 있는 불필요 모듈까지 직접 이용하는 일이 없도록 캡슐화된 기능만 이용하도록 하는 건데
이 경우는 왜 implementation이 오히려 부적절하고 api가 적절한지에 대한 학습이 추가적으로 필요하다 느꼈다.)