초록 스터디 - Spring 회고

Jayson·2025년 9월 10일
0
post-thumbnail

5주차: Spring MVC 회고

https://github.com/next-step/spring-roomescape-playground/pull/459

Intro: 견고한 기본기 위에 프레임워크를 쌓다

어느덧, 스프링 MVC 미션에 도달했다. 이전 미션들에서 치열하게 다졌던 객체지향 설계와 테스트 원칙 덕분인지, 스프링을 사용하는 것이 낯설지 않았다. 오히려 그동안 배웠던 기본기가 프레임워크의 강력함과 만났을 때 어떤 시너지를 내는지 체감하는 시간이었다.


배움 1: '직접' 만들지 않아도 되는 이유 - 프레임워크에 위임하기

고민의 시작: 자바 미션을 거치며 '모든 것은 내 손으로 만들어야 한다'는 생각이 은연중에 자리 잡고 있었다. DTO의 날짜/시간 파싱을 직접 구현하고, ReservationService 내부에서 new 키워드로 Repository를 직접 생성하는 등 프레임워크의 도움 없이 문제를 해결하려는 습관이 남아있었다.

멘토의 통찰: 멘토님과 동료의 피드백은 '스프링을 왜 사용하는가'에 대한 본질적인 질문을 던졌다. 스프링이 제공하는 자동 파싱 기능을 사용하지 않은 이유, 그리고 @Repository로 선언한 빈을 new로 직접 생성하며 DI 컨테이너의 이점을 포기한 이유를 꼬집었다. 이는 나에게 프레임워크를 단순한 라이브러리 모음이 아닌, 객체의 생성과 관계 설정을 위임하는 제어의 역전(IoC) 컨테이너로 바라보게 했다.

나의 성장: 프레임워크의 도움을 받는 것을 두려워하지 않게 되었다. 이제는 내가 직접 구현해야 할 비즈니스 로직과 프레임워크에 맡겨야 할 기반 기술을 구분하는 시야가 생겼다. @RestController의 의미를 이해하고 불필요한 @ResponseBody를 제거하는 작은 리팩토링 과정 속에서, 프레임워크의 컨벤션을 따르는 것이 곧 생산성과 직결된다는 것을 배웠다.


배움 2: 원칙은 프레임워크를 초월한다 - 기본기의 중요성

고민의 시작: 스프링이라는 편리한 도구를 사용하면서도, '어떻게 하면 더 나은 객체를 만들 수 있을까'하는 고민은 계속되었다. 특히 정적 팩토리 메서드를 습관처럼 of로만 명명하거나, 처리 방식이 같은 예외를 굳이 다른 클래스로 나누는 등 과거의 습관이 불쑥 튀어나왔다.

멘토의 통찰: 멘토님은 프레임워크의 문법이 아닌, 변하지 않는 설계 원칙을 다시 한번 상기시켜 주셨다. 정적 팩토리 메서드의 장점은 create, withId처럼 이름에 의도를 담는 것에 있고, 예외 클래스는 처리 방식이 다를 때 분리해야 의미가 있다는 점을 짚어주셨다. 이는 기술의 종류와 상관없이 좋은 코드를 만드는 핵심 원칙이었다.

나의 성장: 좋은 설계 원칙은 어떤 기술 스택을 사용하든 통용되는 '언어'와 같다는 것을 체감했다. 프레임워크는 강력한 도구이지만, 그 도구를 빛나게 하는 것은 결국 응집도, 결합도, 객체의 책임과 같은 기본기라는 사실을 다시 한번 마음에 새겼다.


6주차: Spring JDBC 회고

https://github.com/next-step/spring-roomescape-playground/pull/462

Intro: InMemory에서 실제 DB로, 설계의 힘을 체감하다

이번 미션은 이론으로만 알던 JDBC를 직접 다루며, 애플리케이션의 데이터를 인메모리(In-Memory)가 아닌 실제 데이터베이스에 연결하는 과정이었다. 놀라웠던 점은, 데이터 저장 기술이라는 큰 변화에도 불구하고 기존 코드의 수정이 거의 없었다는 점이었다. 이전 미션들에서 끈질기게 고민했던 '역할과 책임 분리'가 얼마나 중요한지, 그 설계의 힘을 온몸으로 체감하는 순간이었다.


배움 1: 경계를 명확히 할수록 코드는 강해진다

고민의 시작: DELETE 쿼리 실행 후 영향받은 행이 0개일 때, 이 예외를 어디서 처리해야 할지 고민했다. 데이터의 결과를 가장 먼저 아는 Repository가 예외를 던지는 것이 자연스럽다고 생각했다.

