새로운 블로그로 이전하였습니다!
article thumbnail image
Published 2024. 1. 31. 13:21

회사에 입사한지도 이제 만 2년이 되어간다.

블로그에 마지막 글을 작성한지 벌써 3개월이 지났다.

핑계를 대자면 요즘은 새로운 개인 공부로 코드를 작성하거나, 책을 읽거나, 위스키에 시간을 쓰고 있다.

그래도 이렇게 회고록을 남기며 글을 갱신해보려고 한다.

 

블로그의 조회수는 어느덧 15K를 넘겼다. 한참 블로그를 작성할땐 매일 조회수가 얼마나 올랐나 확인했었는데, 오히려 블로그 작성이 뜸해진 10월부터 조회수가 늘어났다.

프론트 엔드 개발자로 나아가고 있는데 대부분 조회수는 백엔드와 인프라 부분이다. 프론트엔드 관련 매력적인 글을 작성해서 벨런스를 맞춰야겠다.

 

새로운 프로젝트의 시작

2023년의 프로젝트는 내가 프론트엔드 개발자로써 코드를 작성하는 레벨을 바꿔준 프로젝트였다.

새로운 프로젝트는 사내 솔루션의 모니터링 기능을 제공하여 운영/관리 효율을 높이는 것이 목표였다.

 

프로젝트 인원 구성의 특징

새로운 프로젝트를 작성하기에 앞서, 프로젝트 인원별 역할의 특이한 점이 있다.

 

  1. 개발자가 기획도 하고 개발도 한다.

해당 프로젝트를 구현하려면 사내 솔루션을 직접 고객사에 배포해보면서,
외부에서 솔루션의 데이터를 어떻게 호출하는지, 내부 솔루션간 네트워크 통신은 어떻게 이루어지는지 등을 알고있어야한다.

비록 솔루션을 배포해본 경험은 한번 뿐이였지만, 일단 솔루션의 구조를 어느정도 파악하고 있었기에, 기획 단계에서 여러가지 방안을 제시할 수 있었다.

거기에 더해서, 다른 모니터링 솔루션의 아키텍처와 UI를 분석해보고, 고객사나 회사의 시니어 분들을 통해 평소 필요하다 생각하셨던 기능들을 조사해보며 기획을 진행했다.

 

  1. 프론트엔드와 백엔드를 구분하지 않는다.

개발자에게 클라이언트/서버에 대한 전반적인 도메인/프로젝트 지식 이해를 장려했던 것 같다.

난 프론트엔드 개발을 메인으로, 다른 개발자는 백엔드 개발을 메인으로 담당하였지만,
서로 작성한 코드를 상대에게 알려주고,
서로 작성해야할 부분을 공유하였으며,
서로 작성한 코드를 리뷰하며 진행하였다.

서로 어떤 업무를 수행하고 있는지 긴밀하게 공유하며 프로젝트를 진행해서, 각 FE, BE 대한 이해도가 높아질 수 있었던 것 같다.

 

프로젝트의 진행 방식

기본적으로 작은 규모의 기획, 이를 바탕으로 개발하는 방식으로 진행되었다.
순서대로 기술하자면,

 

  1. 구현할 기능을 작성한다.

큰 틀의 필요한 기획을 전달받으면, 그 안에서 세부적인 기능들을 정의한다.
주로 다른 모니터링 툴을 참고하여 세부 기능을 작성하곤 했었다.

 

  1. 디자이너와 UI를 작성한다.

기획을 바탕으로 화면 설계서를 작성하고, 디자이너와 공유하여 Figma 툴을 통해 완성된 디자인을 전달받는다.

 

  1. 바로 개발을 시작한다.

하지만 디자인 작업이 완료될 때 까지 기다릴 수는 없었다.
따라서 프론트엔드를 주로 담당했던 나는, 디자인 부분만 제외하고 기능 동작 구현을 위주로 바로 개발을 시작했다.

기능을 구현하면서 API 명세를 같이 만들고, 디자인이 오면 기능만 구현했던 컴포넌트에 디자인을 적용하는 방식으로 유기적으로 협업하며 개발을 진행했다.

 

