포스트

주니어 백엔드 개발자가 실무에서 자주 부딪히는 질문들

백엔드 실무를 처음 겪기 시작하면 기술 자체보다 “무엇을 먼저 고민해야 하는가”가 더 어려울 때가 많다. 개념은 어느 정도 알고 있어도, 실제 프로젝트 안에서는 무엇이 중요한 질문인지 판단하기가 쉽지 않다.

이 글은 주니어 시절부터 계속 붙잡고 있던 질문들을 정리한 것이다. 명쾌한 정답을 내리는 글이라기보다, 실무를 하면서 계속 돌아보게 되는 질문 목록에 가깝다.

1. 테스트는 어디까지 해야 하는가

테스트를 많이 작성하는 것이 좋다는 건 알지만, 실제로는 어디까지를 목표로 잡아야 할지 항상 고민된다.

  • 테스트 커버리지는 몇 퍼센트가 적절한가
  • 단순한 메서드도 테스트해야 하는가
  • 어떤 코드를 먼저 테스트해야 하는가
  • 단위 테스트와 통합 테스트의 비중은 어떻게 잡아야 하는가

실무에서는 모든 코드를 고르게 테스트하기보다, 실패 비용이 큰 로직부터 보호하는 쪽이 현실적이다. 비즈니스 규칙, 경계 조건, 금전/정합성 관련 로직은 우선순위가 높다. 반대로 getter, 단순 위임, 지나치게 구현 세부적인 코드까지 무리하게 테스트하면 유지비가 더 커질 수 있다.

결국 주니어에게 중요한 질문은 “커버리지를 몇 퍼센트로 맞출까”보다 어떤 실패를 먼저 막아야 하는가에 가깝다.

2. MSA와 외부 API 연동은 무엇이 다른가

서비스를 나눈다는 말은 자주 듣지만, 실제로는 “이건 MSA인가, 그냥 외부 API 붙인 구조인가”가 헷갈릴 때가 많다.

이 질문은 단순한 용어 구분이 아니라 다음 문제로 이어진다.

  • 서비스 경계를 어디서 나눌 것인가
  • 네트워크 비용을 감수할 이유가 있는가
  • 장애 전파를 어떻게 막을 것인가
  • 데이터 정합성은 어떻게 맞출 것인가

주니어 입장에서는 MSA라는 단어보다, 서비스를 분리했을 때 생기는 운영 비용과 책임 이동을 먼저 이해하는 편이 더 중요하다고 느낀다.

3. 캐시는 언제 붙여야 하는가

Redis나 Memcached 같은 도구는 익숙하지만, 실제로는 “어디에 붙여야 하는가”가 더 어렵다.

  • 어떤 데이터가 캐시 대상이 되는가
  • TTL은 어떻게 정해야 하는가
  • 캐시 미스가 몰리면 어떻게 되는가
  • 캐시와 원본 데이터의 불일치는 어디까지 허용할 수 있는가

캐시는 성능 도구이지만, 동시에 정합성과 운영 복잡도를 바꾸는 도구이기도 하다. 그래서 “느리니까 캐시를 붙인다”보다 어떤 읽기 비용을 줄이고, 어떤 stale 데이터를 허용할 것인가를 먼저 생각해야 한다.

4. 쿼리 튜닝과 인덱싱은 어떻게 접근해야 하는가

처음에는 인덱스를 추가하면 성능이 좋아질 것 같고, SQL 문장을 조금 바꾸면 큰 차이가 날 것처럼 느껴진다. 그런데 실제로는 쿼리 하나보다 데이터 분포, 인덱스 선택도, 실행 계획, 테이블 구조가 더 중요할 때가 많다.

항상 고민되는 질문은 이런 것들이다.

  • 어떤 컬럼에 인덱스를 걸어야 하는가
  • 복합 인덱스 순서는 무엇이 기준인가
  • 읽기 성능을 위해 쓰기 성능을 얼마나 희생할 수 있는가
  • 지금 느린 이유가 SQL 자체인가, 테이블 구조인가

주니어 시절에는 “인덱스를 외우는 것”보다 실행 계획을 읽고 병목 위치를 찾는 습관이 더 중요하다고 느꼈다.

5. ORM은 어디까지 믿어야 하는가

