항해플러스 5주차 후기

🌱 0. 들어가며

❤️‍🔥 Chapter 2 가 끝났다.


마침내, 항해 플러스 백엔드 5기 과정의 Chapter2 가 끝났다. 👏🏻

Chapter 2가 끝났다는 것은, 항해를 하는 동안 만들고 완성해 나갈 서비스의 큰 뼈대가 완성되었다는 말이다.

내가 선택했던 서비스의 시나리오는 _**'콘서트 대기열 시스템'**_ 이었고,

이 서비스의 요구사항에 맞춰 Usecase 를 설계하고 개발하는 것이 이번 챕터의 목적이었다.

기간은 총 3주였고, 그 기간내에 과제를 완수하는 것은..

게다가 직장을 다니면서 모든 과제를 다 해내는 것은....

넘나, 넘나, 넘나리.. 힘든일이었다..🥹


💎 Chapter 3 에서 내가 공부 할 것


Chapter2 를 통해 서비스의 요구사항을 개발하고 서버를 구축했다면,

Chapter3 에서는 _**이 어플리케이션을 실제로 서비스를 할 때 발생하는 여러 문제들을 대응하는 방법**_을 다룬다.

대표적인 이슈로는 '대용량 트래픽''동시성 이슈' 가 있다.

내가 항해플러스 백엔드 과정을 시작하기로 결심했던 큰 이유중 하나가 바로 이것에 대한 내용이었다.

이 두가지 이슈는 백엔드 개발자가 회사에서 서비스를 개발하고, 개선해나가면서 무조건 마주하게 되는 문제다.

그리고 이 두가지 이슈를 서버에서 제대로 처리하지 못한다면 예상치 못한 큰 장애와 손실을 가져다 줄 수 있다.

그렇기에, 백엔드 엔지니어는 이 부분에 대해 단단하고 견고한 훈련이 되어있어야한다.

하지만, 내게 저 부분에 있어서 어떻게 구현하는지, 어떻게 대응하는지, 어떻게 해결하는지 물어본다면..

자신있게 대답할 수 있을까..?

글쎄.. 🤔

이제 벡엔드 개발자로 만 3년차가 되었지만, 부끄럽게도 자신있게 대답을 하지 못했다.

그렇기에 더더욱 이번 Chapter를 통해 이 부분에 있어서 단단하고 자신있는 기술적 탁월함을 성취하고 싶다.

RedisKafka 를 사용한 대용량 트래픽과 동시성 제어에 대한 학습에 큰 기대를 가지고 있다.


🍇 1. 5주차 항해 회고


🤮 힘들었다. 하지만 해냈다.

상대적으로 4주차에 비해서는 과제의 양이 많지 않았다.


Step9. 구현해야할 Filter 와 Interceptor

내 어플리케이션에서 구현한 Filter 와 Interceptor 는 로깅을 위한 Filter 와 토큰 검증을 위한 Interceptor 였다.


@Component

class LoggingFilter : OncePerRequestFilter() {

    override fun doFilterInternal(

        request: HttpServletRequest,

        response: HttpServletResponse,

        filterChain: FilterChain,

    ) {

        val requestWrapper = ContentCachingRequestWrapper(request)

        val responseWrapper = ContentCachingResponseWrapper(response)

        logger.info(getRequestLog(requestWrapper))

        filterChain.doFilter(request, responseWrapper)

        logger.info(getResponseLog(responseWrapper))

    }

    private fun getRequestLog(request: ContentCachingRequestWrapper): String {

        val requestBody = String(request.contentAsByteArray)

        return """

            |=== REQUEST ===

            |Method: ${request.method}

            |URL: ${request.requestURL}

            |Headers: ${getHeadersAsString(request)}

            |Body: $requestBody

            |================

            """.trimMargin()

    }

    private fun getResponseLog(response: ContentCachingResponseWrapper): String {

        val responseBody = String(response.contentAsByteArray)

        return """

            |=== RESPONSE ===

            |Status: ${response.status}

            |Headers: ${getHeadersAsString(response)}

            |Body: $responseBody

            |=================

            """.trimMargin()

    }

    private fun getHeadersAsString(request: HttpServletRequest): String =

        request.headerNames.toList().joinToString(", ") {

            "$it: ${request.getHeader(it)}"

        }

    private fun getHeadersAsString(response: HttpServletResponse): String =

        response.headerNames.joinToString(", ") {

            "$it: ${response.getHeader(it)}"

        }

}


- 처음에는 Interceptor 로 구현하려고 했었지만, 멘토링을 받은 후에 Filter 로 구현하는 것으로 변경했다.

- Request 와 Response 에 대한 로깅이므로, Servlet 컨테이너에 의해 관리되는 Filter 가 더 적합하다고 판단했다.

- 더군다나, Request 와 Response 는 Spring 과는 무관한 녀석이므로, Spring Context 외부에서 동작하는 Filter 에서 처리하는 것이 맞다고 판단했다.


@Component