그래서 뭘 만들어봤나?

  • 프론트엔드
    • Next.js -> Astro 프레임워크 도입
    • Vanilla JS 기반 사내 디자인 시스템을 React로 재구현
    • ECharts 라이브러리를 활용한 차트 스트리밍 대시보드 구현
    • React Query + Concurrent UI 패턴 도입
  • 백엔드
    • J2EE Servlet 기반 API 핸들러 구현
    • API 명세 작성
    • Enum 기반의 공통 API 응답 코드 정의
    • 일부 서비스 로직 구현 및 DB 쿼리 작성 등등..

 

그 중 가장 기억에 남는건?

 

Vanilla JS 기반 사내 디자인 시스템을 React로 재구현

막연하게 코드를 작성하던 시절에서, 구조를 고민하고 패턴을 적용하며 코드를 작성하게 된 계기가 되었다.

기존의 사내 디자인 시스템은 Vanilla JS로 작성되었고, Storybook을 통해 문서화하고 있었다.

하지만, 신규 프로젝트는 React를 도입하였다보니, Storybook과 기존 코드를 참고하며 React로 재구현해야 했다.

 

초기엔 막연하게 컴포넌트를 구현했더니, 새로운 요구사항이 생기면 기존에 만들어둔 구조를 변경하기 일쑤였고, 이를 사용하던 다른 곳에 영향을 주는 경우가 많았다.

컴포넌트가 다른 곳에서 어떻게 사용할지 예상하는 것은 쉽지 않았다..

그래서, 유명한 오픈소스 라이브러리를 찾아봐서, antd, mui를 참고하며 컴포넌트를 구현하였고, 문제가 완화되었다. 싶었으나,

그럼에도 특수한 요구사항이 생기게되고, 문제를 반복하고 있었는데..

 

그 때 도움이 되어준게 아래의 우테코 영상이였다.

[10분 테코톡] 네이브의 컴포넌트 IoC패턴

 

영상을 보고 바로 컴포넌트 IoC 패턴을 도입해봤다.

기본 컴포넌트를 구현하고, 내부에서 필요한 로직을 Custom Hook으로 분리하여 사용하는 방식이였다.

interface DrawerClose {
    onClose: () => void;
}

interface DrawerProps extends DrawerClose {
    isExpanded: boolean;
}

interface DrawerContext {
    drawerState: [DrawerClose[], Dispatch<DrawerClose[]>];
}

const drawerContext = createContext<DrawerContext>( {} as DrawerContext );

export default function Drawer( props: PropsWithChildren<DrawerProps> )
{
    const {
        isExpanded,
        children,
        onClose,
    } = props;

    const drawerRef = useRef<HTMLDivElement>( null );

    return <Modal type="drawer">
        { isExpanded && <>
            <>...기본 구성요소들</>
            {children}
            <Shortcut keyCode="Escape"
                event={onClose} />
        </>
        }
    </Modal>;
}

export const useDrawer = () =>
{
    const { drawerState } = useContext( drawerContext );

    const [isExpanded, setIsExpanded] = useState( false );

    const onOpen = () =>
    {
        if( isExpanded ) return;

        // ...open 로직
    };

    const onClose = () =>
    {
        // ...close 로직
    };

    return {
        isExpanded,
        onOpen,
        onClose,
    };
};

export const DrawerProvider = ( props: PropsWithChildren ) =>
{
    const drawerState = useState<DrawerClose[]>( [] );

    return <drawerContext.Provider value={
        {
            drawerState,
        }
    }>
        {props.children}
    </drawerContext.Provider>;
};

컴포넌트를 사용할 때, 추가적인 로직이 필요하면 Custom Hook으로 받은 메인 로직과 합쳐서 사용하면 되었다.

export default function App()
{
    const { isExpanded, onOpen, onClose } = useDrawer();

    const handleOpen = () =>
    {
        // ...추가 로직
        onOpen();
    };

    return <>
        <Button onClick={handleOpen}>Click Me</Button>
        <Drawer isExpanded={isExpanded}
            onClose={onClose}>
            <>...Content에 채워넣을 것</>
        </Drawer>
      </Button>
      </>;
}