JPA를 쓰다 보면 생산성은 높지만, 내부에서 무엇이 일어나는지 모르면 오히려 불안하다.

  • 영속성 컨텍스트는 정확히 언제 flush 되는가
  • 지연 로딩은 언제 쿼리를 발생시키는가
  • 이 변경이 왜 update 쿼리로 나갔는가
  • 이 구조가 N+1을 만들고 있지는 않은가

ORM을 잘 쓴다는 건 단순히 편하게 쓰는 것이 아니라, ORM이 SQL을 언제 어떻게 만들고 있는지 감각이 있는 상태에 더 가깝다.

6. Mocking은 어디까지 해야 하는가

테스트를 작성하다 보면 Mockito 같은 도구를 많이 쓰게 된다. 그런데 mocking이 많아질수록 오히려 테스트가 구현에 과하게 묶이는 느낌도 든다.

  • Mockito를 쓰는 게 맞는가
  • 간이 테스트 더블 클래스를 직접 만드는 게 나은가
  • 외부 의존성을 어느 수준까지 끊어야 하는가
  • 이 테스트는 진짜 동작을 검증하는가, 호출 순서만 검증하는가

주니어 시절에는 “mock을 얼마나 잘 쓰는가”보다 무엇을 검증하려는 테스트인지 먼저 분명히 하는 것이 더 중요하다고 느꼈다.

7. REST API는 어디까지 쪼개야 하는가

API 설계에서는 항상 프런트엔드와의 경계가 고민된다.

  • 한 번에 많은 데이터를 내려주는 것이 좋은가
  • 화면 단위로 쪼개는 것이 좋은가
  • 조합 로직은 프런트가 가져가야 하는가, 백엔드가 가져가야 하는가
  • 페이지네이션, 필터링, 정렬은 어느 쪽 책임인가

이 질문은 결국 성능, 재사용성, 화면 요구사항, 팀 간 책임 분배와 연결된다. 그래서 API 설계는 기술 문제이기도 하지만 협업 구조 문제이기도 하다.

8. 성능은 어느 계층부터 봐야 하는가

처음에는 서버 코드만 최적화하면 성능이 좋아질 것 같지만, 실제 성능 문제는 다양한 계층에서 생긴다.

  • 클라이언트 렌더링
  • CDN, LB, 네트워크 왕복
  • API 서버 처리 시간
  • DB 접근과 락 경합
  • 외부 API 호출

주니어 때는 “어디가 느린지”보다 “무조건 코드가 비효율적인 것 같다”는 식으로 보기 쉽다. 그런데 실무에서는 어느 계층이 병목인지 먼저 구분하는 것이 거의 절반 이상이라고 느꼈다.

9. 임시 식별자와 엔티티 생성 타이밍은 어떻게 처리해야 하는가

프런트엔드에서 아직 영구 ID가 없는 엔티티를 여러 개 동시에 만들고 편집해야 하는 경우가 있다. 이때 임시 식별자를 어떻게 다룰지 항상 고민된다.

  • 프런트에서 임시 ID를 만들게 할 것인가
  • 서버가 먼저 예약 ID를 내려줄 것인가
  • 영구 ID와 임시 ID의 매핑은 어떻게 맞출 것인가
  • 숫자 변환 같은 편법을 써도 되는가

이런 문제는 사소해 보여도 나중에 정렬, 수정, 삭제, 동기화 로직에서 크게 번질 수 있다. 그래서 주니어일수록 편법보다 식별자 생명주기 자체를 명확히 설계하는 습관이 중요하다고 생각한다.

10. DB는 어떻게 문서화하고 형상관리해야 하는가

레거시 시스템을 볼 때 가장 힘든 순간 중 하나는 테이블과 컬럼의 역할을 코드 추론으로만 알아내야 할 때다.

  • 어떤 컬럼이 왜 존재하는가
  • 지금 이 제약 조건은 어디서 보장되는가
  • 스키마 변경 이력은 어디에 남는가
  • 운영 DB와 개발 DB는 어떻게 동기화되는가

애플리케이션 코드는 Git으로 관리하면서 DB는 그렇지 않은 경우가 많다. 그래서 Flyway나 Liquibase 같은 도구를 단순한 마이그레이션 도구가 아니라, DB 변경 이력을 남기는 개발 문화로 이해할 필요가 있다고 느꼈다.

11. 장애 원인은 어떻게 찾아야 하는가

