Node 에서 Java spring 로의 리팩토링 기록 ( NoSql 에서 Sql로)

2024. 3. 14. 23:09자바/자바스프링

728x90
반응형

 

 

#1. 리팩토링 전

1. SW구조적 문제점

💡 Monorepo / 모듈 단위 개발

  • 초기엔 특정 프론트엔드 개발자와 백엔드 개발자가 같이 협업하여 단일 모듈을 맡아 개발하였다.
    • monorepo 구조가 이런 특징에 있어서 빠른 속도로 개발하기에 적합하였지만, 신규 인원이 많아지고, 코드의 양이 많아지며, 고도화가 필요한 시점에서 더 이상 monorepo 구조가 적합한 구조가 아니게 되었다.
    • 단일 repository 에 api, db, 백엔드, 프론트엔드 코드가 전부 포함되어있고, 또한 각자 자신만의 모듈을 맡아 개발을 하다보니, 코드의 방대함, 코드의 통일성 부재 등으로 인한 가독성 저하 문제가 유발되었다.
  • 또한 인과관계가 반대로지만, 백엔드 개발에 있어서 node.js 의 단점 (추후 설명)에 의해 백엔드 메인 언어를 java spring boot로 설정하였기에, 해당 언어로는 더 이상 monorepo로 개발하기 힘들어졌다.
  • CI/CD 측면에서는 모노레포 구조 자체가 쿠버네티스 내에 컨테이너 서버로 담기엔 적합한 전략이 아니다.
    • 모노레포 구조 자체가 monolith 한 구조의 service다 라고 보긴 어렵지만, 구조적으로 모노레포 구조를 MSA 구조로 변환시키기 어려운 문제가 있다.

2. DB 구조의 문제점

💡 MongoDB

  • 초기 MongoDB를 사용함으로써 자유로운 스키마 구조를 사용할 수 있다는 이점을 가지고 보다 빠른 개발을 진행할 수 있었으나, 데이터의 신뢰도, 일관성가 중요한 ERP 시스템 도메인 특성상, ACID 성질이 보장되는 트랜잭션이 제한적인 NoSQL은 우리의 개발에 있어서 적합하지 않았다. 제한사항:
    1. 단일 데이터베이스 내에서만 트랜잭션 지원
    2. 분산된 컬렉션 간의 트랜잭션 제한
    3. 몇 가지 특수한 쿼리에서의 제한
    4. MongoDB 드라이버의 지원 필요
    5. 일부 제한된 환경에서의 제한
  • 제한적인 트랜잭션 처리로 인해 ACID 를 온전히 보장해주지 못한다는 의미는, 직접 로직적으로 트랜잭션 처리에 대한 고려를 해줘야한다는 것이다.
    • 그러나 이때 node.js 에 비해서 java spring boot 는 트랜잭션 처리를 어노테이션을 통해 간단히 해줄 수 있다. (연쇄적으로 파생되는 언어의 문제점)
  • 또한 mongoDB는 JOIN 등 연산을 지원하지 않아서, 개발자가 원하는 데이터의 추출 작업을 기존에는 서버 측에서 처리를 자주하였다.
    • 이는 코드의 길이를 증가시키고, 서버사이드에서의 작업 부담을 늘린다.

3. 개발 언어적 문제점

💡 node.js / typescript

  • 노드는 가볍다는 장점으로 초기 빠른 개발과 실행 속도, 그리고 프론트엔드와의 언어적 통일성 등으로 빠른 개발 사이클을 가져갈 수 있게끔 도와줬다.
  • 그러나 노드는 단일 스레드로 동작하기 때문에 CPU-bound 작업에 대한 처리가 상대적으로 느릴 수 있다.
    • 데이터의 규모가 커질 수록 이는 단점이 될 수 있다.
  • 그리고 백엔드 개발 생태계적으로, (특히 국내에서) java spring boot는 node 보다 긴 역사를 가지고 있어, 백엔드 개발에 있어서 더욱 도움이 되는 모듈들을 제공한다.
    • 위에서 설명한대로, node.js는 트랜잭션 처리를 할 수 있지만, java spring boot에 대비하여 편하게 쓰긴 무리가 있다.
      • 콜백함수를 사용해야 하는데, 이는 코드의 길이를 기하급수적으로 길게 만든다.
    • validation 처리에서도 node.js에서 express-validator 모듈이 제공을 하여서 validationResult함수를 통해 진행할 수 있지만, 필드 규칙을 정의하는 부분에 있어서 각 API 마다 별도 지정을 해줘야하는 것이 번거로울 수 있다.