멘토의 통찰: 멘토님은 "이 예외 발생의 책임이 과연 repository일까요?" 라는 질문을 던지셨다. 이 질문을 통해, Repository의 역할과 Service의 역할을 더 명확히 구분하게 되었다.

  • Repository의 책임: 데이터에 접근하고, 그 결과를 사실 그대로 보고하는 것. (e.g., "0개의 행이 삭제되었습니다.")
  • Service의 책임: Repository로부터 받은 보고를 비즈니스 맥락에 맞게 해석하고 판단하는 것. (e.g., "0개가 삭제되었다는 것은, ID에 해당하는 예약이 없다는 뜻이므로 예외 상황이다.")

나의 성장: 각 계층의 책임이 무엇인지 훨씬 더 선명하게 이해하게 되었다. 또한, 페이지를 렌더링하는 @Controller와 데이터만 반환하는 @RestController의 책임이 다르다는 것을 배우며, 느슨하게 결합되고 응집도 높은 계층형 아키텍처의 중요성을 다시 한번 깨달다.


배움 2: 백엔드 개발자는 코드와 DB 모두를 책임진다

고민의 시작: JdbcTemplate을 처음 사용해보며 SQL을 직접 작성하는 것이 신기했지만, 한편으로는 깊이 있는 학습 없이 기능 구현에만 급급한 것 같아 아쉬움이 남았다. 데이터베이스 스키마를 작성할 때도 습관적으로 VARCHAR(255)를 사용했다.

멘토의 통찰: 멘토님은 "VARCHAR의 크기는 왜 255인가요?" 라는 질문으로 데이터 타입의 중요성을 일깨워주셨다. 이 질문 덕분에 DATE, TIME 같은 전용 데이터 타입을 사용했을 때의 데이터 무결성, 성능, 활용성 측면의 이점을 깊이 탐구할 수 있었다. 또한, PreparedStatement의 원리와 장점 등 추가 학습 과제를 통해 JdbcTemplate의 내부 동작 원리를 더 깊게 이해하도록 이끌어주셨다.

나의 성장: 백엔드 개발자의 책임 범위가 애플리케이션 코드를 넘어, 데이터베이스 설계와 SQL 최적화까지 이어진다는 것을 명확히 인지하게 되었다. 이제는 단순히 기능을 구현하는 것을 넘어, 데이터의 성격에 맞는 최적의 타입을 선택하고 효율적인 쿼리를 고민하는 개발자로 성장하는 발판을 마련했다.


7주차: Spring Core 회고

https://github.com/next-step/spring-roomescape-playground/pull/480

Intro: 설계의 진정한 시험대 - 변화하는 코드베이스

Spring Core 미션은 소프트웨어 유지보수의 현실을 여행하는 과정이었다. 새로운 기능을 처음부터 만드는 것이 아니라, 새로운 요구사항에 맞춰 기존 코드베이스를 발전시키는 일이었다. 이 과정은 제가 과거에 내렸던 설계 결정들에 대한 진정한 시험대였고, 처음에 가졌던 자신감은 이내 겸손한 경험으로 바뀌었다. 좋은 설계의 진정한 가치는 처음 만들 때의 쉬움이 아니라, 변화에 얼마나 우아하게 적응하는지에 있다는 것을 깨달았습니다.


배움 1: 리팩토링의 고통 - 낮은 응집도에 대한 대가

  • 마주한 문제: '시간'을 나타내는 String 타입을 별도의 Time 객체로 바꾸는, 비교적 간단해 보였던 작업이 복잡하고 시간이 많이 소요되는 난관으로 변했다. 이 변경 사항은 애플리케이션 전체에 파급 효과를 일으켜, 관련 없어 보이는 여러 곳을 수정해야만 했다.

  • 깨달음: 어려움의 원인은 작업 자체가 아니라, 기존 코드의 낮은 응집도에 있었다. '시간'과 관련된 비즈니스 로직이 하나의 객체에 깔끔하게 캡슐화되지 않고 VO, DTO, 서비스 등 여러 곳에 흩어져 있었기 때문이다. 저는 흩어진 모든 조각을 찾아내 수정해야 했다.

  • 나의 성장: 응집도 높은 코드는 미래를 위한 투자라는 중요한 교훈을 얻었다. 관련된 책임을 한곳에 모으기 위해 들이는 노력은, 훗날 요구사항이 변경될 때 엄청난 보상으로 돌아온다. 좋은 설계는 학문적인 연습이 아니라, 유연하고 유지보수 가능한 시스템의 근간 그 자체였다.