에러가 나면 처음에는 이것저것 바꿔보며 감으로 접근하기 쉽다. 하지만 실제로는 대조군을 나눠서 원인을 좁혀 가는 방식이 훨씬 중요하다.

예를 들면 이런 질문을 먼저 해야 한다.

  • 로컬에서는 재현되는가
  • 개발/운영 환경 차이는 무엇인가
  • 네트워크 경계에서 막히는가
  • 서버 안에서 직접 호출하면 되는가
  • 외부 서비스 정책이나 IP 제한 문제는 없는가

주니어 시절에 가장 크게 배운 것 중 하나는, 장애 원인을 빨리 맞히는 능력보다 가설을 하나씩 제거하는 순서가 더 중요하다는 점이었다.

12. 검증 로직은 어디에 두어야 하는가

실무에서는 검증이 항상 애매하다.

  • 프런트 검증이면 충분한가
  • 컨트롤러에서 걸러야 하는가
  • 서비스에서 비즈니스 규칙으로 다뤄야 하는가
  • DB 제약 조건까지 내려가야 하는가

문제가 생긴 뒤에 검증을 추가하는 건 쉽지만, 사전에 어디까지 막아야 하는지는 늘 어렵다. 그래서 검증은 단순 입력 체크가 아니라 시스템이 반드시 지켜야 하는 규칙을 어디서 보장할 것인가의 문제라고 느끼게 됐다.

13. 로그는 얼마나 남겨야 하는가

초기에는 로그를 많이 남기면 좋다고 생각하기 쉽다. 하지만 실제로는 너무 많은 로그가 오히려 원인 파악을 방해할 때도 많다.

  • 어떤 이벤트를 INFO로 남길 것인가
  • WARN과 ERROR의 기준은 무엇인가
  • 개인정보나 민감정보는 어떻게 제외할 것인가
  • 요청 추적을 위해 어떤 식별자를 로그에 포함할 것인가

좋은 로그는 양이 많은 로그가 아니라, 장애 시점에 다시 읽었을 때 의사결정에 도움이 되는 로그라는 걸 점점 더 느끼게 된다.

14. 배포는 얼마나 자주 해야 하는가

주니어일수록 배포는 “잘 끝나기만 하면 된다”는 생각을 하게 되는데, 실제로는 배포 주기와 배포 단위가 팀 생산성에 큰 영향을 준다.

  • 큰 배포를 드물게 하는 게 나은가
  • 작은 배포를 자주 하는 게 나은가
  • rollback은 실제로 가능한가
  • DB 스키마 변경이 들어가면 배포 순서는 어떻게 잡아야 하는가

이 질문은 코드 품질보다도 운영 준비도와 연결된다.

15. 어디까지 추상화해야 하는가

주니어 때는 중복이 조금만 보여도 바로 추상화하고 싶어진다. 그런데 실무에서는 너무 이른 추상화가 오히려 더 큰 복잡도를 만들 때가 많다.

  • 지금 공통화할 시점인가
  • 비슷해 보이지만 실제로는 나중에 달라질 가능성이 큰가
  • 추상화가 오히려 읽기 비용을 키우고 있지는 않은가

결국 좋은 추상화는 코드 줄 수를 줄이는 기술이 아니라, 변화의 방향이 비슷한 것들을 같이 묶는 판단에 더 가깝다.

정리

주니어 시절의 질문은 대부분 “정답이 무엇인가”보다 “무엇을 기준으로 판단해야 하는가”로 이어진다.

돌아보면 실무 초반에 가장 중요했던 것은 개념을 많이 아는 것보다 다음 두 가지였다.

  • 질문을 더 정확하게 만드는 능력
  • 질문을 시스템 구조와 운영 맥락으로 연결하는 능력

그래서 이런 질문들을 오래 붙잡아 보는 것 자체가 의미가 있다고 생각한다. 주니어 시절에는 답을 빨리 내리는 것보다, 좋은 질문을 오래 유지하는 편이 더 큰 자산이 된다.

질문별 모범 사례

질문을 오래 붙잡는 것과 별개로, 실무 초반에 비교적 안정적으로 적용할 수 있는 원칙도 있다. 완벽한 정답은 아니지만, 대부분의 팀과 프로젝트에서 크게 어긋나지 않는 기준들이다.

1. 테스트는 실패 비용이 큰 곳부터 시작한다