Modal을 통하는 컴포넌트를 구현할 때가 가장 머리가 아팠는데, 위 패턴 적용 이후엔 어느정도 해결되었고,

이 때 부터 빅테크 기업의 기술 블로그와 영상, 오픈소스들을 찾아보기 시작했다.

 

React Query + Concurrent UI 패턴 도입

다양항 요구사항들을 처리하며 복잡해지던 코드 구조를 React Query와 Concurrent UI 패턴을 알게되고, 코딩 스타일이 많이 개선되었다.

 

관제센터에 띄우기 위한 목적이었기 때문에, 사용자의 조작이 없더라도 화면이 멈추면 안됐고, 고객사마다 다양한 환경으로 인해, 예상못한 에러를 핸들링할 수 있는게 중요했다.

기존에 Concurrent UI 패턴을 도입하기 전에는, React Query의 isLoading을 통해 로딩 상태를 관리하고, 에러 핸들링은 isError, error를 열어보거나, Response 받은 data에서 에러 메시지를 추출하여 사용하였습니다.

그러다보니 코드가 길어지고, 명령형으로 작성하고, 난잡해지는 코드가 많았다.

import { useQuery } from 'react-query';

function MyComponent() {
  const { data, isLoading, isError, error } = useQuery('myData', fetchData);

  if (isLoading) {
    return <Spinner />;
  }

  if (isError) {

    // API에서 가공된 error 코드/메시지를 줬을 때
    if( data?.error ) {
      return <UserFallback error={data.error} />;  
    }

    return <UserFallback error={error} />;
  }

  return <DataComponent data={data} />;
}

async function fetchData() {
  // 데이터 요청 로직
}

위에 처럼 작성하던 코드를 개선하던 과정이 있었는데, 관련해서 포스팅했었던 글을 남겨둔다.
Concurrent UI 패턴으로 개발하기

 

[React] Concurrent UI 패턴으로 개발하기

시작하며 프론트엔드 개발자는 웹 애플리케이션을 개발할 때 항상 사용자 경험에 대해 고민하게 됩니다. 사용자들은 웹 앱에서 빠른 응답성, 부드러운 애니메이션, 실시간 업데이트 같은 매혹

gomban.tistory.com

 

회사 DB에 OOM 발생시키기

아마 평생 잊을 수 없을 것 같다..

모니터링을 위해 기록된 로그성 DB 데이터가 너무 많이 쌓이니, 특정 주기마다 오래된 데이터를 삭제하는 로직을 구현했었다.

그것은 DELETE FROM TABLE WHERE DATE < ? 쿼리였는데.. 그땐 DB 트랜젝션에 대한 이해가 부족했었다.

DELETE 쿼리는 rollback을 위해 트랜잭션이 쌓이게 되었고, 트랜잭션의 수가 많아지자 여럿이 공유하던 DB에서 OOM이 발생한 것이다.

수석님들과 CIO님까지 모여서 원인을 찾기 시작했고, 그 원인이 내가 만들었던 쿼리라는 것을 알게 되었을 때.. 후회하긴 늦었었다 ㅎ..

덕분에 쿼리를 날리기 전에 한번 더 고민해보는 습관을 경험을 통해 몸소 배우게 되었다.

다시는 이런 사고를 치지 말자..

 

아쉬운 점

좀 더 고민하고 조사하고 개발을 시작했더라면 더 좋은 아키텍처를 적용할 수 있었을 것 같다.

현재 완성한 프로젝트를 되돌아보면, 좀 더 개선할 수 있는 부분이 많이 보인다.

빠르게 완성본을 보여주기 위해 할 줄 아는 대로 개발을 시작했고, 개발 중 새로 알게된 기술은 적용하기엔 시간이 부족했다.