#2. 리팩토링 과정

1. 적용 기법

1) 구조적 변화

💡 Multi-repo / 기술 단위 개발

  • 백엔드팀은 언어적, 구조적으로 프론트엔드로부터 분리되어서, 개별적인 repository 환경에서 개발이 진행되었고, 또한 팀 단위 개발로 점진적으로 탈바꿈하였다.
  • 해당 repository에 코드를 커밋할 때, 이슈와 pr를 통한 머지 방법을 통해 진행이 되고, 이를 통해 util 등의 개발이 있을 때 팀 멤버에게 공유하여, 각자 구현한 부분을 재활용하며 쓸 수 있도록 하였다.
    • 또한 백엔드 코드만 바라보기 때문에, repository 코드가 과도하게 방대하지는 것을 막을 수 있다.
  • 이는 코드의 통일성과 코드의 중복을 막기 위해 중요한 포인트였고, 가독성을 높이며 결과적으로 개발의 생산성 증가를 유도하였다.

2) DB의 변화

💡 NoSQl → SQL -- PostgreSQL

  • 트랜잭션을 지원하여 데이터베이스의 안전성과 신뢰성을 보장해주는 postgreSQL을 사용함으로써, VC ERP 시스템을 이용하는 client 들에게 큰 신뢰를 안겨줄 수 있게 되었다.
  • 또한 SQL 언어를 사용함으로써 복잡한 JOIN 연산을 할 수 있게 되었다.
    • 다양한 API 를 구현함에 있어서 같은 테이블이더라도 각각 다른 조건과 형태의 데이터를 요할 때가 있는데, 이는 개발자에 따라 가지각색의 쿼리를 작성해 자신이 원하는 데이터를 DB로부터 뽑아낼 수 있게되었다.
    • 이로써 서버에서 로직적으로 처리하던 데이터 추출 작업에 대부분을 DB 쪽으로 넘길 수 있게 되었다는 것이다.
    • 추후 myBatis에서 JPA로 넘어가게되면 이러한 장점이 극대화 되는 것을 기대할 수 있을 것이다.

3) 개발 언어의 변화

💡 Java Spring Boot

  • 자바는 컴파일 언어로써 컴파일 시 오류를 잡아 코드의 안정성을 높일 수 있다.
  • 노드와 달리 다중 쓰레드를 지원하여, CPU-bound job에 대한 처리를 더욱 효율적으로 처리할 수 있다. 이는 복잡한 서버사이드 비즈니스 로직을 해결하는 데에 있어서 도움이 된다.
  • java spring boot는 백엔드 개발에 있어서 더욱 도움이 되는 모듈들을 제공한다.
    • 트랜잭션 처리:
      • @Transactional(value = "VCTransactionManager", rollbackFor = ApiException.class)
      • 위와 같은 어노테이션과 try catch 통해 트랜잭션 처리를 수행할 수 있다.
    • validation 처리:
      • dto에 @Size(min=5, max=10) 와 같은 어노테이션으로 각 dto 클래스 속성에 대해 직관적으로 validation 규칙을 지정할 수 있고,
      • controller에서 request body 파라미터 앞에 @Valid 를 통해 쉽게 얻을 수 있다.