모든 코드를 같은 밀도로 테스트하려 하지 말고, 정합성·금전·권한·상태 전이처럼 실패 비용이 큰 로직부터 먼저 보호하는 편이 낫다. 특히 주니어 때는 “커버리지를 얼마나 채웠는가”보다 “실패했을 때 가장 아픈 흐름을 막고 있는가”를 먼저 보는 습관이 중요하다.

예를 들어 주문 상태 전이, 결제 금액 계산, 권한 체크처럼 한 번 잘못되면 운영 이슈로 바로 이어지는 부분은 테스트 우선순위가 높다. 반대로 단순 DTO 매핑이나 getter, 프레임워크가 이미 보장하는 동작까지 과하게 테스트하면 유지비만 커질 수 있다.

실무에서는 보통 다음 순서가 안전하다.

  • 핵심 비즈니스 규칙 테스트
  • 경계값과 예외 흐름 테스트
  • 외부 시스템 연동이 얽힌 통합 테스트
  • 회귀 가능성이 큰 버그 재현 테스트

이 기준이 잡히면 “무엇부터 테스트할까”라는 질문이 훨씬 쉬워진다.

2. MSA와 외부 API 연동은 경계와 책임으로 구분한다

외부 API 호출이 있다고 해서 곧바로 MSA가 되는 것은 아니다. 서비스 분리는 배포 단위, 데이터 소유권, 장애 전파, 운영 책임이 함께 나뉘는 구조인지로 판단하는 편이 더 정확하다.

중요한 것은 “프로세스가 몇 개냐”가 아니라 “장애와 변경 책임이 어디까지 분리됐느냐”다. 단순히 다른 팀 시스템을 HTTP로 호출한다고 해서 그 자체가 MSA적 설계라고 보기는 어렵다.

서비스 경계를 판단할 때는 다음을 같이 봐야 한다.

  • 이 서비스가 자체 데이터와 비즈니스 규칙을 소유하는가
  • 독립 배포가 실제로 가능한가
  • 장애가 나도 다른 서비스가 최소 기능을 유지할 수 있는가
  • 네트워크 호출 비용과 복잡도를 감수할 이유가 있는가

주니어 때는 MSA라는 단어를 정의로 외우기보다, 분리했을 때 생기는 비용과 이득을 같이 설명할 수 있는 상태를 목표로 잡는 편이 낫다.

3. 캐시는 읽기 비용이 큰 곳에만 붙인다

캐시는 무조건 붙이는 기능이 아니라, 원본 조회 비용을 줄이기 위한 선택지다. TTL, 무효화, 정합성 허용 범위까지 같이 설계해야 한다.

캐시를 붙일 때 가장 흔한 실수는 “느리니까 일단 Redis를 붙인다”는 식으로 접근하는 것이다. 하지만 캐시는 조회 속도를 얻는 대신, 데이터 신선도와 운영 복잡도를 함께 바꾼다.

그래서 최소한 다음 질문은 먼저 해야 한다.

  • 이 데이터는 얼마나 자주 바뀌는가
  • 조금 오래된 값을 보여줘도 되는가
  • 캐시 미스가 몰리면 원본 시스템이 버틸 수 있는가
  • 무효화는 이벤트로 할 것인가, TTL로 할 것인가

실무에서는 조회 비용이 크고, 읽기 비율이 높고, 약간의 stale data를 감수할 수 있는 데이터부터 시작하는 편이 가장 안정적이다. 반대로 정합성 민감도가 높은 데이터는 캐시보다 쿼리나 인덱스, 조회 구조 자체를 먼저 점검하는 편이 나을 때가 많다.

4. 쿼리는 실행 계획으로 판단한다

느린 SQL을 볼 때는 문장 모양만 보지 말고 실행 계획, 인덱스 선택, 데이터 분포를 함께 봐야 한다. 인덱스 추가는 항상 읽기와 쓰기 비용을 같이 바꾼다.

주니어 때는 WHERE에 쓰인 컬럼에 인덱스를 걸면 끝날 것처럼 느껴지기 쉽다. 하지만 실제로는 데이터 분포가 치우쳐 있거나, 정렬과 범위 조건이 섞여 있거나, 조인 순서가 비효율적이면 단순한 인덱스 추가로 해결되지 않는다.

