본문 바로가기
🖌 DevLog/- Best Of the Best 활동기

[Best Of the Best 활동기] 1차 팀 프로젝트 후기 - 프론트엔드를 개발하며 했던 고민들

by Wonit 2021. 8. 25.

오늘은 BoB 10기 보안제품개발 트랙의 보안 솔루션 제작 수업에서 한달동안 진행한 1차 팀 프로젝트에 대해서 이야기해보려 한다.

해당 글은 총 5부작으로 팀 빌딩과 협업 과정 그리고 서비스 설명 및 개발 과정 으로 나뉘어져 있습니다.

프로젝트 github 바로가기 -> 보안 위협 트래픽 분석 솔루션 github

 

GitHub - dhslrl321/L7-monitor: 🔐 L7 에서 동작하는 Access Log 기반 트래픽 분석 및 보안 위협 분석 서비스

🔐 L7 에서 동작하는 Access Log 기반 트래픽 분석 및 보안 위협 분석 서비스 (BoB 10 기 보안제품개발 1차 팀 프로젝트) - GitHub - dhslrl321/L7-monitor: 🔐 L7 에서 동작하는 Access Log 기반 트래픽 분석 및 보

github.com

 

지난 시간 우리가 했던 고민들과 문제들, 그리고 협업에 대한 내용을 정리해보았다.

 

오늘은 우리가 개발한 서비스를 좀 더 기술적인 관점에서 알아보려 한다.

 

개발하는 과정이 어떻게 진행되고 어떤 문제들을 만났으며 어떤 방법을 취했는지!

 

나는 Web 파트를 맡았기 때문에 내가 개발한 기능과 API에 대해서만 설명을 할 수도 있다 ㅎㅎ..

 

서비스 구조

 

우선 서비스의 목적은 지난 시간에 이야기했던 것 처럼 Access Log 기반 트래픽 분석 및 보안 위협 분석 이다.

 

사내의 Web Application 이 기존에 쌓아놓던 Access Log 를 기반으로 하여 분석하고 보안 위협을 시각화 해준다.

 

서비스의 큰 플로우는 다음과 같다.

 

서비스 플로우

 

  1. 고객사는 기존에 운영중이던 서버와 해당 서버가 저장하는 access.log 가 존재한다.
  2. 우리가 개발한 Agent 패키지를 설치한다.
  3. 해당 Agent에서 access log 의 위치를 지정하고 분석 서버로 access log 를 전달한다.
  4. 운영 서버의 성능을 위해서 분석은 다른 서버에서 진행한다.
  5. 분석 서버로 들어온 access log 는 분석을 진행하여 공격 유형에 따라서 DB에 저장한다.
  6. 해당 DB 의 값들을 토대로 보안 레벨을 산출하고 각종 시각화에 필요한 데이터를 정제하여 Front로 반환한다.
  7. Front Server는 Backend 에게 받아온 데이터를 가지고 대시보드에서 시각화를 수행한다.

 

원래 처음 기획은 SaaS 형태였기 때문에 분석 서버는 우리가 운영하는 서버여야 하지만 시간적 문제로 인해 On-Premise 형태로 구성하는 방향으로 진행했다.

 

즉, Client는 기존에 운영중이던 서버 + 분석 서버를 운영하는 방향인데, 이게 조금 서비스 관점에서 많은 의문과 비효율이 존재한다.

 

어쩔 수 없다고 타협했지만 아직 많이 아쉽다..

 

프론트엔드의 기술적 고민

 

앞서 이야기했듯, 우리는 Front End를 React로 개발하였다.

 

이 과정에서 했던 고민들은 크게 3가지 이다.

 

  1. HTML Rendering Server 의 결정
  2. 리액트의 state 관리 도구
  3. 통신 모듈의 선택
  4. 디자인 패턴의 도입

하나씩 알아보자!

HTML Rendering Server 의 결정

 

Front End를 처음부터 React로 하려던 생각은 없었다.

 

처음에는 순수 HTML과 Vanila Javascript 를 이용할 생각이었다.

 

그리고 여러 기술 조사를 했을 때, 간단히 들었던 생각은 바로 Express.js를 이용해 static file render 서버를 구축하는 것이었다.

 

Expressjs 는 Template Engine 프레임워크이기 때문에 html render server 로사용하기 위해서 몇 가지 작업을 해줘야 한다.

 

우선 app.js 에서 express.static() 을 이용해서 static 파일을 렌더하도록 구성했다.

 

import express from "express";

import path from "path";
const __dirname = path.resolve();

const app = express();
const port = 3000;

const setRenderPage = (filename) => __dirname + "/views/" + filename;

app.use("/script", express.static("script"));
app.use("/static", express.static("static"));

app.get("/", (req, res) => {
  res.sendFile(setRenderPage("index.html"));
});

app.listen(port, () => {
  console.log("server started on " + port);
});

 

이 방법에는 한 가지 문제가 생겼는데, 바로 css, images, js 파일을 각각 render 할 url 을 열어줘야 했었다.

 

