본문 바로가기

웹프로그래밍/부트캠프 - Codeit

Medium 클론 프로젝트 분석

728x90

이 글은 블로그 사이트인 (medium.com)의 클론 사이트를 분석한 내용과 느낀 점을 정리한 글입니다. 실무에서 프론트엔드와 백엔드 코드가 어떻게 작성되는 지 살펴볼 수 있었습니다.

프로젝트 개요

이 프로젝트는 Medium.com의 핵심 기능을 구현한 풀스택 웹 애플리케이션입니다. 주요 기능은 다음과 같습니다:

  • JWT 기반 사용자 인증 및 회원가입
  • 게시글 CRUD 및 댓글 관리
  • 페이지네이션을 통한 효율적인 데이터 로딩
  • 게시글 저장(북마크) 및 팔로우 기능
  • 마크다운 기반의 게시글 작성 및 렌더링

프론트엔드 프로젝트 분석

링크: https://github.com/gothinkster/react-redux-realworld-example-app?tab=readme-ov-file

1. 라우터 구조 매핑

프론트엔드 프로젝트에서는 Redux를 이용하여 라우터 구조를 매핑하였습니다. 이는 최근에 쓰이는 Next.js와는 다른 라이브러리를 쓰였음을 알 수 있습니다.

import article from './reducers/article';
import articleList from './reducers/articleList';
import auth from './reducers/auth';
import { combineReducers } from 'redux';
import common from './reducers/common';
import editor from './reducers/editor';
import home from './reducers/home';
import profile from './reducers/profile';
import settings from './reducers/settings';
import { routerReducer } from 'react-router-redux';

export default combineReducers({
  article,
  articleList,
  auth,
  common,
  editor,
  home,
  profile,
  settings,
  router: routerReducer
});

2. 컴포넌트 구조 분석

개인적으로 느꼈을 때는 Component 단위를 세세하게 나눈 느낌이 들진 않았습니다. 따라서 Atom 파일 구조가 아닌 Molcule 정도의 구조를 가졌다는 느낌이 들었습니다. 아무래도 Atom 구조로 할 경우, 유지보수에 문제가 생길 수 있어, 이 정도로 나누지 않았나 생각합니다.

모든 컴포넌트는 Article, Home과 그 이외의 그룹으로 구분되며, 각각의 작은 컴포넌트로 구분되고 있었습니다. 또한, Article과 Home에 해당하는 부분에는 페이지에 해당하는 소스코드들이 존재함을 알 수 있었습니다.

3. 인사이트 공유

클래스형 Component를 사용하여 예전 문법을 사용하여, 예전 문법을 사용한다는 점을 알 수 있었습니다. 그렇다 보니 Deprecated된 문법이 많았다는 것을 느꼈습니다. 하지만, Full-Stack의 관점에서 Node.js를 사용하고 React (프론트엔드)와 Express (백엔드)를 사용하는 오픈소스 프로젝트를 찾다보니 어쩔 수 없었다는 생각이 들었습니다. 참고로 프론트엔드 프로젝트는 2021년 이후 저장소가 Archived 되어 있었습니다.

agent.js에는 함수를 네임스페이스 구조 패턴으로 불러오는데, 이는 가독성에 좋은 패턴이라 생각듭니다. 특히나 하나의 파일에서 여러 개의 함수를 로딩하는 경우가 많은데, 이런 패턴으로 로드할 경우에 코드가 깔끔해지고 가독성이 올라가는 효과를 느낄 수 있었습니다. 다만, 이 경우 export하는 함수의 파일 하단에 묶어 줘야 한다는 점은 단점으로 작용할 것 같습니다.

백엔드 프로젝트 분석

링크: https://github.com/gothinkster/node-express-realworld-example-app

1. 데이터 모델링

백엔드는 PostgreSQL과 Prisma ORM을 사용하여 데이터를 관리합니다. 특히 User, Article, Comment, Tag 간의 관계가 명확하게 정의되어 있습니다:

model User {
  articles   Article[] @relation("UserArticles")
  favorites  Article[] @relation("UserFavorites")
  followedBy User[]    @relation("UserFollows")
  following  User[]    @relation("UserFollows")
  comments   Comment[]
}

이 구조에서 눈여겨볼 점은 자기 참조 관계(self-referential relation)를 활용한 팔로우 기능입니다. followedBy와 following 필드를 통해 사용자 간의 관계를 효과적으로 표현했습니다.

2. 이 프로젝트의 특징

이 프로젝트는 계층형 아키텍처로써, 다음과 같은 흐름으로 구성되어 있습니다:

main.ts → routes.ts → Controller → Middleware(auth) → Service → 응답 반환

Controller는 요청을 받아 Service로 전달하고 Service는 비즈니스 로직을 처리합니다. 이러한 계층 분리는 코드의 책임을 명확히 하고 테스트를 용이하게 하는 장점이 있습니다.


이 프로젝트는 이중 인증 미들웨어 전략을 채택하고 있습니다:

const auth = {
  required: jwt({  // 로그인이 필수인 엔드포인트
    secret: process.env.JWT_SECRET || 'superSecret',
    getToken: getTokenFromHeaders,
    algorithms: ['HS256'],
  }),
  optional: jwt({  // 로그인이 선택적인 엔드포인트
    secret: process.env.JWT_SECRET || 'superSecret',
    credentialsRequired: false,
    getToken: getTokenFromHeaders,
    algorithms: ['HS256'],
  }),
};

이 패턴은 실용적인 패턴이라 할 수 있습니다. 예를 들어, 게시글 목록은 비 로그인 사용자도 볼 수 있지만, 로그인한 사용자는 좋아요 등의 표시를 할 수 있는데, auth.optional 미들웨어를 사용하여 토큰이 있을 때만 사용자의 정보를 추출하고, 없을 경우에도 요청을 진행할 수 있습니다.

이렇게 하지 않은 경우에는 로그인을 했을 때와 로그인을 하지 않았을 때를 구분하여 별도로 구현하거나 하는 불편함이 있었습니다. 이런 패턴을 통해 이런 불편함을 해결하고 코드의 효율화를 가져왔습니다. 이 점은 배울만한 점이라 생각합니다.


이 프로젝트는 도메인별로 폴더를 구성하여, 도메인과 관련된 코드를 한 곳으로 모았습니다:

├── article/
│   ├── article.controller.ts
│   ├── article.service.ts
│   └── article.types.ts
├── user/
│   ├── user.controller.ts
│   ├── user.service.ts
│   └── user.types.ts
└── comment/
    ├── comment.controller.ts
    └── comment.service.ts

이런 구조는 특정 기능을 찾거나 수정할 때 관련 파일을 쉽게 찾을 수 있게 해줍니다. 다만, 각 도메인의 함수를 개별적으로 import하는 것보다는, 네임스페이스 객체로 묶어서 import하면 가독성이 더 좋아질 것 같습니다:

// 현재 방식
import { createArticle, updateArticle, deleteArticle } from './article.service';

// 개선 방식
import * as ArticleService from './article.service';

ArticleService.createArticle(...);
ArticleService.updateArticle(...);

실무에 적용할 교훈들

이번 프로젝트 분석을 통해 얻은 핵심 인사이트를 정리하면 다음과 같습니다:

  1. 상태 관리의 모듈화: Redux의 reducer를 기능별로 분리하여 관심사를 명확히 구분
  2. 이중 인증 전략: 선택적/필수 인증을 구분하여 유연한 API 설계 가능
  3. 도메인 중심 설계: 폴더 구조를 도메인별로 구성하여 코드 탐색성 향상
  4. 계층형 아키텍처: Controller-Service 구조로 책임을 명확히 분리
  5. 레거시 코드 이해: 오래된 패턴을 이해하는 것도 중요한 실무 역량

제가 수행하는 프로젝트에서는 이러한 장점을 살리되, 최신 기술 스택(React Hooks, TypeScript 타입 안정성 강화, 입력 검증 미들웨어 추가)을 활용하여 더 현대적이고 안전한 애플리케이션을 만들어보려고 합니다.

728x90