그래서 쿼리 튜닝은 보통 이 순서가 좋다.

  1. 실제 느린 쿼리와 실행 계획을 본다.
  2. 어떤 단계에서 비용이 커지는지 확인한다.
  3. 인덱스, 조인 구조, 조회 범위, 정렬 조건을 함께 본다.
  4. 개선 후에도 쓰기 비용과 저장 공간 비용이 감당 가능한지 본다.

중요한 것은 “인덱스를 외우는 것”보다 “왜 이 실행 계획이 나왔는지 설명할 수 있는 것”이다. 그 감각이 있어야 다른 쿼리에도 적용할 수 있다.

5. ORM은 추상화이지만 SQL 감각은 유지한다

JPA나 Hibernate를 쓰더라도 flush 시점, 지연 로딩, N+1, update 발생 조건은 직접 설명할 수 있어야 한다. ORM을 잘 쓰는 것은 SQL을 안 보는 것이 아니라 SQL을 함께 볼 줄 아는 것이다.

ORM의 장점은 생산성이다. 하지만 내부 동작을 모른 채 쓰면 성능 문제나 정합성 문제를 만났을 때 원인을 설명하지 못하게 된다. 특히 주니어 때는 “왜 여기서 쿼리가 나갔는가”를 설명하는 훈련이 중요하다.

최소한 다음 정도는 직접 확인할 수 있어야 한다.

  • 어떤 시점에 flush 되는가
  • 지연 로딩이 언제 추가 쿼리를 만드는가
  • 더티 체킹이 어떤 update를 발생시키는가
  • 현재 코드가 N+1을 만들고 있지는 않은가

실무에서는 ORM을 추상화 계층으로 받아들이되, SQL 로그와 실행 결과를 같이 보는 습관을 들이는 편이 가장 안전하다.

6. Mocking은 검증 목표가 분명할 때만 쓴다

mock이 많아질수록 테스트는 구현 세부사항에 묶이기 쉽다. 호출 순서를 과하게 검증하기보다, 외부 의존성을 끊어야 하는 이유가 분명한 곳에서만 제한적으로 쓰는 편이 더 안정적이다.

예를 들어 외부 결제 API, 메시지 발행, 시간 의존 로직처럼 테스트 환경에서 직접 다루기 어려운 의존성은 mock이나 fake가 유용하다. 반대로 내부 협력 객체까지 전부 mock으로 바꾸면 테스트가 동작 검증보다 구현 복제에 가까워진다.

그래서 mocking을 쓸 때는 먼저 이렇게 생각하는 편이 좋다.

  • 내가 검증하려는 것은 결과인가, 호출 자체인가
  • 이 의존성을 끊지 않으면 테스트가 너무 느리거나 불안정한가
  • mock보다 fake나 test double이 더 읽기 쉬운가

mock을 잘 쓰는 테스트보다, 검증 목표가 분명한 테스트가 더 오래 버틴다. 이 기준이 흔들리면 테스트가 코드 리팩터링을 방해하기 시작한다.

7. REST API는 화면보다 책임 경계를 먼저 본다

화면 단위로 무조건 API를 쪼개거나 반대로 한 번에 모든 데이터를 내려주기보다, 어떤 조합 책임을 서버가 가져가야 하는지부터 정해야 한다. 재사용성, 응답 크기, 변경 빈도를 같이 봐야 균형이 맞는다.

프런트엔드 화면은 자주 바뀌지만, 도메인 경계는 상대적으로 오래 간다. 그래서 API를 설계할 때 화면 구조만 따라가면 금방 깨지기 쉽다. 반대로 재사용성만 과하게 의식하면 실제 화면에서는 조합 비용이 너무 커질 수 있다.

그래서 보통은 다음 균형을 같이 본다.

  • 이 응답은 특정 화면에만 강하게 묶이는가
  • 여러 화면에서 재사용될 가능성이 있는가
  • 필터링, 정렬, 페이지네이션 책임은 어디까지 서버가 가져갈 것인가
  • 프런트가 조합할 때 과도한 네트워크 왕복이 발생하지 않는가

좋은 API 설계는 정답 하나가 있는 문제가 아니라, 책임을 어느 계층에 둘지 팀이 합리적으로 선택하는 문제에 가깝다.

8. 성능 문제는 항상 병목 위치부터 찾는다