그래서 <head> 태그 내부에 url routing path 를 생각하면서 static file 을 반환했지만, 이는 생산성을 저해시키는 것으로 보였다.

 

이건 그래 해결 가능한데, 문제는 바로 es6 에서 Module을 Bundling 하는데 정말 힘들었다.

 

예를 들면 es6 이전에는 모듈을 가져올 때, const express = require('express') 와 같은 방식을 이용했지만 es6 이상은 import express from 'express' 형태로 사용하기 때문에 이들 사이의 패러다임 불일치?를 해결했어야 했고, 각각 따로 번들링을 하는 방법이 필요했다.

 

전문 Front 개발자가 아닌 나에게는 조금 어려웠다..

 

그래서 생각해낸 방법이 바로 Webpack 이었다.

 

 

웹팩은 모던 js를 위한 static 모듈 bundler 라고 한다.

 

간단하게 웹팩에 대해서 이야기하자면, main.js 파일을 만들어서 하나의 main.js 에 우리가 만든 다양한 모듈들을 넣고 main.js 하나만 url 로 개방해주는 방법과 비슷한것 같았다.

 

그래서 앞서 발생했던 문제들은 더 깔끔하고 현대적인 방법으로 해결할 수 있었다.

 

사실 리액트도 웹팩을 이용하고 있고 나도 웹팩에 대한 언급은 들어봤지만 깊게 공부해본 적이 없었는데, 이제 웹팩이 무엇을 하는 기술인가? 에 대한 답을 어느정도 할 수 있게 되어서 매우 신났었다 ㅎㅎ..

 

자세한 사항은 기술 고민을 정리한 github에서 확인할 수 있습니다 -> Webpack 으로 Front-Server 구성하기

하지만 이 마저도 완벽한 기술 선정은 아니었다.

 

나는 Vanila js 로 하나의 웹을 개발해본 경험이 없었기에 만약 프론트 개발 팀원이 도움을 요청할 때, 큰 도움을 줄 수 없을것 같았다.

 

리액트에서는 data fetching 을 하면 state를 바꿔주어 렌더링을 수행했지만, 바닐라 js 에서는 직접 document 객체를 이용해서 dom 을 조작해야 했고, ajax 통신에서도 어떻게 렌더를 바꿔야 하는지 확실한 이해가 부족했었다.

 

간단히 게시판 정도는 개발할 수 있어서 머리속으로는 어떻게 해야겠다! 싶지만 막상 규모가 있는 앱을 개발할 때, 발생할 문제를 해결할 수 있는 자신감이 부족했다.

 

정말 다행인 것은 프론트를 맡은 팀원이 리액트 경험이 있다고 하여 리액트로 하는 것은 어떻겠냐는 의견을 내었고, 긍정적으로 받아들여줘서 우리는 프론트를 리액트로 구성하기로 하였다.

 

결국 팀원들의 양해를 구해 리액트로 변경!

 

지난 내 경험에서는 직접 Design과 Style 개발을 했었지만, 이번 프로젝트에서는 Backend 에서 해야할 일들이 조금 있었고, 내가 인프라까지 구성을 했어야 했기 때문에 디자인 쪽은 아예 할 수 없었다.

 

또한 UI를 전문적으로 공부했던 팀원들이 없었기에 React Bootstrap을 이용하기로 했다.

 

 

나는 Bootstrap 을 한 번도 사용해본 적이 없었기에 프론트를 맡은 팀원이 Bootstrap 을 만져보며 우리에게 필요한 Chart 들과 컴포넌트를 따로 빼서 모듈화를 해 주었다.

 

그리고 프로젝트 Repository 에서 create-react-app 을 이용해서 기본 Building 을 하고, 해당 컴포넌트들을 가져와 사용을 했었다.

 

리액트의 state 관리 도구

 

React 의 큰 특징 두 가지를 이야기 하자면 바로 State와 Props 이다.

 

Props와 State는 일반 Js 객체로 컴포넌트를 렌더링할 때의 영향을 주는 데이터를 의미한다.

 

state는 하나의 컴포넌트 내부에서 사용되는 변수형 데이터인데, 해당 state를 이용해서 동적인 데이터를 결정하고 컴포넌트 UI에 삽입해서 렌더링한다.

 

이 과정에서 state는 하나의 컴포넌트 내부에 위치하여 컴포넌트 렌더 트리를 잘 생각해서 컴포넌트를 구성해야 한다.

 

이런 특성을 잘못 이용한다면 원하는 결과가 잘 렌더링되지 않게 되는데, 이를 제대로 사용하기란 조금 까다롭다.

 

그래서 이 문제를 해결하는 방법으로는 다양한 선택지가 있었다.

 

  1. 순수 State + Props 이용하기
  2. 전역 State 관리 도구 이용하기 (Mobx, Context API, Redux)

 

순수 State + Props 이용하기

 

순수 state를 사용한다면 컴포넌트 렌더 트리의 구조를 잘 파악하고 적절히 props로 내려줘야 한다.

 

그래서 코드가 자칫 잘못하면 좀 더러워지거나 가독성이 나빠진다.

 