배움 2: 테스트는 정적인 체크리스트가 아닌, 살아있는 명세서

  • 마주한 문제: 새로운 비즈니스 규칙(예: 예약은 사전에 등록된 시간을 사용해야 함)에 맞춰 운영 코드를 수정하자, 기존 테스트들이 실패하기 시작했다. 새로운 테스트를 통과시키면 기존 테스트가 깨지고, 그 반대도 마찬가지였다. 저는 운영 코드와 테스트 코드 중 무엇이 잘못된 것인지 혼란스러운 고리에 갇혔다.

  • 깨달음: 저는 중요한 결론에 도달했다. 비즈니스 로직이 진화하면, 그 로직을 설명하는 테스트도 함께 진화하고 발전해야 한다는 것이다. 과거의 테스트는 더 단순한 규칙을 위해 작성되었다. 새로운 규칙이 생겼다고 해서 과거의 테스트가 틀린 것이 아니라, 단지 '더 이상 유효하지 않게(obsolete)' 되었을 뿐이다. 테스트는 검증하려는 시나리오에 더욱 구체적이어야 했다.

  • 나의 성장: 저는 테스트를 더 이상 고정불변의 안전망으로 보지 않게 되었다. 대신, 특정 조건 하에 시스템의 동작을 명시하는 살아있는 문서로 바라보게 되었다. 이 경험은 지나치게 일반적인 테스트보다, 목적이 명확한 시나리오 기반의 구체적인 테스트가 훨씬 가치 있고 유지보수하기 쉽다는 것을 가르쳐주었다.


배움 3: 도구를 정복한다는 것 - '어떻게'를 넘어 '왜'를 이해하기

  • 마주한 문제: 저는 그동안 배워온 패턴에 따라 @ControllerResponseEntity 같은 스프링 애노테이션을 사용해왔다. 하지만 그 미묘한 차이를 완전히 이해하지 못했고, @ResponseBody를 언제 써야 하는지, 페이지 렌더링 컨트롤러와 API 컨트롤러를 어떻게 분리해야 하는지에 대한 혼란이 있었다.

  • 깨달음: 멘토님의 피드백과 스스로의 탐구를 통해, 스프링이 내부적으로 어떻게 동작하는지 더 깊이 파고들었다. ResponseEntity는 스프링이 직접 처리하는 특별한 타입이어서 @ResponseBody가 불필요하다는 것을 알게 되었다. 이는 사소한 최적화를 넘어, 관심사를 분리하는 더 큰 개념으로 이어졌다. 페이지 렌더링과 데이터 제공은 명백히 다른 책임이며, 각각의 전문화된 컨트롤러(@Controller vs @RestController)에 속해야 한다는 것을 이해했다.

  • 나의 성장: 프레임워크에 대한 저의 이해는 '어떻게 사용하는가'에서 '왜 이렇게 동작하는가'로 전환되었다. 이 깊은 지식은 더 의도적이고 효과적인 설계를 가능하게 한다. 이는 우리가 사용하는 도구의 명령어를 암기하는 데 그치지 않고, 그 도구를 진정으로 이해하는 것이 얼마나 중요한지 다시 한번 일깨워주었다.


Outro

단순한 기술 습득의 시간이 아니었다. 개발자로서 저의 관점에 근본적인 변화를 가져왔다. 처음에는 '동작하는 코드'를 만드는 데 집중했던 저는, 이제 '올바른 코드'를 만드는 데 열정을 쏟게 되었다.

첫 자바 미션부터 마지막 스프링 애플리케이션까지, 저는 코드를 기계를 위한 명령어 집합이 아닌, 미래의 저와 동료들을 위한 소통의 한 형태로 바라보게 되었다. "이 코드는 응집도가 높은가?", "이 테스트는 의미가 있는가?", "이 클래스의 진정한 책임은 무엇인가?"와 같은 질문들이, 처음에 가졌던 답보다 더 중요하다는 것을 배웠다.

이 블로그는 그 변화의 기록이다. 과잉 설계를 경계하고, 실용적인 균형점을 찾아 헤맸으며, 기능적인 코드를 넘어 깔끔하고 유지보수 가능하며 실용적인 코드를 향해 나아간 여정... 이 성장을 가능하게 해준 날카로운 질문과 귀중한 가이드를 주신 멘토 오찌님께 깊은 감사를 전합니다..!!

profile
Small Big Cycle

0개의 댓글