코드를 바로 고치기 전에, 클라이언트·네트워크·서버·DB 중 어디가 느린지 먼저 좁혀야 한다. 병목 위치를 모른 채 최적화하면 대부분 헛수고가 된다.

실무에서는 “느리다”는 제보 하나로 원인을 단정하면 거의 항상 돌아가게 된다. 서버 메서드가 문제일 수도 있지만, 실제로는 외부 API 호출, DB 락 대기, 네트워크 왕복, 프런트 렌더링이 더 큰 원인일 수 있다.

그래서 먼저 해야 할 일은 최적화가 아니라 계측이다.

  • 어디서 시간이 가장 많이 쓰이는가
  • 평균이 느린가, tail latency가 느린가
  • 특정 요청만 느린가, 전체가 느린가
  • 특정 시간대나 데이터 조건에서만 느린가

병목을 먼저 좁히면 이후 판단이 훨씬 쉬워진다. 반대로 병목 위치를 모른 채 코드를 고치기 시작하면, 시간이 들었는데 체감 효과는 거의 없는 경우가 많다.

9. 임시 식별자는 생명주기 기준으로 설계한다

프런트 임시 ID, 서버 영구 ID, 둘 사이의 매핑 시점을 먼저 정해야 한다. 숫자 변환 같은 편법보다 생성, 저장, 동기화 단계마다 어떤 식별자가 기준인지 명확히 두는 편이 장기적으로 안전하다.

이 문제는 처음엔 사소해 보이지만, 나중에 정렬, 수정, 삭제, optimistic update, 배치 저장까지 얽히면 빠르게 복잡해진다. 특히 프런트엔드가 여러 엔티티를 동시에 만들고 수정하는 화면에서는 식별자 규칙이 흐리면 버그가 자주 난다.

안전한 기준은 보통 이렇다.

  • 생성 직후에는 임시 ID로 UI 상태를 안정적으로 관리한다.
  • 서버 저장 이후에는 영구 ID를 기준으로 전환한다.
  • 둘 사이 매핑 시점과 교체 규칙을 명확히 둔다.
  • 임시 ID를 숫자 변환 등으로 억지로 맞추지 않는다.

식별자는 값 하나가 아니라 생명주기다. 이 감각이 있으면 복잡한 생성/편집 화면에서도 구조가 훨씬 덜 흔들린다.

10. DB도 코드처럼 문서화하고 형상관리한다

스키마와 제약 조건, 변경 이력은 애플리케이션 코드처럼 추적 가능해야 한다. Flyway나 Liquibase 같은 도구는 배포 편의성만이 아니라, DB 변경 이유를 남기는 장치로 보는 편이 좋다.

레거시 프로젝트에서 힘든 지점은 테이블이 복잡해서가 아니라, “왜 이렇게 생겼는지”를 아는 사람이 없다는 점일 때가 많다. 그래서 DB도 코드처럼 이력과 의도를 남기는 쪽이 훨씬 낫다.

최소한 다음은 남는 편이 좋다.

  • 스키마 변경 스크립트
  • 변경 이유와 배경
  • 제약 조건과 인덱스 추가 이유
  • 운영 반영 순서와 호환성 고려사항

이런 문화가 없으면 DB는 점점 “건드리기 무서운 영역”이 된다. 반대로 형상관리와 문서화가 잡히면 스키마 변경도 코드 리뷰와 협업 대상이 된다.

11. 장애 분석은 대조군을 나눠서 한다

로컬, 개발, 운영, 네트워크 경계, 외부 API 호출 지점을 하나씩 분리해서 확인해야 한다. 빠른 추측보다 재현 조건과 차이를 좁히는 순서가 더 중요하다.

장애가 나면 사람은 본능적으로 가장 익숙한 원인을 먼저 의심한다. 하지만 장애 대응에서는 감보다 대조군이 훨씬 중요하다. 어디서는 되고 어디서는 안 되는지를 분리해야 원인이 좁혀진다.

예를 들어 이런 순서가 기본이 된다.

  • 로컬에서는 재현되는가
  • 같은 API를 서버 내부에서는 호출할 수 있는가
  • 개발 환경과 운영 환경의 차이는 무엇인가
  • 요청 파라미터나 데이터 조건이 다른가
  • 외부 서비스 정책, IP, 인증 정보 차이는 없는가

원인을 빨리 맞히는 사람보다, 잘못된 가설을 빨리 버리는 사람이 실제 장애 대응에서는 더 강한 경우가 많다.