전역 state 관리 도구 이용하기

 

현재 개인적인 프로젝트에서 프론트엔드를 redux와 redux-thunk 를 사용하고 있다.

 

확실히 순수 state와 props 를 사용할 때 보다 다양하고 더 쉬운 개발이 가능하지만 환경을 구성하기 까지 조금 시간이 걸리며, 무엇보다 이 생각이 앞섰다.

 

과연 우리 서비스에서 Redux가 필요할까??

 

정답은 No 이다.

 

사실 동적 Data Fetching 도 없고 단지 컴포넌트의 Depth 도 깊어봤자 5개? 정도였다.

 

그래서 Redux는 과김히 Pass 하고 순수 state와 props와 useState 훅을 주로 이용하기로 하였다.

 

통신 모듈의 선택

 

브라우저에서 Ajax 를 하기 위해서는 내가 사용했던 통신 모듈은 2가지 이다.

 

  1. Fetch API
  2. Axios

 

둘 다 Ajax 의 Request 를 추상화한 모듈로 개인적으로 Axios를 더 많이 선호한다.

 

그 이유는 몇 가지가 있는데,

 

  1. Promise 처리가 쉽다
  2. 코드가 깔끔하다
  3. json 처리가 더 쉽다
  4. 경험이 더 많다 ㅎㅎ

 

 

결국 Axios 를 이용하기로 하였고, 다음과 같이 API 모듈을 구성했다.

 

import axios from "axios";

export const SERVER = axios.create({
  baseURL: "http://" + process.env.REACT_APP_HOST_IP,
  headers: {
    "Content-Type": "application/json",
  },
});

export const TEST_SERVER = axios.create({
  baseURL: "http://localhost:8080/api",
  headers: {
    "Content-Type": "application/json",
  },
});

 

SERVER.js 라는 파일을 만들고 위와 같이 서버의 호출 ip를 넣어서 axios instance 를 생성한다.

 

그리고 각각의 관심사에 맞게 민든 Service.js 파일에서 axios instance 를 import 하여 사용하는 방법이다.

 

import { SERVER, TEST_SERVER } from "util/SERVER";

export const fetchAllLog = async () => {
  const { data } = await SERVER.get("/api/logs/all");

  return data;
};

export const fetchUnknownLog = async () => {
  const { data } = await SERVER.get("/api/logs/unknown");

  return data;
};

 

디자인 패턴의 도입

 

나는 디자인 패턴은 소프트웨어 개발에 있어서 매우매우매우 중요하다고 생각하는 사람이다

 

그래서 간단하다고 볼 수 있는 이번 프로젝트에서도 가장 먼저 디자인 패턴을 고민했었다.

 

내가 프로젝트에서 경험했던 디자인 패턴은 크게 2가지였다.

 

  1. Container-Presenter Pattern
  2. Atomic Design Pattern

 

Atomic Design Pattern 은 원소가 되는 컴포넌트를 재사용하는 데에 특화되어 있는데, 우리는 사실상 Single Page 로 운영되고 Routing 조차 하지 않기 때문에 결국 Container-Presenter Pattern 을 사용하였다.

 

앞서 이야기했던 통신 모듈과 Service 를 Container 에서 받아와 그 아래의 Presenter 들에게 Props 로 전달해주게 된다.

 

 

아래 코드는 실제 Log 를 테이블 형식으로 렌더하는 컴포넌트 에서 사용하는 Container 코드이다.

 

import React, { useEffect, useState } from "react";

import LogTable from "components/presenter/LogTable/index";

import { fetchAllLog } from "service/LogService";
const LogTableContainer = () => {
  const [logDatas, setLogDatas] = useState([]);

  useEffect(async () => {
    const data = await fetchAllLog();
    setLogDatas(data);
  }, []);

  return <LogTable logDatas={logDatas} />;
};

export default LogTableContainer;

 

먼저 해당 컴포넌트가 브라우저에 로드되는 순간 API 호출을 시도하게 하려고 useEffect 내부에서 data fetching 을 한다.

 

받아온 데이터를 <LogTable> 의 props 로 넘겨주면, 다음과 같이 적절히 자식 컴포넌트로 내려주면서 자연스럽게 UI를 렌더링하게 된다.

 

 

이런 구조로 Front End React Application 은 구성되어 있다.

 

 

사실 이 구조가, 프론트를 리액트로 선정한 이유 조차가 타당하지 않을 수 있다.

 

또한 기술 자체의 사용법도 올바르지 않은 방향으로 구현되어있을 수 있지만 나름 1달이라는 짧은 기간동안 유용하고 꽤나 구조적으로 잘 구성하여 협업을 진행했었다.

 

프론트엔드는 단순히 취미 쯤으로 생각했던 지난의 경험들이 이렇게 빛을 낼 수 있다는 사실이 뿌듯했고, 나름 자랑스러웠따 ㅎㅎ.

 

다음 시간은 백엔드의 Spring Boot 에 대해서 이야기해보려 한다!

 

댓글0