2. 도전 및 해결책

  • 기존에는 monorepo 와 모듈 단위 개발에 의해 프론트엔드 개발자와 백엔드 개발자에게 좀 더 밀접한 소통이 요구가 되었고, 같은 API interface를 공유하고 있었기에, 잦은 소통을 하며, API 명세서가 필요하지 않았었다.
  • 그러나 현재는 팀 단위로 개발하게 되고, 같은 API 코드를 바라보고 있지 않는다.
    • 따라서 API 명세서가 필요했고, 이는 스웨거로 채택이 되었지만, 개발 초기에는 전체적인 API의 service 로직 개발이 우선시 되었기에 API에 대한 정보가 부족했었다.
    • 이는 오히려 더 잦은 소통이 요구가 되었고, 결국엔 프론트엔드와 백엔드 양 측에 있어서 개발적 delay를 유발하였다.
  • 해결책
    • java spring boot 는 monorepo 와 친숙하지 않은 언어고, swagger와 친한 언어다. 즉 이는 많은 API에 대해서 부연설명를 돕는 모듈이 제공 되고 있다는 것이다.
    • GET 메서드의 response 에 대한 상세 설명은
      • @ApiResponse
    • POST, PUT 메서드의 request body 에 대한 상세 설명은
      • @ExampleObject
    • 어노테이션을 추가하여 해결하였다.
  •  

#3. 리팩토링 후

1. 코드 가독성 증가

  • 백엔드 단일 repository 와 커밋과 개발 방식의 변화로 인해 코드의 통일성을 얻을 수 있었고 이는 같은 백엔드 개발자끼리 서로의 코드를 쉽게 이해할 수 있도록 도왔다.
    • 이를 통해 코드를 쉬이 공유하게 만들며, 코드의 중복을 막을 수 있었다.
  • 또한 각 모듈에 대해서 책임자는 설정을 하되, 정, 부 책임자를 설정하여 각각에게 코드 리뷰를 받는 방식을 통해 코드의 가독성을 높이는 일을 필수불가결하게 제약하였다.

2. 데이터 안정성 확보

  • NoSQL 에서 SQL로의 변화로 인해, 트랜잭션 처리를 할 수 있었고, 이 덕에 데이터의 안정성과 신뢰성을 확보할 수 있었다.
  • 또한 ERP 시스템 특성상 데이터 자체의 가치가 중요하기에, 똑똑 용어 파싱 프로그램을 통해 정형화된 데이터를 확보할 수 있었고, 데이터의 가치를 높일 수 있었다.

3. 개발 생산성 증가

  • 코드의 통일성과 가독성이 높아지며, 코드를 유틸화 시켜서 다른 사람들이 만든 코드를 그대로 호출하여 쓸 수 있는 재사용성을 높이는 것은 결과적으로 개발의 생산성 증가를 유도하였다.
  • 또한 기존에 비해 비즈니스 로직에서 구현한 데이터와 인접한 로직들은 쿼리로 처리를 하여, 서비스단의 코드가 간단해졌다. 이는 이미 구현된 쿼리를 가져다 쓰면서 불필요한 서비스 로직 상의 중복을 줄이며 스스로의 복제를 막는 (DRY) 개발 방식이 많이 접목이 되어서, 개발의 생산성이 증가 될 수 있었다.
  • 즉, 다른 사람이 이미 구현했던 코드를 다시 구현하지 않는 점과 스스로 이미 구현했던 코드를 다시 구현하지 않는 점에서 개발의 생산성이 현재 언어적, 구조적 이점 하에서 도약될 수 있었다.

4. 성능 향상

  • JOIN 등 복잡한 쿼리 연산을 활용할 수 있기에 서버 측의 부담을 덜어낸다.
    • 즉 기존 서버에서 하던 작업을 DB 단으로 미뤄서, 서버의 작업 효율을 향상 시킨다.
  • java 라는 언어가 주는 멀티쓰레드의 이점을 통해 서버의 작업 효율성을 향상 시킨다.

 

 

728x90
반응형