12. 검증은 입력 체크와 비즈니스 규칙을 구분한다

형식 검증은 경계에서, 도메인 규칙 검증은 서비스나 도메인 로직에서, 최종 무결성은 DB 제약 조건에서 보장하는 식으로 층위를 나누는 편이 좋다.

검증이 꼬이는 이유는 대부분 모든 검증을 한 곳에 몰아넣거나, 반대로 아무 데서나 중복 검증하기 때문이다. 검증은 한 종류가 아니라 층위가 다르다.

보통은 이렇게 나누면 정리가 쉽다.

  • 컨트롤러/입력 경계: null, 형식, 길이, 범위
  • 서비스/도메인: 상태 전이, 권한, 비즈니스 규칙
  • DB: unique, foreign key, not null 같은 최종 무결성

이렇게 나누면 검증 실패 메시지도 더 명확해지고, 어디까지를 재사용 가능한 규칙으로 봐야 하는지도 드러난다.

13. 로그는 나중에 읽을 사람 기준으로 남긴다

로그를 남길 때는 “지금 보기 편한가”보다 “장애가 났을 때 원인을 판단할 수 있는가”를 기준으로 생각해야 한다. 요청 식별자, 주요 상태 전이, 실패 맥락은 남기고 민감정보는 제외해야 한다.

처음에는 로그를 많이 남기면 안전할 것 같지만, 실제로는 너무 많은 로그가 더 큰 노이즈가 되기도 한다. 결국 로그는 양보다 밀도가 중요하다.

좋은 로그는 대체로 다음 질문에 답할 수 있어야 한다.

  • 어떤 요청에서 발생한 일인가
  • 어디까지 정상적으로 진행됐는가
  • 어떤 이유로 실패했는가
  • 재현이나 후속 조치에 필요한 맥락이 남아 있는가

반대로 민감정보를 그대로 남기거나, 의미 없는 INFO 로그를 지나치게 많이 쌓으면 정작 중요한 순간에 읽히지 않는다. 로그는 남기는 순간보다 다시 읽는 순간을 기준으로 설계하는 편이 좋다.

14. 배포는 작고 되돌릴 수 있게 만든다

작은 단위 배포와 명확한 rollback 경로는 운영 안정성에 직접 연결된다. 특히 DB 스키마 변경이 있으면 애플리케이션 배포 순서와 호환 기간을 함께 고려해야 한다.

배포는 코드가 끝난 뒤 마지막에 하는 작업이 아니라, 설계 초반부터 같이 고려해야 하는 운영 문제다. 변경이 너무 크면 실패 원인을 찾기도 어렵고, rollback도 복잡해진다.

그래서 실무에서는 다음 원칙이 유용하다.

  • 기능은 가능한 작은 단위로 나눈다.
  • 한 번에 너무 많은 변경을 묶지 않는다.
  • rollback 경로를 사전에 생각한다.
  • DB 변경은 backward compatibility를 의식한다.

배포를 자주 하자는 말은 무조건 자주 배포하자는 뜻이 아니라, 작은 단위 변경을 안전하게 내보낼 수 있는 구조를 만들자는 뜻에 가깝다.

15. 추상화는 중복이 아니라 변화 방향으로 판단한다

겉으로 비슷해 보여도 앞으로 다르게 바뀔 가능성이 크다면 섣불리 공통화하지 않는 편이 낫다. 좋은 추상화는 현재 줄 수를 줄이는 기술이 아니라, 미래 변경 비용을 줄이는 판단이다.

주니어 때는 중복이 보이면 바로 공통화하고 싶어진다. 하지만 실무에서는 두 코드가 “지금만 비슷한가”, “앞으로도 같은 방향으로 바뀔 것인가”를 먼저 봐야 한다.

비슷해 보여도 아래와 같은 경우는 급하게 묶지 않는 편이 낫다.

  • 도메인 의미가 다르다
  • 변경 주체가 다르다
  • 정책이 자주 달라질 가능성이 있다
  • 공통화하면 오히려 읽는 사람이 의도를 파악하기 어려워진다

좋은 추상화는 중복 제거가 목적이 아니라, 비슷하게 변할 것들을 같이 묶는 것이다. 그래서 추상화는 문법 기술보다 변화 예측에 가깝다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

댓글

아직 댓글이 없습니다