가장 큰 아쉬운 점은 WebSocket을 적용할 수 없기에 polling을 택했지만, STOMP라는 프로토콜을 알았더라면 실시간 데이터 처리에 더 좋았을 것 같다는 점이다.

만약 리펙토링을 과제로 갖고가게 된다면 STOMP를 가장 먼저 적용해보고 싶다.

 

서비스 기업에 가고싶어졌다.

사용자의 직접적인 피드백을 보고 싶어졌다.

지금 회사의 제품은 고객사에게 판매하고, 고객사의 폐쇄망에 배포하는 구조였기 때문에, 사용자의 피드백을 직접적으로 받을 수 없는 환경이였다.

웹을 개발할 때 UX를 고려하며 코딩을 하지만, 사용자의 피드백을 받지 못하니 불편한 부분이나, 요구사항을 파악하기가 어려웠고,
사용자의 요구사항을 파악하지 못하니, 사용자가 원하는 기능을 구현하기가 어려웠다.

그때문인가 혼자 매너리즘에 빠져가는 느낌도 드는 것 같았다.

서비스업은 Mixpanel이나 Google Analytics 등 다양한 툴을 통해 사용자의 행동을 분석해보거나, 리뷰를 찾아볼 수 있다는 점이 서비스 기업에 가고싶게 만들었다.

좋은 리뷰를 보고 성취감을 느끼거나, 건의 사항을 받고 개선해나가는 과정을 경험해보고 싶다.

 

서비스 기업은 웹 접근성, SEO, 반응형 웹을 요구하지만, 다 나에겐 부족한 부분들이였다.

지금의 회사에선 모두 불필요하고, 개발 시간만 늘릴 오버 엔지니어링이였기 때문이다.

하지만, 서비스 기업을 목표한 이상 이런 역량들을 키워나가야만 한다.

 

이력서 웹사이트 만들기

그 역량을 키우기 위한 목표로 직접 이력서 웹사이트를 만들었다.

이력서 - 김도현

 

이력서 - 김도현

프론트엔드 개발자 김도현의 이력서입니다.

kdh-portfolio.vercel.app

기본 구조는 유명 구직 사이트의 템플릿을 참고하면서, 필요한 부분들을 추가하였고, 이력서를 PDF로 다운로드 받을 수 있도록 구현하였다.

익숙한 Tailwind를 사용하여 반응형 웹을 구현하였고, 시멘틱 태그와 aria, alt 속성을 적용하여 웹 접근성을 고려하였다.

다 만들고 보니 Lighthouse 올 100점이 나오고 폭죽 이펙트도 볼 수 있었다.

 

코딩 테스트 준비하기

코딩 테스트에 대한 준비가 하나도 안되어있었다.

과거 우연히 넣었던 기업에서 라이브 코딩 테스트를 보게되었는데, 문제를 하나도 풀지 못하고 끝마쳤다..

그 때 좌절감을 크게 맛보고 한동안 멘탈이 나가있었다..

라이브 코딩이 끝나고, 면접관께서 질문할게 있으면 하라고 하셨었는데, 라이브 코딩도 못풀고 질문은 뭐하러 그렇게 많이 했는지 모르겠다. ㅋㅋ;

코딩 테스트 연습했던 Github Repository

연습의 덕분에 최근엔 시리즈 C 기업의 코딩테스트도 무난히 통과할 수 있었다. (통과했지만, 면접은 ㅋ..)

 

마무리

다음 목표는 서비스 기업에 입사하는 것이다.

하지만 요즘 채용시장이 얼어붙었다고한다. 개발자 커뮤니티를 찾아보니 경력 이직도 서류 100 ~ 200곳은 돌린다는데.. 아직 물 들어올 때가 아닌 듯 하다.

시장이 조금 풀릴 때 까지 웹앱을 하나 더 만들어보려한다.

로스트아크를 열심히 하고있어서, 관련된 웹앱을 만들까 생각중이다.

이 후 커뮤니티에 올려서 홍보해보면 겸사겸사 포트폴리오가 될 것 이고, 원하는 사용자의 직접적인 피드백도 받을 수 있을 것 같다.

복사했습니다!