
카프카를 사용하는 이유는 단순한 데이터 파이프라인이 아니라 고가용성(High Availability) 확보에 있다.
카프카는 반드시 3대의 브로커로 구성한다.
비용 부담이 있으므로 t3.micro 서비스를 사용하는 것을 추천
카프카 브로커의 권장 구성은 가용성 확보를 위해 3대의 독립된 서버에 각각 설치하는 것이며, 원활한 운영을 위해 서버당 최소 8GB 이상의 램(RAM) 사양을 권장합니다. 하지만 운영 비용이 부담될 수 있는 팀 프로젝트 환경에서는 사양을 낮추어 t3.micro(1GB RAM) 인스턴스를 활용하되, 부족한 메모리를 보완하기 위해 4GB 크기의 스왑(Swap) 공간을 설정하여 구성합니다.
카프카 파이프라인 보안 조치 안하면 누구나 토픽 생성 하고 데이터를 생산/소비 가능해
반드시 TSL/SSL 암호화 적용이 필요하다.
설치 난이도가 꽤 있는 편이라 시간을 충분히 두고 설치하는것을 추천해주셨다.
인프라에 시간 과투자 금지
아키텍처를 배우는 과정이기 때문에 인프라 구축에 시간을 할애해서 프로젝트를 완수하는것이 힘들 수 있다.
인프라 구축에 20% 만 할애하고 코드 구현 80%
아래 4가지를 프로젝트 시작 전에 구축하는것을 추천해주셨다.
1. 유레카 서버 (서비스 간 통신)
2. API 게이트웨이 (인증 처리)
3. 카프카 (이벤트 처리)
4. 컨피그 서버 (설정 관리)
강의 구성에서는 컨피그 서버를 나중에 배웠는데, 컨피그 서버를 설정하고 구성하는것을 추천해주셨다.
Config 서버는 설정 정보를 관리하는 방식에 따라 다음과 같이 나뉜다.
Git 기반 설정을 사용할 경우, 별도의 수동 refresh 없이도 설정 변경이 반영되도록 구성할 수 있다.
계층화된 설정으로 인스턴스마다 설정이 겹치는 부분이 많아서 common 설정을 사용하고 인스턴스에 따라 달라지는 부분만 새로 작성하거나 덮어 쓸 수 있다.
Config 서버 설정파일의 search-paths 속성은 설정 적용 순서를 결정한다.
먼저 써진 부분부터 우선 적용되며, 이후에 작성되는 설정은 기존 설정을 덮어쓴다.
튜터님께서 제공해주시는 설치 가이드 문서 참고하기.
우당탕탕...
class Solution {
public int solution(String t, String p) {
int answer = 0;
for(int i = 0; i < t.length() -p.length()-1; i++) {
String sub = t.substring(i, i+p.length()-1);
if(Integer.valueOf(sub) <= Integer.valueOf(p)) {answer++;}
}
return answer;
}
}
Integer.parseInt와 Integer.valueOf는 빈 문자열("")을 정수로 변환할 수 없다.
Exception in thread "main" java.lang.NumberFormatException: For input string: ""
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:68)
at java.base/java.lang.Integer.parseInt(Integer.java:662)
at java.base/java.lang.Integer.valueOf(Integer.java:983)
원인:
i + p.length() - 1이 t.length()를 넘어가, 빈 문자열이 나올 수 있다.해결:
반복문 조건을 i <= t.length() - p.length() 로 수정
p.length() 길이로 자르기는 → substring(i, i + p.length())이 맞음
문자열 -> int 변환은Integer.parseInt()
문자열 -> Integer 변환은 Integer.valueOf()
Integer.parseInt()는 32비트 정수 범위(-2,147,483,648 ~ 2,147,483,647) 만 처리 가능t = "12345678901234567890", p = "12345678901" 같은 경우 → 범위 초과!class Solution {
public int solution(String t, String p) {
int answer = 0;
for(int i = 0; i <= t.length() -p.length(); i++) {
String sub = t.substring(i, i+p.length());
if(Integer.parseInt(sub) <= Integer.parseInt(p)) answer++;
}
return answer;
}
}
long으로 변환해 해결해야한다.
if (Long.parseLong(sub) <= Long.parseLong(p)) answer++;
class Solution {
public int solution(String t, String p) {
int answer = 0;
for(int i = 0; i <= t.length() -p.length(); i++) {
String sub = t.substring(i, i+p.length());
if(Long.parseLong(sub) <= Long.parseLong(p)) answer++;
}
return answer;
}
}
사다리타기 결과 팀에서 Gateway 구축을 맡게 되었다! 😂
뭔가 모든 요청이 통하게 되는 중요한 역할을 맡게 된 것 같아 어깨가 무거워지지만... 프로젝트 초반에 결심했던 도메인을 전체적으로 보자는 목표에 가까워진 것 같아 한편으론 다행으로 여겨졌다. (아직 학습 프로젝트니까요!🙌)
다행히 친절하신 튜터님께서 세팅 가이드를 만들어주셨기 때문에 참고해서 만들 수 있어 더욱 마음이 놓였다.
프로젝트를 생성 할 때 가장 헷갈렸던 부분이 의존성 설정이었는데, 강의에서는 실습을 위해서 컨트롤러를 만들기 위해? Spring Web 의존성을 추가했었다. 그래서 나도 추가해야하나 고민이 되었는데, 찾아보니 Gateway의존성 (Spring Cloud Gateway)에서 이미 Spring WebFlux 의존성을 가지기 때문에 사용 하지 않아도 된다고 했다.
게다가 동기/비동기 설정에 충돌 가능성 때문에 둘은 동시에 사용하지 않는다고 했다.
Spring MVC (동기)WebFlux (비동기)우선 프로젝트는 다음과 같이 생성했다.