class TokenInterceptor(

    private val jwtUtil: JwtUtil,

) : HandlerInterceptor {

    override fun preHandle(

        request: HttpServletRequest,

        response: HttpServletResponse,

        handler: Any,

    ): Boolean {

        if (handler is HandlerMethod) {

            val requireToken =

                handler.hasMethodAnnotation(TokenRequired::class.java) ||

                    handler.beanType.isAnnotationPresent(TokenRequired::class.java)

            if (!requireToken) {

                return true

            }

            val token = request.getHeader(QUEUE_TOKEN_HEADER)

            if (token == null) {

                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "$QUEUE_TOKEN_HEADER is missing")

                return false

            }

            if (!isValidToken(token)) {

                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid $QUEUE_TOKEN_HEADER")

                return false

            }

            request.setAttribute(VALIDATED_TOKEN, token)

        }

        return true

    }

    private fun isValidToken(token: String): Boolean = jwtUtil.validateToken(token)

}


- 이 컴포넌트에서는 발급한 Jwt 토큰의 유효성을 검토한다.

- @TokenRequired 라는 어노테이션이 붙어있는 메서드에서 동작하도록 설계했다.


Step10. 정상적으로 구동되는 서버 완성 및 통합 테스트 작성


126개의 단위 테스트 및 통합테스트를 작성했고, 모두 성공시켰다.


Step10.5 Github Actions 를 활용한 Docker push CI 작성


- Github Actions Workflow 를 통해 Main 브랜치에 push 되면 Docker 파일이 빌드되고 컨테이너 이미지가 github container registry 에 저장되도록 했다.


🏅 지금까지 All Pass !

- Chapter2가 마무리 되는 현재까지 과제를 모두 통과했다.

- 더군다나 이번 과제에서는 따봉👍🏻 을 받았다. 기분이 매우좋으다 크크..


🍐 2. 딱 절반이 지난 지금, 나는 얼마나 성장했나.


👨🏻‍💻 코딩을 할 때 더 시간이 소요된다.


많은 고민 없이 하던 방식대로 손이 먼저 나가고 어느새 하나의 메서드를 완성하고 또 다른 작업을 진행했었다.

하지만, 항해를 시작하고 코딩을 할 때 멈칫 멈칫 할 때가 많다.

>"이렇게 짜면 테스트하기 어려울 수 있을 것 같은데..?"

"패키지 구조는 이게 맞나..?"

"이 메서드는 이 클래스의 책임이 아닌 것 같은데 어떻게 분리시키지?"

"이 도메인은 여기까지 관여를 하면 안될 것 같은데... 설계를 다시 고민해 봐야하나?"

고민 없는 코드는 좋은 코드가 될 수 없다.

단순히 기능 구현의 고민이 아닌, 코드의 품질을 위한 고민을 하기 시작했다.

내 코드가 나만 알아보고 돌아만 가서 끝나는 악취나는 녀석이 되지 않게끔 내 최선을 다해 고민하려는 스탠스를 가지게 되었다.

그리고, 경험치가 쌓이면 이 시간도 단축되고 당연하듯 코드를 쓰게 되겠지..🤗


🐦 객체가 서비스에서 숨쉬며 살아있다는 말이 조금은 이해가 되기 시작했다.


저번 주 회고에서도 언급했지만, 조영호님의 객체지향의 사실과 오해에서 읽었던 대목들이 눈에 들어오기 시작했다.

단순히 @Service 어노테이션 붙이고, 클래스명에 xxxxService 로 만들고, 로직을 작성하고...

나는 그냥 클래스들을 딱딱하고 기계적인 무언가, 정적인 무언가로 생각하며 코딩을 했던 것 같다.

객체들에게 책임을 부여하니까, 이 녀석들이 그 책임을 가지고 일을 하더라.

그리고 그 책임을 가진 녀석들에게 적당한 이름을 붙여주니까 코드가 더 보기 좋아지더라.

실제로 회사에서 코드를 그렇게 변경해봤다.

xxxIssuer, xxxMaker, xxxManager

@Component 를 붙이고, 각자의 책임을 부여하고 xxxService 에서 조립을 하니까 훨씬 코드가 명확해졌다.

그리고, 무엇보다 내 어플리케이션에서 작은 친구들이 각자 부여된 하나의 역할을 충실하게 수행하는 것을 보니 기분이 좋았다.


🧪 테스트코드가 조금은 익숙해졌다.

아, 정말 개인적으로 큰 성장이라고 생각한다.

테스트코드 한 줄도 짜지 못하고, JUnit 을 듣기만 해봤던 내가 테스트코드가 익숙해졌다고 글을 쓰고 있다니..

아직은 그래도 낯선 부분도 있고, 더 좋은 테스트코드를 짜기 위한 고민과 노력이 필요하겠지만, 항해의 반이 지난 지금, 테스트코드가 조금은 익숙해졌다.


3. 글을 마치며

이제 반이 지났다.

언제 이렇게 시간이 갔나 싶다.

매주 회고글을 쓸 때마다 느끼는건데, 항상 이말을 쓰는 것 같다. '시간 참 빠르다.'

이제 시작하는 Chapter3 도 늘 그랬듯, 성실하게 최선을 다해 임해서 내가 성취하고 싶었던 배움의 기쁨을 누리고 싶다.

이번 주도 화이팅 💪🏻


다음 내용이 궁금하다면?

또는

이미 회원이신가요?

2024년 7월 21일 오후 3:11

댓글 0