팀원들과 회의한 내용을 바탕으로 컨벤션에 따라 dev branch를 만들고,
Issue와 PR 템플릿을 세팅해줬다.
git init
git remote add origin <repo-url>
git add .
git commit -m "chore: initialize gateway project"
git push -u origin main
git checkout -b dev
git push origin dev
GitHub Docs - Using templates

마침 프로젝트 설정하면서 팀원들과 커밋 컨벤션에서 docs, chore 차이에 대해 이야기를 나눴는데, 이 템플릿 커밋은 어디에 속하는지 헷갈렸다.

템플릿은 GitHub의 동작과 협업 방식에 영향을 주는 “프로젝트/협업 환경 설정”이기 때문에 docs가 아니라 chore에 해당한다.
(나중에 이 템플릿을 사용해 문서를 작성 할 때가 docs)
git add .
git commit -m "chore: 이슈 및 PR 템플릿 추가"
git push origin dev
지난 프로젝트에서 이슈 크기를 너무 크게 잡아서 프로젝트 완성까지 이슈 하나를 닫게 되는 대참사가 일어나서... 이슈 크기에 대한 감을 잡고 가기로 했다.
❌ 나쁜 예 (너무 큼)
- 회원 시스템 만들기
- 결제 시스템 구현
너무 커서 진행 상황 안 보임 PR도 거대해짐 → 리뷰 지옥
✅ 좋은 예 (적절한 크기)
- 회원가입 API 구현
- 로그인 API 구현
- JWT 토큰 발급 로직 구현
1~2일 안에 끝남 PR도 자연스럽게 작아짐:
💡 게이트웨이 기준 예시
- API Gateway 라우팅 설정
- JWT 인증 필터 구현
- 공통 에러 처리 필터 추가
✅ 좋은 PR
리뷰어가 5~10분 안에 이해 가능해야 함 :
- 한 가지 기능만 포함
- 파일 3~10개 정도
- 100~300줄 정도
${환경변수이름:기본값} 형태로 작성하면 값이 주어지지 않았을 때 기본 값으로 동작한다.
server:
port: ${SERVER_PORT:3000}
spring:
application:
name: gateway-server
config:
import: 'optional:configserver:'
cloud:
config:
discovery:
enabled: true
service-id: config-server
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: ${EUREKA_URL:http://localhost:3101/eureka/}
부팅에 필수인 설정은 로컬(application.yml)
환경에 따라 바뀌는 설정은 Config Server
로컬에 두는 것
server.port)spring.application.name)→ 없으면 애플리케이션 실행 불가
Config Server에 두는 것
routes)→ 운영 중 변경 가능, 환경별로 달라짐
아직 프로젝트 발제 전이라 API 명세도 없어 정해진 게 없기 때문에
간단히 디스커버리 라우팅 활성화만 추가해 놓았다.
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true